release: cut v0.3.0, and make the package prove it contains what the notes claim - #96
release: cut v0.3.0, and make the package prove it contains what the notes claim#96AetherAI3 wants to merge 4 commits into
Conversation
|
CI closes the one thing the local candidate reported as All four required checks green on
Those Test jobs run the full
|
…notes claim On 2026-08-20 this repository described five different releases at once. package.json said 0.2.0. package-lock.json still said 0.1.0 — the #81 bump never touched it. RELEASE_NOTES.md's top entry described a 0.2.0 whose feature list was written at 477f0fc, before #72 added `aether skills`, `aether capabilities` and `aether support-bundle`. The npm registry served 0.1.0 and nothing else. And a packed aether-agents-0.2.0.tgz sat in the repo root from #83 until #90 deleted it, containing a build with no skills runtime in it at all. Nothing was lying on purpose. The four statements drifted apart because nothing compared them. 0.2.0 -> 0.3.0, not 0.2.1 ------------------------- #72 is a backwards-compatible feature addition — a skills runtime, three new CLI commands, six built-in skills shipped inside the package — that the v0.2.0 notes never mentioned. Semver makes that a minor bump. Reusing 0.2.0 was rejected on identity grounds. 0.2.0 already names a specific dated artifact: the August 19 notes entry, the README availability paragraph, install.sh's pin example, and that committed tarball. Publishing different contents under the same version would make two materially different artifacts answer to one name, which is the defect this release exists to close. 0.2.0 is retired unused; it will never be published. Reconciled ---------- - package.json, package-lock.json (both version fields) and src/version.ts all say 0.3.0. - RELEASE_NOTES.md leads with v0.3.0 covering 477f0fc..ed094dc: one feature, nine user-visible fixes, three test-only commits, one unwired module (#86 ship rail, which no command invokes), one docs commit. The v0.2.0 entry is marked superseded rather than rewritten — it is the record of what was written that day, not an install instruction. - README and the release log name 0.3.0, and both still say plainly that `npm i -g aether-agents` gives you 0.1.0, because it does. New gate: test/release_coherence.test.ts ---------------------------------------- Gate A asserts every place that names the release names the same one — including both package-lock version fields, which is the drift nothing was watching. Gate B is the load-bearing one: for every feature the release notes claim, the code behind it must be present in the file list `npm pack` would actually ship, and the command exposing it must be in the CLI registry. The source checkout's dist/ is NOT the package — the allowlist is dist/src plus four docs. Mutation-checked. Adding "!dist/src/commands/skills.js" to the files allowlist silently drops `aether skills` from the tarball: verify:production -> {"ok":true, ... "packedFiles":523} MISSED IT release_coherence -> FAIL: dist/src/commands/skills.js (agent skills runtime — `aether skills`) Restored: 7/7 pass, 524 packed files. New: npm run release:candidate ------------------------------ release.yml only runs after a founder has already tagged and published, so everything it checks is checked too late to change the decision. This runs the same sequence, in the same order, before the tag exists — against a detached git worktree of a specific commit, never the dirty checkout. A dirty tree is refused unless --allow-dirty, which marks the report commitBound:false rather than pretending. Every CLI proof runs the binary npm installed from the packed tarball into a clean prefix: --version, --help, `skills list`, `capabilities`, and the handoff demo (the demo harness is not shipped, so it is placed beside the installed package and resolves the CLI and its imports from the tarball's own dist/src). Skipped steps are recorded as "not-run" with a reason and can never read as a green tick — the full npm test suite is release.yml's gate and says so in the report. No string is handed to a shell: npm is reached through its own JS entrypoint under node, so every argument stays an argument. Also ---- - scripts/verify-production.ts exports createPackReport, so a gate reasoning about what a user receives asks npm instead of reading the build directory. - test/release_canaries.test.ts's coverage map said canaries 5 and 7 were unwritable. #86 and #87 wrote them two commits later; the excuse outlived the condition. The map is now enforced by a test that reads the delegated files. - docs/releases/OPERATOR-PACKET-v0.3.0.md: the founder-owned publish sequence, with what is proven and what is explicitly not. Gates at this commit: npm run typecheck exit 0 release + production test files 21 pass / 0 fail npm run verify:production --tag v0.3.0 ok Full npm test NOT run locally (a tool_executor test does not return on this Windows box); CI is the authority. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…te evidence Run against a63e1c6 in a detached worktree: commitBound true, ok true, exit 0. Tarball aether-agents-0.3.0.tgz, 524 entries, 589,829 bytes packed / 2,435,029 unpacked, sha256 25f33524bd866275674eccbf8cfe5706f14e925cb0ba35861dc6bc21a9245a2d. The digest came out identical from an uncommitted tree and from the detached worktree, which is recorded as content stability across a docs-only commit and explicitly NOT as a cross-machine reproducibility claim. Also records the mutation check: dropping dist/src/commands/skills.js from the files allowlist leaves verify:production reporting ok:true at 523 files, and fails release_coherence by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ional The ground moved while PR #96 was open. #98 squash-merged to main, main became 426b124, and the v0.3.0 notes said nothing about it — which is this lane's own defect arriving from the direction the lane did not gate. WORK A: the notes now cover #98 ------------------------------- Its user-visible half is three fixes, and the first is the exact failure class this codebase forbids: - `aether doctor --live` never received --live. main.ts parses non-strictly, so a flag the command had not declared was captured globally and stripped from what the command was handed. --live ran the fast configured-only report and EXITED 0, presenting a live end-to-end verification that was never performed. Unknown rendered as verified. --deep, --dry-run, --no-ui and --only were lost the same way, and the whole --fix repair path was unreachable. - `aether doctor --fix --yes` answered "re-run with --yes" to someone who had just passed it, because the global --yes never arrived either. - Command lookup lowercased the token while dispatch was case-sensitive, so `aether Vault` fell past the typo guard into a chat turn and billed a paid model call. The seam itself is announced too: a command now carries its own help metadata, flag table and loader in one entry, flag collisions are load-time errors, and reachability is structural rather than a regex over main.ts's source. Range moves 477f0fc..ed094dc -> 477f0fc..426b124, 16 commits -> 17, across RELEASE_NOTES.md, docs/releases/2026-08-22.md and the operator packet. Fixed a miscount while re-deriving: the packet said "9 user-visible fixes" over a list of ten. WORK B: the gate now runs both directions ----------------------------------------- Gate B ran notes -> package: a claim with no code behind it fails. It could not see the inverse — a user-visible command that ships with NO claim anywhere — and the inverse is what actually keeps happening, once per lane that lands between the note being written and the tag being cut. Gate C: every non-hidden command in the CLI registry must be announced by some release note, or named in SHIPPED_WITHOUT_A_NOTE with a reason. An explicit list is fine; silence is not. It is enforced in both directions — a stale entry fails, and an entry that IS announced fails — so it cannot rot into a permanent bypass that quietly absorbs the next unannounced command. Every entry must also appear in the operator packet, because the founder cutting the tag is the person who needs to know what goes out unannounced. 15 commands are on it today; all predate the release log or were announced by capability in the June 2026 entry, and none is new in this release. Mutation-checked, per the controller's instruction. Added to the registry: { name: "teleport", args: "<dest>", summary: "beam the working tree somewhere", section: "System" } verify:production -> {"ok":true, ... "packedFiles":527} MISSED IT release_coherence -> FAIL: no user-visible command ships without either a release note or a named exemption + [ 'teleport — beam the working tree somewhere' ] - [] Registry restored byte-identical (md5 37b3f944…, `git status src/` empty), 10/10 pass. cli_registry.ts is read-only for this lane: it was mutated only in the working tree, never committed, and the restore is verified by digest. Also: Gate B now reads the registry by IMPORTING it instead of by regex over its source. #98 replaced "reachability asserted by a regex over main.ts" for exactly this reason, and it immediately split the commands across CLI_COMMANDS and DISPATCH_COMMANDS — a source regex keyed on one array would have silently stopped covering the other. doctor is in the second one. Gates at this commit: npm run typecheck exit 0 release_coherence 10 pass / 0 fail Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…igest change The base moved to 426b124 (#98), so every number in the packet was taken against a tree that no longer exists. Re-run, commit-bound, at 22aa021: commitBound true, ok true, exit 0 aether-agents-0.3.0.tgz sha256 8c5c119d93cabf49af0c49c97addb055308d508af93f8675a26b6f5c8ecba307 527 entries, 597,400 packed / 2,459,474 unpacked The digest CHANGED from the a63e1c6 run (25f33524…, 524 entries), and the packet now says why that is the correct outcome rather than a discrepancy: #98 added dist/src/core/command_dispatch.* and rewrote main.js, so the packed content is genuinely different. A digest that had survived that change would have meant the pack was not reading the tree. Both mutation checks were re-measured against this base rather than carried forward: skills drop verify:production {"ok":true, ... "packedFiles":526} MISSED IT release_coherence FAIL: dist/src/commands/skills.js restored -> 527 files, 10/10 pass teleport verify:production {"ok":true, ... "packedFiles":527} MISSED IT release_coherence FAIL: 'teleport — beam the working tree somewhere' restored -> registry byte-identical, 10/10 pass I had initially edited the skills figure from 523 to 526 by inference. That is a number nobody measured, so it was re-run before being written down. Release-owned suite at this commit: 24 pass / 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
02a8aa8 to
e0f9458
Compare
|
Rebased onto All five checks green at
Two things worth flagging to a reviewer: The digest changed and that is correct. Gate C is the new direction. Gate B caught a note promising something the package lacks. It could not see a command shipping with no note at all — which is what #98 just did to this very PR. Both directions are now enforced, both mutation-proven at this base, and Still not merging. Tag and publish remain founder-owned, and #93/#94/#95/#97/#99/#100/#101/#102 are all expected to land first — each one moves the range and the digest again, which is why step 2 of the packet re-runs the candidate on the merge commit instead of trusting this PR's number. |
|
MERGE-TRAIN STATE — HELD (2026-08-22T15:56Z) Head: Evidence: required Ubuntu, Windows, supply-chain, and CodeQL checks passed on this exact head. Independent release review found the recorded Owner: release PR branch writer. Next legal action: run |
The problem
On 2026-08-20 this repository described five different releases at once, and nothing compared them:
package.json0.2.0package-lock.json(both version fields)0.1.0— the #81 bump never touched itRELEASE_NOTES.mdtop entry0.2.0whose feature list was written at477f0fc, before #720.1.0, the only version ever publishedaether-agents-0.2.0.tgzcommitted in the repo root, #83 → #90None of it was deliberate. The statements drifted apart because nothing asserted they had to agree.
Semver decision: 0.2.0 → 0.3.0 (minor)
#72 is a backwards-compatible feature addition the v0.2.0 notes never mention — a skills runtime, three new CLI commands (
aether skills,aether capabilities,aether support-bundle), and six built-in skills shipped inside the package. Semver makes that a minor bump.Reusing
0.2.0was rejected on identity grounds, not style.0.2.0already names a specific dated artifact: the August 19 notes entry, the README availability paragraph, theinstall.shpin example, and that committed tarball — whose contents predate the skills runtime entirely. Publishing different contents under the same version makes two materially different artifacts answer to one name, which is precisely the defect this release exists to close.0.2.0is retired unused. Nothing will ever be published under it.Feature boundary: everything the v0.2.0 notes described (never shipped) plus
477f0fc..426b124— 17 commits, 2026-08-19 08:39 EDT → 2026-08-22 11:12 EDT:What this PR does
package.json, bothpackage-lock.jsonversion fields, andsrc/version.tsall say0.3.0.RELEASE_NOTES.mdleads with v0.3.0; the v0.2.0 entry is marked superseded rather than rewritten, because it is the record of what was written that day, not an install instruction. New dated log atdocs/releases/2026-08-22.md, indexed.npm i -g aether-agentsgives you0.1.0, because it does.test/release_coherence.test.ts— a bidirectional gate (below).npm run release:candidate— runs the exactrelease.ymlsequence before the tag exists.docs/releases/OPERATOR-PACKET-v0.3.0.md— the founder-owned publish sequence with digest, manifest, the unannounced-command list, and an explicit list of what is not proven.The gate runs both directions
Gate A asserts every place naming the release names the same one — including both lockfile version fields, the drift nothing was watching.
Gate B (notes → package). For every feature the release notes claim, the code behind it must be in the file list
npm packwould actually ship, and the command exposing it must be in the CLI registry. The source checkout'sdist/is not the package — the allowlist isdist/srcplus four docs.Gate C (package → notes). The inverse: a user-visible command that ships with no claim anywhere in the notes. That is the direction that actually keeps happening — it happened to this very PR while it was open, when #98 landed on
mainand the notes said nothing about it. Every non-hidden command in the CLI registry must now be announced by some release note, or named inSHIPPED_WITHOUT_A_NOTEwith a reason. An explicit list is fine; silence is not.The list is enforced in both directions — a stale entry fails, and an entry that is announced fails — so it cannot rot into a permanent bypass that quietly absorbs the next unannounced command. Every entry must also appear in the operator packet, because the founder cutting the tag is the person who needs to know what goes out unannounced. 15 commands are on it today; all predate the release log or were announced by capability in the June 2026 entry, and none is new in this release.
login/logoutare exempt by rule via the registry's ownhiddenflag.Both mutations, measured at this base
Gate B — added
"!dist/src/commands/skills.js"to thefilesallowlist, silently droppingaether skillsfrom the tarball:Gate C — added a command to the registry and mentioned it nowhere:
Both restored: 527 packed files, 10/10 pass. The pre-existing production gate caught neither, because it does not know what the notes promised and does not know what the registry exposes.
src/commands/cli_registry.tsis read-only for this lane. The Gate C mutation existed only in the working tree, was never committed, and the restore is verified by digest (md5 37b3f944…,git status src/empty).Evidence —
npm run release:candidate, commit-boundRun against
22aa02141fba158927cb1f01e4344cfa3e8f1a01in a detached worktree of that commit, not the working checkout.commitBound: true,ok: true, exit 0:The last five lines all ran the binary
npm install --globalplaced on disk from that exact tarball, in a clean prefix. The handoff demo's harness is not shipped (allowlist isdist/srcplus four docs), so it is placed beside the installed package and resolves both the CLI and its own imports from the tarball'sdist/src.Tarball:
aether-agents-0.3.0.tgz, 527 entries, 597,400 bytes packed / 2,459,474 unpacked,sha256:8c5c119d93cabf49af0c49c97addb055308d508af93f8675a26b6f5c8ecba307.Registry / tag / release readings taken 2026-08-22, verbatim:
What changed on rebase
mainmoved to426b124while this PR was open. The branch was rebased (trial-merge was clean; no conflicts) and everything was re-derived:aether doctor --livenever received--live. The argv parse is non-strict, so a flag a command had not declared was captured globally and stripped from what the command was handed —--liveran the fast configured-only report and exited 0, presenting a live end-to-end verification that was never performed.--deep,--dry-run,--no-uiand--onlywere lost the same way, and the whole--fixpath was unreachable. Second:aether doctor --fix --yesreplied "re-run with--yes" to someone who had just passed it. Third: command lookup lowercased the token while dispatch was case-sensitive, soaether Vaultfell past the typo guard into a chat turn and billed a paid model call.a63e1c6eproduced25f33524…over 524 entries. feat(cli): command registration seam, and the flags doctor never received #98 addeddist/src/core/command_dispatch.*and rewrotemain.js, so the packed content genuinely differs. A digest that had survived that change would have meant the pack was not reading the tree.main.ts" for exactly this reason, and it immediately split the commands acrossCLI_COMMANDSandDISPATCH_COMMANDS— a source regex keyed on one array would have silently stopped covering the other.doctoris in the second one.Explicitly NOT proven
Named as unproven rather than omitted:
npm testsuite locally. The candidate runs the release-owned test files only and reportsnpm-testasnot-runwith a reason — it can never read as a green tick. Atool_executortest does not return on this Windows box; CI's reading is the authority, and CI is green at this head.npm auditis a reading at pack time, not a standing property.Shared-file collisions
None committed.
src/main.tsandsrc/commands/cli_registry.tsare not modified by this PR — this lane registers no command. The registry was read (Gate B/C import it) and temporarily mutated in the working tree for the Gate C mutation proof, then restored byte-identical and verified by digest before committing.Every changed file is inside the AA-REL-01 lease:
scripts/verify-production.tsgained exactly one thing:createPackReportis nowexported, so a gate reasoning about what a user receives asks npm instead of reading the build directory. No policy was weakened — the allowlist, audit, provenance and exact-tarball smoke test inrelease.ymlare untouched.test/release_canaries.test.tshad a coverage map claiming canaries 5 and 7 were unwritable. #86 and #87 wrote them two commits later; the excuse outlived the condition. The map is now enforced by a test that reads the delegated files rather than asserted in a comment.Founder-owned — this PR does none of it
npm run release:candidateon the merge commit and confirmok: true,commitBound: true. Do not tag against this PR's digest — the base is still moving.git tag -a v0.3.0 <merge-sha>and push it.v0.3.0— publication, not tag creation, triggersrelease.yml.npm-productionenvironment exists andNPM_TOKENis set before publishing the release, or the run fails after the release is already public.test/release_coherence.test.tsenforces that the claim cannot be added without the registry actually serving it.Full detail:
docs/releases/OPERATOR-PACKET-v0.3.0.md.No Aether credential, npm token or GitHub token was used, read, or written by any part of this work.
🤖 Generated with Claude Code