Skip to content

ci: enforce the checks a v1.0 tag depends on - #127

Merged
skyoo2003 merged 10 commits into
mainfrom
chore/release-hardening
Aug 9, 2026
Merged

ci: enforce the checks a v1.0 tag depends on#127
skyoo2003 merged 10 commits into
mainfrom
chore/release-hardening

Conversation

@skyoo2003

@skyoo2003 skyoo2003 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

The release procedure was documented end to end in docs/release.md but unenforced. Four defects were live on main, each one caught by a check the repo did not run. This adds the checks and turns the prose into a pre-flight checklist.

Related Issue

Refs #126

Changes

The fidelity manifest could drift silently. internal/generated is committed but derived, and nothing verified the committed output still matched its sources. cmd/devcloud/fidelity_test.go guards the manifest's shape — floors, registered services, the CRUD registry — but none of those notice an operation a provider gained and the manifest never did: absence from a 7,475-entry map is invisible. A new codegen-drift CI job regenerates and diffs.

Its first catch was this repo — the manifest's own doc comment was a release behind its template, because #126 edited the template without regenerating. That regenerated file is in this PR.

This is what makes the PRD's primary metric ("100% of reachable operations carry a declared fidelity tier, enforced by a test that fails the build") true rather than approximately true. It was enforced for two of the three ways an operation becomes reachable.

Eight changelog fragments rendered dead links. .changie.yaml declares Issue as type: int, minInt: 1, but changie batch does not validate custom fields on hand-written fragments and exits 0. Reproduced with changie batch v9.9.9 --dry-run:

* Per-operation fidelity manifest ... ([#](https://github.com/skyoo2003/devcloud/issues/))

All eight came from #126 and would have shipped verbatim in the v1.0 release notes. Fragments fixed; the release workflow now rejects a batched file containing one.

One fragment was in a directory nothing reads. .changes/unreleased/Fixed-113.yaml sat beside the real changes/unreleased/. changesDir: changes, so it was excluded from every release since it was written. Moved — the batch goes 25 → 26 entries.

A tag published without waiting for tests. release.yml verified the release notes existed, then ran GoReleaser. CI triggers on tags too, but the two workflows race, so a red commit could still publish binaries, container images and a Homebrew formula. The release job now needs: test.

Plus: docs/ ships inside the release archive, so docs are versioned by tag — the docs/ beside a binary describes that binary. A pre-flight checklist opens docs/release.md, including a deprecation review with the dashboardadmin rename as the precedent.

Test Plan

  • Drift guard proven to fire: injected case "DriftProbeOp": into internal/services/kms/provider.go, ran make codegen — the operation appeared in the manifest and git diff --exit-code internal/generated returned 1. Reverted; the manifest no longer contains it.
  • Codegen is reproducible: two consecutive make codegen runs on a clean tree produce byte-identical output across all 370 files in internal/generated. This resolves the plan's open question in favour of a full regeneration rather than a fidelity-only check.
  • Fragment fix verified: changie batch v9.9.9 --dry-run | grep -c 'issues/)' goes 8 → 0, and the recovered #113 entry appears in the batch.
  • CGO_ENABLED=1 go test ./... — 109 packages, exit 0
  • make test-compat — 775 passed
  • golangci-lint run ./... — 0 issues
  • Workflow and GoReleaser YAML parse; goreleaser check reports the config valid.

Checklist

  • Self-reviewed the code
  • Added/updated tests — the drift guard is a CI job rather than a Go test, because it needs the generator to run against the working tree
  • Lint/format passes (golangci-lint run)
  • Updated documentation (if applicable)
  • Added a Changie changelog fragment (Changed-20260809-180000.yaml, citing this PR)

Separately: goreleaser check reports the brews section as deprecated (homebrew_casks replaces it), which arrived with #121. Left alone here — casks and formulae differ in what brew install actually does, so that is a user-facing call rather than a mechanical rename.

The release procedure was documented end to end but unenforced, and four
defects were live on main.

The fidelity manifest could drift silently. internal/generated is committed
but derived, and nothing verified the committed output still matched its
sources. The Go tests guard the manifest's shape — floors, registered
services, the CRUD registry — but none of them notice an operation a provider
gained and the manifest never did; absence from a 7,475-entry map is
invisible. A codegen-drift job now regenerates and diffs. Its first catch was
this repo: the manifest's own doc comment was a release behind its template.

Eight changelog fragments carried an empty Issue field, which changie renders
as ([#](https://github.com/skyoo2003/devcloud/issues/)) and batches without
complaint. They would have shipped as dead links in the v1.0 release notes.
The fragments are fixed and the release workflow now rejects a batched file
containing one.

One fragment sat in .changes/unreleased/ rather than changes/unreleased/, so
it was excluded from every release since it was written. Moved; the batch
goes from 25 entries to 26.

A tag push published without waiting for tests. CI does trigger on tags, but
the two workflows race, so a red commit could still ship binaries, container
images and a Homebrew formula. The release job now needs a test job.

Also ships the docs tree inside the release archive, so docs are versioned by
tag: the docs/ beside a binary describe that binary.
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci CI/CD workflows and scripts labels Aug 9, 2026
@skyoo2003 skyoo2003 self-assigned this Aug 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a2a886e4d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Six follow-ups from review, each verified against the tree.

The drift check did not gate publishing. release.yml waited only on the Go
tests, which are exactly the checks that cannot see a stale fidelity manifest
— the reason the drift job exists. A tag could publish generated code that
misstates what the release serves. The release job now needs a codegen-drift
job of its own rather than trusting that CI won the race.

The drift check missed untracked output. `git diff --exit-code` ignores a
newly generated package, so adding a service model and forgetting to commit
its generated directory passed. cmd/codegen/main.go:18-20 already documents
this trap and smithy-sync.yml:44-46 already avoids it; both drift checks now
use `git status --porcelain`, as that one does.

codegen exited 0 after skipping a model. An unreadable or malformed model
printed to stderr and continued, so generation could be incomplete while the
drift check saw no changed files and called it clean. It now exits non-zero,
and does so before writing the CRUD registry and fidelity manifest — those
describe the whole fleet, and building them from a partial set would state in
generated code that a service's operations do not exist.

The issue-number guard only caught the empty case. `Issue: "abc"`, `"0"` and
`"-1"` all render links that go nowhere and all batched cleanly. The check now
requires every entry to end in a positive integer rather than enumerating the
malformed spellings.

Manual dispatch tested the wrong commit. actions/checkout defaults to the ref
the run was launched from, not the tag input, so the gate vouched for a branch
while GoReleaser was asked for a tag. Both jobs now check out the resolved tag.

The gate ran on amd64 only while GoReleaser publishes arm64 artifacts. The
test job now mirrors ci.yml's architecture matrix.
@github-actions github-actions Bot added the codegen Smithy codegen and generated code label Aug 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6b0dd7b4b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml Outdated
compat.yml triggers on branch pushes and pull requests only, so on a tag it
does not race the release the way CI does — it never runs at all. The boto3
suite is the guardrail this project leans on hardest, and it had no bearing on
what a tag published; docs/release.md listed it as a manual pre-flight step,
which is the judgment call the release gate exists to remove.

The pre-flight checklist now says which boxes the workflow re-runs and which
only a human catches, and quotes the drift check the way CI actually runs it
(git status --porcelain, not git diff --exit-code).
@skyoo2003

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2ea4012d25

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yml Outdated
Regenerating in place only overwrites the filenames the generator still emits.
internal/codegen/generator.go has no deletion path — no os.Remove, no
RemoveAll — so an output it stops emitting stays on disk, tracked and
unchanged, and `git status --porcelain` reports nothing. The drift check then
passes on generated code that no longer matches its generator, which is the
case the check exists to catch. The repo has hit this before: cmd/codegen/
main.go:16-22 exists because stubs deleted in #91 and #96 kept coming back.

Both drift jobs now clear internal/generated before regenerating, so a retired
or renamed output shows up as a deletion. Every tracked file under that tree
carries a generated marker, so nothing hand-written is at risk, and
scripts/generate-imports.sh writes to cmd/devcloud/imports.go, outside it.

Verified by committing a file the generator does not emit: in-place
regeneration left porcelain empty, clean-tree regeneration reported
` D internal/generated/zz_obsolete_gen.go`. A clean-tree run against the
current tree reproduces it byte for byte, so the stricter check starts green.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20a3e804da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/release.md Outdated
Comment thread .github/workflows/release.yml
Comment thread .goreleaser.yaml
… locally

Adding docs/ to the archive exposed how little of it resolved: measured
against a real snapshot archive, README.md and the docs tree reach seven
top-level files by relative path that were not shipped. Adding them takes the
archive from 26 of 28 relative links broken — the state before docs/ was
included at all — to 17 of 134, and every one of those points into source or
CI config, which a binary archive has no business carrying.

Verified by extracting dist/devcloud_..._linux_amd64.tar.gz and resolving each
link against the extracted tree: 117 of 134 work. Archive size 3.2M.

The pre-flight checklist also still told developers to regenerate in place,
which is the exact check the previous commit taught CI not to trust. It now
mirrors the clean-tree sequence.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e0d6a8f5f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml
The issue-link check filtered the '* ' entries and complained about the ones
that did not carry a valid link. With no '* ' entries at all the filter matched
nothing, `|| true` turned the empty result into success, and the gate passed
having checked nothing. changes/v0.1.0.md is exactly that shape — hand-written
prose with 25 '- ' bullets and no issue links — so a manual dispatch for that
tag would have replaced its release with notes this gate never inspected.

Now a notes file must contain at least one changie entry, and any other bullet
form is rejected outright: those are hand edits, which is what the link check
cannot vouch for and what docs/release.md already forbids.

Verified against the real files and synthetic cases: v0.1.0.md and a headers-
only file now fail on "no changie entries", a file mixing a changie entry with
a hand-written bullet fails on the bullet, a malformed issue link still fails,
and v0.2.0.md plus the current unreleased batch still pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 971a1009dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml Outdated
The hand-edit check enumerated bullet markers — '^[-+] ' — and its comment
claimed it rejected "any other bullet form". It rejected two of them. An
ordered item, an indented item and a pasted paragraph all passed, and the
entry check that follows only inspects '* ' lines, so those lines shipped
into a GitHub release with nothing about them verified:

  * Real entry ([#42](.../issues/42))
  1. Hand-written item with no issue link      <- old check: pass

Inverted it. A batched file holds only what .changie.yaml renders: the
version heading, one kind heading per section, and one '* ...' entry per
fragment. Allowing those three shapes and rejecting the rest covers every
form a human might reach for, including the ones nobody thought to list.

Verified against authentic output — `changie batch` (v1.25.0, current
config, all 27 unreleased fragments) and changes/v0.2.0.md both pass;
'1. text', '  - text', '- text' and a bare paragraph each fail with the
offending line numbered. The empty-notes and missing-file gates still
fire on changes/v0.1.0.md and an absent path.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 539aab14af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml Outdated
All four jobs resolved `github.event.inputs.tag || github.ref` on their own.
A tag is a mutable pointer: force-update it, or delete and recreate it, while
the run is in flight — which is what a maintainer does on spotting that the
wrong commit got tagged — and the gates check out one commit while the release
job checks out another. "The gates passed" then says nothing about the
artefacts GoReleaser publishes.

A new `resolve` job checks out the ref once, records `git rev-parse HEAD`, and
every job downstream takes `ref: ${{ needs.resolve.outputs.sha }}`. It carries
the tag name and the dry-run flag too, so the `resolve_tag` step inside
`release` and the ref expression repeated four times both collapse into it.

github.sha alone would not do: it is immutable, but on workflow_dispatch it is
the branch the run was launched from, not the tag that was typed in.

The release checkout also needs `fetch-tags: true`, and that is not redundant
with `fetch-depth: 0`. actions/checkout always fetches with --no-tags and
brings tags down only through an explicit refspec; for a SHA ref that refspec
is the bare commit (ref-helper.ts getRefSpec, git-command-manager.ts fetch, at
the pinned de0fac2). Pinning without it hands GoReleaser a repo with no tags
and no way to name the version. With it, a tag that moved after `resolve` no
longer points at HEAD, and `git describe --exact-match` inside GoReleaser's
git pipe fails the run rather than mispublishing.

Verified: the job graph parses, every `needs` resolves, all four downstream
checkouts pin to needs.resolve.outputs.sha, and the three outputs read are the
three declared. The resolve step emits tag+sha on a tag push and adds
extra_flags=--snapshot only under dry_run. The notes gate still passes real
`changie batch` output and still rejects every hand-edited fixture.
@skyoo2003

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f407e137ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml Outdated
…ased

The allowlist matched '^## ' and '^### ' by prefix, so it accepted any
version heading at all. Copy or rename an earlier release's notes file to
changes/<new-tag>.md and every check passed: the entries carry valid issue
links, nothing is hand-written, and GoReleaser hands the body through
verbatim — so the v1.0.0 release opens with a heading linking to the v0.2.0
release, dated to it.

The heading is now rebuilt from the tag and required exactly, exactly once.
Dots in the tag are escaped, which is not cosmetic: without it the regex for
v9.9.9 matches a heading reading v9x9x9.

Kind headings are restricted to the labels in .changie.yaml, read out of the
config rather than restated here so that adding a kind does not start failing
releases. If that extraction ever stops matching it yields an empty set,
which rejects every kind heading — the check fails loudly instead of silently
checking nothing, the same failure mode the empty-entry gate above exists for.

Verified against authentic `changie batch` output, 14 cases: correct tag
passes; the same file under a different tag, a stale heading, two headings,
no heading, '### Bugfixes', an ordered item, an indented item, a pasted
paragraph, an entry without an issue link, changes/v0.1.0.md and an absent
path all fail. changes/v0.2.0.md still passes under its own tag. Dot escaping
confirmed by control: the unescaped variant passes the v9x9x9 heading.
@skyoo2003
skyoo2003 merged commit 3c6da70 into main Aug 9, 2026
8 checks passed
@skyoo2003
skyoo2003 deleted the chore/release-hardening branch August 9, 2026 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/CD workflows and scripts codegen Smithy codegen and generated code documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant