diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5657267..152b09e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,3 +39,42 @@ jobs: run: | CGO_ENABLED=1 go build -o dist/devcloud ./cmd/devcloud CGO_ENABLED=1 go build -o dist/codegen ./cmd/codegen + + # internal/generated is committed but derived. The Go tests check the fidelity + # manifest's shape — floors, registered services, the CRUD registry — none of + # which notice an operation a provider gained and the manifest never did. + # Regenerating and diffing is the only check that does. + codegen-drift: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v7 + with: + go-version: "1.26" + + # Regenerating in place only overwrites the filenames the generator still + # emits — it never removes one it has stopped emitting, and + # internal/codegen/generator.go has no deletion path at all. Clearing the + # tree first turns a retired or renamed output into a visible deletion. + # Every tracked file under internal/generated carries a generated marker, + # so nothing hand-written is lost. + # + # No SQLite headers: cmd/codegen builds with CGO_ENABLED=0. + - name: Regenerate from a clean tree + run: | + rm -rf internal/generated + make codegen + + - name: Check the committed output is current + # --porcelain rather than `git diff --exit-code` so an added or removed + # file counts as drift instead of being silently dropped, matching + # smithy-sync.yml's check. + run: | + drift="$(git status --porcelain internal/generated)" + if [ -n "$drift" ]; then + echo "$drift" + echo "::error::internal/generated is stale. Run 'make codegen' and commit the result." + exit 1 + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7595b7b..dbd0ba8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,34 +23,209 @@ env: HOMEBREW_TAP_REPO: homebrew-tap jobs: + # A tag is a mutable pointer, and every job below would resolve it + # independently. Force-update or delete-and-recreate it while the run is in + # flight — which is exactly what a maintainer does on spotting that the wrong + # commit got tagged — and the gates vouch for one commit while GoReleaser + # publishes another. Resolving once here and passing the SHA down is what + # makes "the gates passed" a statement about the artefacts. + # + # The resolution has to happen in a job rather than an expression: github.sha + # is immutable but on workflow_dispatch it points at the branch the run was + # launched from, not the tag that was typed in. + resolve: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.resolve.outputs.tag }} + sha: ${{ steps.resolve.outputs.sha }} + extra_flags: ${{ steps.resolve.outputs.extra_flags }} + steps: + - name: Checkout the code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + - name: Pin the tag to a commit + id: resolve + run: | + echo "tag=${{ github.event.inputs.tag || github.ref_name }}" >> "$GITHUB_OUTPUT" + echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + if [[ "${{ github.event.inputs.dry_run }}" == "true" ]]; then + echo "extra_flags=--snapshot" >> "$GITHUB_OUTPUT" + fi + + # CI triggers on a tag push too, but the two workflows race — nothing stops + # GoReleaser from publishing binaries, images and a Homebrew formula off a red + # commit. These three jobs are what make the tag wait, and they mirror the + # checks in ci.yml rather than trusting that CI got there first. + test: + needs: resolve + strategy: + matrix: + # GoReleaser publishes arm64 artifacts, so the gate covers arm64 too. + runner: [ubuntu-latest, ubuntu-24.04-arm] + runs-on: ${{ matrix.runner }} + steps: + - name: Checkout the code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ needs.resolve.outputs.sha }} + - name: Set up Go + uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + - name: Install SQLite dev headers + run: sudo apt-get install -y libsqlite3-dev + - name: Run Go tests + run: CGO_ENABLED=1 go test ./... + + # The Go tests cannot see a stale fidelity manifest — that is the whole reason + # ci.yml has a codegen-drift job — so waiting only on `test` would let a tag + # publish generated code that misstates what the release serves. + codegen-drift: + needs: resolve + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ needs.resolve.outputs.sha }} + - name: Set up Go + uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + # Cleared first so a retired or renamed output shows up as a deletion; the + # generator overwrites what it still emits but never removes what it does + # not. See the same step in ci.yml. + - name: Regenerate from a clean tree + run: | + rm -rf internal/generated + make codegen + - name: Check the committed output is current + run: | + drift="$(git status --porcelain internal/generated)" + if [ -n "$drift" ]; then + echo "$drift" + echo "::error::internal/generated is stale on this tag. Run 'make codegen', commit, and re-tag." + exit 1 + fi + + # compat.yml triggers on branch pushes and pull requests only, so on a tag it + # does not race the release — it never runs at all. Without this job the boto3 + # suite, the guardrail the project leans on hardest, has no bearing on what + # gets published. + compat: + needs: resolve + runs-on: ubuntu-latest + steps: + - name: Checkout the code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ needs.resolve.outputs.sha }} + - name: Set up Go + uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + - name: Install SQLite dev headers + run: sudo apt-get install -y libsqlite3-dev + - name: Build devcloud binary + run: CGO_ENABLED=1 go build -o dist/devcloud ./cmd/devcloud + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: "3.12" + - name: Install Python dependencies + run: pip install -r tests/compatibility/requirements.txt + - name: Run compatibility tests + run: pytest tests/compatibility/ -v --tb=short + env: + DEVCLOUD_BIN: dist/devcloud + release: + needs: [resolve, test, codegen-drift, compat] runs-on: ubuntu-latest permissions: contents: write packages: write attestations: write steps: + # fetch-tags is not redundant with fetch-depth. actions/checkout always + # fetches with --no-tags and brings tags down only via an explicit refspec, + # and for a SHA ref that refspec is the bare commit — so pinning the commit + # without this leaves a repo with no tags at all, and GoReleaser cannot name + # the version it is releasing. With both, a tag that moved after `resolve` + # no longer points at HEAD and GoReleaser refuses the run outright. - name: Checkout the code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 + fetch-tags: true + ref: ${{ needs.resolve.outputs.sha }} - name: Set up Go uses: actions/setup-go@v7 with: go-version-file: go.mod cache-dependency-path: go.sum - - name: Resolve tag - id: resolve_tag + - name: Verify changie release notes run: | - echo "tag=${{ github.event.inputs.tag || github.ref_name }}" >> "$GITHUB_OUTPUT" - if [[ "${{ github.event.inputs.dry_run }}" == "true" ]]; then - echo "extra_flags=--snapshot" >> "$GITHUB_OUTPUT" + notes="changes/${{ needs.resolve.outputs.tag }}.md" + if [ ! -f "$notes" ]; then + echo "::error::Changie fragment $notes not found." + echo "::error::Run 'changie batch ${{ needs.resolve.outputs.tag }}' and 'changie merge' before pushing the tag." + exit 1 fi - - name: Verify changie release notes exist - run: | - if [ ! -f "changes/${{ steps.resolve_tag.outputs.tag }}.md" ]; then - echo "::error::Changie fragment changes/${{ steps.resolve_tag.outputs.tag }}.md not found." - echo "::error::Run 'changie batch ${{ steps.resolve_tag.outputs.tag }}' and 'changie merge' before pushing the tag." + # changie renders every entry as '* ...', so a notes file with none of + # them was not batched from fragments. Checking that first stops the + # entry validation below from passing on an empty match set — a + # hand-written file (changes/v0.1.0.md is one) would otherwise sail + # through with nothing checked at all. + if ! grep -q '^\* ' "$notes"; then + echo "::error::$notes contains no changie entries. Release notes come from fragments — run 'changie batch ${{ needs.resolve.outputs.tag }}' and 'changie merge'." + exit 1 + fi + # changie renders the heading for the version it batched, so a prefix + # match on '## ' accepts a file copied or renamed from an earlier + # release: the notes for this tag then open with a link to that other + # release, dated to it, and GoReleaser passes it through verbatim as + # the release body. Rebuild the heading .changie.yaml would have + # produced for this tag and require exactly that, exactly once. + tag="${{ needs.resolve.outputs.tag }}" + want="^## \[${tag//./\\.}\]\(https://github\.com/${{ github.repository }}/releases/tag/${tag//./\\.}\) - [0-9]{4}-[0-9]{2}-[0-9]{2}$" + if [ "$(grep -cE '^## ' "$notes")" != "1" ] || ! grep -qE "$want" "$notes"; then + grep -n '^## ' "$notes" || true + echo "::error::$notes needs exactly one changie version heading, naming $tag. Run 'changie batch $tag' instead of copying or renaming an earlier release's file." + exit 1 + fi + # A batched file holds only what .changie.yaml renders: that heading, + # one kind heading per section, and one '* ...' entry per fragment. + # Allowing those three shapes and rejecting every other line catches + # the hand edits the entry check below cannot vouch for. Enumerating + # bullet markers instead misses everything that is not one — '1. text', + # an indented ' - text', a pasted paragraph — and each of those + # publishes with nothing about it checked. + # + # The kinds are read out of the config rather than restated here, so + # adding one to .changie.yaml does not start failing releases. An + # extraction that stops matching yields an empty set, which rejects + # every kind heading and fails loudly rather than checking nothing. + kinds="$(sed -n 's/^ - label: //p' .changie.yaml | paste -sd'|' -)" + foreign="$(grep -nvE "^$|^## |^### ($kinds)$|^\* " "$notes" || true)" + if [ -n "$foreign" ]; then + echo "$foreign" + echo "::error::The lines above are not changie output. Move them into changes/unreleased fragments and re-batch." + exit 1 + fi + # 'changie batch' does not validate custom fields on hand-written + # fragments, so an Issue that is empty, zero or non-numeric renders a + # dead link and batches without complaint. Every entry must end in + # .../issues/), so check the good form rather than + # enumerating the bad ones. + bad="$(grep '^\* ' "$notes" | grep -vE 'issues/[1-9][0-9]*\)\)$' || true)" + if [ -n "$bad" ]; then + echo "$bad" + echo "::error::The entries above do not end in a valid issue link. Fix the Issue field in their changes/unreleased fragment, then re-batch." exit 1 fi - name: Set up Docker Buildx @@ -79,7 +254,7 @@ jobs: with: distribution: goreleaser version: "~> v2" - args: release --clean ${{ steps.resolve_tag.outputs.extra_flags }} --release-notes changes/${{ steps.resolve_tag.outputs.tag }}.md + args: release --clean ${{ needs.resolve.outputs.extra_flags }} --release-notes changes/${{ needs.resolve.outputs.tag }}.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Always defined: {{ .Env.HOMEBREW_TAP_TOKEN }} fails to render if the diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9d155cf..eebb6f3 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -32,6 +32,22 @@ archives: - LICENSE - README.md - CHANGELOG.md + # Docs are versioned by tag, and the ones stating what a release promises — + # the fidelity manifest, the compatibility policy — are only true of the + # binary they ship beside. + - docs + # README.md and the docs tree reach these by relative path, so leaving them + # out dangles the archive's own entry point: before docs/ was added here, + # 26 of README.md's 28 relative links resolved to nothing. What stays broken + # points into source and CI config, which a binary archive has no business + # carrying. + - CONTRIBUTING.md + - CODE_OF_CONDUCT.md + - GOVERNANCE.md + - SECURITY.md + - SUPPORT.md + - NOTICE + - TRADEMARKS.md changelog: disable: false diff --git a/changes/unreleased/Added-20260809-120000.yaml b/changes/unreleased/Added-20260809-120000.yaml index b15def8..a7e69f3 100644 --- a/changes/unreleased/Added-20260809-120000.yaml +++ b/changes/unreleased/Added-20260809-120000.yaml @@ -2,4 +2,4 @@ kind: Added body: Per-operation fidelity manifest declaring every operation as hand-verified, auto-crud or unimplemented, exposed at `GET /devcloud/api/fidelity` and enforced by a build-failing coverage test time: 2026-08-09T12:00:00.000000+09:00 custom: - Issue: "" + Issue: "126" diff --git a/changes/unreleased/Added-20260809-140000.yaml b/changes/unreleased/Added-20260809-140000.yaml index 50620c6..9c59145 100644 --- a/changes/unreleased/Added-20260809-140000.yaml +++ b/changes/unreleased/Added-20260809-140000.yaml @@ -2,4 +2,4 @@ kind: Added body: Real tag support for KMS, CloudWatch and EventBridge — TagResource, UntagResource and ListTagsForResource/ListResourceTags now persist tags per resource ARN instead of being echoed by the generic CRUD engine time: 2026-08-09T14:00:00.000000+09:00 custom: - Issue: "" + Issue: "126" diff --git a/changes/unreleased/Changed-20260809-180000.yaml b/changes/unreleased/Changed-20260809-180000.yaml new file mode 100644 index 0000000..cdf76e9 --- /dev/null +++ b/changes/unreleased/Changed-20260809-180000.yaml @@ -0,0 +1,5 @@ +kind: Changed +body: Release archives now carry the `docs/` tree alongside the binary, so the documentation you unpack — including the fidelity manifest and the release's compatibility promises — describes exactly the version you downloaded +time: 2026-08-09T18:00:00.000000+09:00 +custom: + Issue: "127" diff --git a/.changes/unreleased/Fixed-113.yaml b/changes/unreleased/Fixed-20260725-010755.yaml similarity index 80% rename from .changes/unreleased/Fixed-113.yaml rename to changes/unreleased/Fixed-20260725-010755.yaml index 8973b11..228bace 100644 --- a/.changes/unreleased/Fixed-113.yaml +++ b/changes/unreleased/Fixed-20260725-010755.yaml @@ -1,4 +1,5 @@ kind: Fixed body: Config-time warnings (deprecated `dashboard` key, unknown `DEVCLOUD_SERVICES` tier) now honor `logging.format`/`logging.level` instead of always printing as plain text before the logger is configured +time: 2026-07-25T01:07:55.000000+09:00 custom: - Issue: "113" + Issue: "113" diff --git a/changes/unreleased/Fixed-20260809-120100.yaml b/changes/unreleased/Fixed-20260809-120100.yaml index 7d4df19..0ed35f3 100644 --- a/changes/unreleased/Fixed-20260809-120100.yaml +++ b/changes/unreleased/Fixed-20260809-120100.yaml @@ -2,4 +2,4 @@ kind: Fixed body: Codegen now parses operations bound to Smithy resource shapes, recovering 285 operations that were invisible to the generator (bedrock 0 of 101, lambda 19 of 85, ecs 12 of 76, transfer 29 of 71, sso-admin 67 of 79) time: 2026-08-09T12:01:00.000000+09:00 custom: - Issue: "" + Issue: "126" diff --git a/changes/unreleased/Fixed-20260809-140100.yaml b/changes/unreleased/Fixed-20260809-140100.yaml index 522bcb8..984da28 100644 --- a/changes/unreleased/Fixed-20260809-140100.yaml +++ b/changes/unreleased/Fixed-20260809-140100.yaml @@ -2,4 +2,4 @@ kind: Fixed body: The fidelity manifest now reads each provider's actual dispatch instead of intersecting with the Smithy model, recovering 226 served operations it had hidden (dynamodbstreams listed 4 of its 22, acm's UpdateCertificate, bedrock's InvokeModelWithResponseStream) and dropping 5 non-operations it had invented (identitystore Description/DisplayName/Emails, pipes DELETE/POST) time: 2026-08-09T14:01:00.000000+09:00 custom: - Issue: "" + Issue: "126" diff --git a/changes/unreleased/Fixed-20260809-160000.yaml b/changes/unreleased/Fixed-20260809-160000.yaml index ff5a908..e0f6292 100644 --- a/changes/unreleased/Fixed-20260809-160000.yaml +++ b/changes/unreleased/Fixed-20260809-160000.yaml @@ -2,4 +2,4 @@ kind: Fixed body: EventBridge now drops a bus's or rule's tags when it is deleted. ARNs are derived from the name, so recreating a deleted resource reused its ARN and inherited the previous tags time: 2026-08-09T16:00:00.000000+09:00 custom: - Issue: "" + Issue: "126" diff --git a/changes/unreleased/Fixed-20260809-160100.yaml b/changes/unreleased/Fixed-20260809-160100.yaml index 13fd2f2..6055e50 100644 --- a/changes/unreleased/Fixed-20260809-160100.yaml +++ b/changes/unreleased/Fixed-20260809-160100.yaml @@ -2,4 +2,4 @@ kind: Fixed body: Restored CloudWatch's 17 CRUD-engine operations. The gateway picks the protocol from the request, not from the provider, so CloudWatch reaches the engine whenever a client speaks JSON — filtering the registry by the provider's declared protocol had removed that coverage outright time: 2026-08-09T16:01:00.000000+09:00 custom: - Issue: "" + Issue: "126" diff --git a/changes/unreleased/Fixed-20260809-170000.yaml b/changes/unreleased/Fixed-20260809-170000.yaml index 1eb0996..d7ca838 100644 --- a/changes/unreleased/Fixed-20260809-170000.yaml +++ b/changes/unreleased/Fixed-20260809-170000.yaml @@ -2,4 +2,4 @@ kind: Fixed body: EventBridge rule ARNs now name their event bus, as AWS does. A rule name is unique per bus, so same-named rules on two custom buses previously shared one ARN — and with it, one tag set, where tagging one rule changed the other's and deleting one wiped the survivor's time: 2026-08-09T17:00:00.000000+09:00 custom: - Issue: "" + Issue: "126" diff --git a/changes/unreleased/Fixed-20260809-170100.yaml b/changes/unreleased/Fixed-20260809-170100.yaml index 0b5793d..38bc9d6 100644 --- a/changes/unreleased/Fixed-20260809-170100.yaml +++ b/changes/unreleased/Fixed-20260809-170100.yaml @@ -2,4 +2,4 @@ kind: Fixed body: CloudWatch now drops an alarm's tags when the alarm is deleted, so recreating an alarm under the same name no longer inherits the old one's tags time: 2026-08-09T17:01:00.000000+09:00 custom: - Issue: "" + Issue: "126" diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go index 3939b6b..8fa3bdf 100644 --- a/cmd/codegen/main.go +++ b/cmd/codegen/main.go @@ -47,6 +47,10 @@ func main() { var crudServices []codegen.CRUDServiceData modelOps := make(map[string][]string) + // A model that cannot be read or parsed is skipped, which used to leave the + // exit status at 0 — so a drift check downstream saw no changed files and + // called incomplete generation clean. + skipped := false for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") { @@ -56,12 +60,14 @@ func main() { data, err := os.ReadFile(filepath.Join(*modelsDir, entry.Name())) if err != nil { fmt.Fprintf(os.Stderr, "Error reading %s: %v\n", entry.Name(), err) + skipped = true continue } model, err := codegen.ParseSmithyJSON(data) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing %s: %v\n", entry.Name(), err) + skipped = true continue } @@ -94,6 +100,15 @@ func main() { } } + // Bail before the aggregate artefacts: the CRUD registry and the fidelity + // manifest describe the whole fleet, and writing them from a set that is + // missing a service would state, in generated code, that its operations do + // not exist. + if skipped { + fmt.Fprintln(os.Stderr, "Error: one or more models were skipped; generated output is incomplete") + os.Exit(1) + } + // Write the aggregate CRUD registry only when generating the full fleet // (a filtered run would otherwise clobber it with a partial registry). if len(allowedServices) == 0 { diff --git a/docs/release.md b/docs/release.md index 7621275..587f8fe 100644 --- a/docs/release.md +++ b/docs/release.md @@ -32,6 +32,37 @@ small YAML file under `changes/unreleased/`. Commit it alongside your code chang Config lives in [`.changie.yaml`](../.changie.yaml). +Prefer `changie new` over writing the YAML by hand: it enforces the issue number, and a +fragment without one renders as a dead link in the release notes. + +## Pre-flight checklist + +Run through this before batching. + +The first three are re-run by the Release workflow against the tagged commit, which refuses to +publish if any fails — tick them to find out on your machine rather than from a failed tag. +The rest are only caught here. + +- [ ] **Generated code is current** — + `rm -rf internal/generated && make codegen && git status --porcelain internal/generated` + prints nothing. `internal/generated` is committed but derived; a stale fidelity manifest + misreports what the release can be trusted to do. Clear the tree first, as CI does: the + generator overwrites the outputs it still emits but never removes one it has stopped + emitting, so regenerating in place leaves a retired file looking current. +- [ ] **boto3 compatibility passes** — `make test-compat`. +- [ ] **Go tests pass** — `CGO_ENABLED=1 go test ./...`. +- [ ] **`main` is green**, including lint and CodeQL. +- [ ] **Every unreleased fragment carries an issue number** — + `grep -L 'Issue: "[0-9]' changes/unreleased/*.yaml` prints nothing. +- [ ] **`changes/unreleased/` is not empty.** No fragments means either nothing shipped or + someone forgot one. +- [ ] **Deprecation review.** If this release *removes* anything previously deprecated — a + config key, an env var, an admin route — confirm it shipped for at least one release + with a warning first. The precedent is the `dashboard` → `admin` rename in + [`internal/config/config.go`](../internal/config/config.go): the old key kept working, + emitted a warning, and only then became removable. Removing without that overlap is a + major-version change. + ## Cutting a release 1. **Make sure `main` is green** and holds all changes you want in the release. @@ -70,10 +101,18 @@ Config lives in [`.changie.yaml`](../.changie.yaml). Pushing the tag triggers the Release workflow. It will: -- verify `changes/v0.3.0.md` exists (guard against tagging without release notes), +- resolve the tag to a commit **once**, up front, and check that same SHA out in every job that + follows. Moving the tag mid-run therefore cannot make the guardrails vouch for one commit + while GoReleaser publishes another, +- re-run the guardrails against that commit and stop before publishing anything if any of them + fails: the Go test suite on amd64 and arm64, the boto3 compatibility suite, and the codegen + drift check. CI is not relied on here — it races the tag, and `compat.yml` does not trigger + on tags at all, +- verify `changes/v0.3.0.md` exists (guard against tagging without release notes), contains + only what `changie batch` renders, and that no entry in it is missing its issue number, - run GoReleaser, which builds binaries for **darwin/linux/windows × amd64/arm64**, packages - them as `tar.gz` (`zip` on Windows) with `LICENSE`/`README.md`/`CHANGELOG.md`, and generates - a SHA-256 `CHECKSUMS` file, + them as `tar.gz` (`zip` on Windows) with the `docs/` tree and the top-level files it and + `README.md` link to, and generates a SHA-256 `CHECKSUMS` file, - build and push `*-alpine` container images to `ghcr.io/skyoo2003/devcloud`, - publish a **GitHub Release** whose notes come from `changes/v0.3.0.md` (`--release-notes`, `mode: replace`). @@ -91,4 +130,10 @@ GHCR or GitHub Releases. - The tag (`v0.3.0`) and the fragment file (`changes/v0.3.0.md`) must match exactly. - `changie batch` + `changie merge` must be committed **before** the tag is pushed. +- The tagged commit must pass the Go test suite; the workflow will not publish otherwise. +- Every entry in the batched notes needs an issue number. - No manual GitHub Release editing — release notes are owned by Changie fragments. + +Docs ship inside the release archive, so they are versioned by tag: the `docs/` tree in +`devcloud_v0.3.0_linux_amd64.tar.gz` describes exactly the binary beside it. There is no +separate docs site to version. diff --git a/internal/generated/fidelity/manifest_gen.go b/internal/generated/fidelity/manifest_gen.go index 99e93c1..acaf3e3 100644 --- a/internal/generated/fidelity/manifest_gen.go +++ b/internal/generated/fidelity/manifest_gen.go @@ -6,8 +6,9 @@ // to "can I trust this call?" — see docs/fidelity-manifest.md. // // The manifest is derived, never hand-written: the operation universe comes from -// the in-tree Smithy models, auto-crud from the generated CRUD registry, and -// hand-verified from the dispatch literals of each provider. +// the in-tree Smithy models unioned with what providers serve, auto-crud from +// the generated CRUD registry, and hand-verified from the dispatch literals of +// each provider's HandleRequest. package fidelity // Tier is how faithfully an operation is served. @@ -19,7 +20,12 @@ const ( // TierAutoCRUD is served by the generic CRUD engine with plausible, // store-backed responses: no validation, no business logic. TierAutoCRUD Tier = "auto-crud" - // TierUnimplemented returns an honest InvalidAction error. + // TierUnimplemented is not served: the call fails with an error rather than + // a made-up success. The error is the provider's own, not one shared code — + // JSON and Query services fall through to InvalidAction (HTTP 400), while + // the path-routed providers answer in their own vocabulary (s3 + // MethodNotAllowed, lambda ResourceNotFoundException, bedrock + // UnsupportedOperation). TierUnimplemented Tier = "unimplemented" )