Release Truth: The Day Our Docs Lied About Our Version
The mupot substrate had four different releases living on at once. The code read , the roadmap said , and the tests that should have caught the drift used hardcoded literals that matched the stale docs. We rebuilt build-time identity stamping, decoupled the receipt tests, and derived every test pin from the one constant that moves — so a future cut that forgets the docs now fails CI instead of lying.
What existed: a version on paper, a version in code, and no overlap
On 2026-08-07, the mupot substrate had four releases living on main simultaneously — or rather, four claims about which release main was. None of them agreed.
The constants — package.json, src/version.ts (MUPOT_PUBLIC_API_VERSION), and the CHANGELOG.md heading — read 0.29.0. The ROADMAP.md advertised Stable release | v0.25.0 and a development target of v0.26.0. The README.md said the same. A heading in the roadmap promised ### v0.29.0: Distribution and Commercial Operations — planned, naming customer install, upgrade, and licensing work that had not shipped. So 0.29.0 meant two different releases at once: the code on main and a planned milestone that described different work entirely.
The GitHub git tag topped out at v0.25.0. Sixty-nine commits had merged after that tag, none of them represented in the roadmap’s version column.
A fact should appear in exactly one layer. The version of the substrate is a constant in . Everything else — the roadmap, the README, the changelog, the health endpoint, the test assertions — should derive from that single truth, not carry its own decorative copy. When each surface carries a literal, the literal drifts. That is what happened here.
Here are the concrete defects we found on main before the fix, each traceable to a commit:
| # | Where | What it said | What was true | Evidence |
|---|---|---|---|---|
| 1 | ROADMAP.md | Stable release | v0.25.0, dev target v0.26.0 | package.json and MUPOT_PUBLIC_API_VERSION were already 0.29.0 | #807 commit 8604c49 |
| 2 | ROADMAP.md | ### v0.29.0: Distribution and Commercial Operations — planned (install/upgrade/licensing) | 0.29.0 was the release-truth cut; Distribution work was never delivered — label named two different releases | #807 commit 8604c49 |
| 3 | ROADMAP.md | ”Release blocked” section cited commit count 65 | Blocker was resolved by #806; actual count was 69 | #807 commit 8604c49 |
| 4 | ROADMAP.md | Planned releases v0.26.0–v0.28.0 sat numerically below the cut | Cut jumped to 0.29.0 past all three; they could not stay beneath it | #807 commit 8604c49 |
And it was not just the docs. The /dashboard/motherboard page rendered “Version: v0.28.0” to users while /health reported 0.29.0 from the same build. The motherboard shipped inside 0.29.0; v0.28.0 was never cut. Two surfaces of one deployment disagreed about which release they were — the exact defect class this flight existed to close.
The deepest root was in the test suite itself. tests/dme-integration-runbook.test.ts asserted expect(pkg.version).toBe('0.25.0') — a hardcoded literal. tests/release-v023-readiness.test.ts did the same: expect(pkg.version).toBe('0.25.0') and expect(roadmap).toContain('Stable release | \v0.25.0`’)`. These tests passed while proving nothing, because the literal matched the stale docs, not the live constants. The test suite was a co-conspirator in the drift — it green-lit the very lie it was supposed to catch.
Why it mattered: a published receipt, a blocked cut, and the version that could not move
The drift was not cosmetic. It was a functional blocker.
docs/releases/dme-integration.md is a published tenant receipt — a contract with a live integration partner documenting the project-link addon lifecycle (version 1.0.0, compatibility ^0.24.0). tests/dme-integration-runbook.test.ts asserted that the runtime constants (pkg.version, MUPOT_PUBLIC_API_VERSION) still matched the receipt’s version pin. So any attempt to bump the substrate version — even the 0.25.0 → 0.26.0 step — broke the test.
Compounding the coupling, the native-addon compatibility grace in src/addons/registry.ts (one-minor window: current.minor === minimum.minor + 1) was already spent. The addon manifests declared mupotCompatibility: '^0.24.0'; at a substrate of 0.25.0, that was one minor back. At 0.26.0 it was two minors, and the addons stopped registering.
The result: the substrate could not version itself. PR #802 (commit 4f72168, 2026-08-07) recorded this honestly in the roadmap — claiming no version rather than asserting a release the code could not back. Its commit message is worth quoting directly:
The v0.29.0 cut was attempted and BLOCKED by a constraint neither I nor the gates knew existed: docs/releases/dme-integration.md is a published tenant receipt, and tests/dme-integration-runbook.test.ts asserts the LIVE constants still match it — so any bump fails. A roadmap asserting a release that cannot be cut would be the exact defect this flight exists to close.
That is the principle. A roadmap claiming a release that the code cannot deliver is not a plan — it is the same lie, one layer up. The receipt discipline that blocked us was the same discipline that should have caught the drift before it happened. The problem was never the receipt. It was that the tests guarding the receipt pinned the container version to a hardcoded literal instead of deriving it from the one constant that moves by definition.
How it was fixed: build-time stamping, constants-derived pins, and receipt decoupling
The fix landed in four commits on 2026-08-07, each addressing a specific layer of the drift.
flowchart TD A[“#801 — Build-time stampingcommit · clean · ref generated at buildbuild-info module untracked, [build] hooksin wrangler + pretest/pretypecheck”] B[“#802 — Roadmap: no version claimedrecords the blocker honestlyrefuses a release that cannot be cut”] C[“#806 — Atomic v0.29.0 cutconstants → 0.29.05 native manifests → ^0.29.0receipt tests decoupled (#805 option c)”] D[“#807 — Reconcile ROADMAP+README4 falsehoods fixedmotherboard literals derivedv023-readiness pin DERIVED”] A —> B B —> C C —> D
1. Build-time commit and clean stamping (#801)
scripts/generate-build-info.mjs stamps commit identity into src/build-info.ts at build time — never checked in, always generated. It captures the exact git rev-parse HEAD SHA, the working-tree cleanliness (git status --porcelain — tree status only, not deployment state), the branch ref, and built_at.
// scripts/generate-build-info.mjs — generated at build, never committed
export const BUILD_INFO: BuildInfo = {
commit: "12ff4b6e93b7b769...", // exact HEAD SHA
clean: true, // working tree status ONLY
builtAt: "2026-08-07T21:51:04Z",
ref: "main"
}src/health.ts falls back to BUILD_INFO.commit and BUILD_INFO.clean when the runtime RELEASE_SHA environment variable is missing. Before this, four production deploys on 2026-08-07 ran bare wrangler deploy without the wrapper and every /health response reported commit: null — a deployment with no provenance. The [build] hook in wrangler.toml and the pretest/pretypecheck hooks in package.json ensure the module is always present when tests run or code deploys. The generated src/build-info.ts is .gitignored — stamping is by construction, not by discipline.
2. The release that could not name itself (#802 → #806)
#802 recorded the blocker. #806 (commit 12ff4b6) cut through it with #805 option c — receipt test decoupling. The atomic action:
| File | Before | After |
|---|---|---|
package.json | "version": "0.25.0" (bouncing 0.25↔0.26 trying to land CI green) | "version": "0.29.0" |
src/version.ts | MUPOT_PUBLIC_API_VERSION = '0.25.0' | MUPOT_PUBLIC_API_VERSION = '0.29.0' |
CHANGELOG.md | ## Unreleased | ## 0.29.0 — 2026-08-08 |
| 5 native addon manifests | mupotCompatibility: '^0.24.0' | mupotCompatibility: '^0.29.0' |
The structural moment is the test decoupling. Here is the before/after from the diff:
// BEFORE — hardcoded literals (tests/dme-integration-runbook.test.ts)
expect(pkg.version).toBe('0.25.0')
expect(versionSource).toContain("MUPOT_PUBLIC_API_VERSION = '0.25.0'")
expect(addonManifest).toContain("mupotCompatibility: '^0.24.0'")
// AFTER — derived from the single moving constant (#805 option c)
import { MUPOT_PUBLIC_API_VERSION } from '../src/version'
expect(pkg.version).toBe(MUPOT_PUBLIC_API_VERSION)
expect(versionSource).toContain(`MUPOT_PUBLIC_API_VERSION = '${MUPOT_PUBLIC_API_VERSION}'`)
expect(addonManifest).toContain("mupotCompatibility: '^0.29.0'")The published receipt (dme-integration.md) still records 0.24.0 and ^0.24.0 as an immutable historical contract — that content never changes. What changed is the assertion about which container version guards it: the test now asks “does the live constant match the live constant?” instead of “does the live constant match a dead literal?” The receipt content stays pinned; the version pin moves with the code.
3. Reconciliation and the derived roadmap pin (#807)
#806 cut the constants but left the roadmap and README describing the old world. #807 (commit 8604c49) reconciled them — fixing all four falsehoods, retiring the “Release blocked” section, and renumbering the unshipped releases from 0.26→0.30 onward (scope stays preview, only the number moves).
The structural fix lives in tests/release-v023-readiness.test.ts:
// BEFORE — hardcoded roadmap pin; passed while proving nothing
expect(pkg.version).toBe('0.25.0')
expect(publicApiVersion).toContain("MUPOT_PUBLIC_API_VERSION = '0.25.0'")
expect(roadmap).toContain('Stable release | `v0.25.0`')
expect(roadmap).toContain('Console consolidation (project-centered nav) | `v0.26.0`')
// AFTER — roadmap pin DERIVED from the live constant
import { MUPOT_PUBLIC_API_VERSION } from '../src/version'
expect(pkg.version).toBe(MUPOT_PUBLIC_API_VERSION)
expect(publicApiVersion).toContain(`MUPOT_PUBLIC_API_VERSION = '${MUPOT_PUBLIC_API_VERSION}'`)
expect(roadmap).toContain(`Cut version | \`v${MUPOT_PUBLIC_API_VERSION}\``)
expect(roadmap).toContain('Last tagged release | `v0.25.0`')
// Plan numbers are NOT derivable — they move only by deliberate renumber.
expect(roadmap).toContain('Console consolidation (project-centered nav) | `v0.30.0`')The test comment explains the principle directly:
The roadmap pin is DERIVED from the live constant on purpose: a hardcoded version here goes stale silently at the next cut, which is exactly how the roadmap came to advertise v0.25.0 stable while the constants already read 0.29.0.
A future cut that forgets the roadmap now FAILS CI instead of drifting silently. The test cannot pass unless the roadmap and the live constant agree — the drift class is impossible by construction.
The addon-registry.test.ts trustClass boundary got the same treatment. Its expectation was moved from ^0.24.0 (too old — at 0.29.0 the manifest is rejected long before the trustClass distinction is tested, so the case passed while proving nothing) to ^0.28.0 (exactly on the grace boundary, where trustClass is what actually decides). The addon version literal in src/addons/torivers.ts was deliberately not coupled to MUPOT_PUBLIC_API_VERSION — addons have their own semver, and coupling them would reintroduce the same confusion one layer down.
::before-after Before: 69 commits past v0.25.0 with no way to name them — receipt coupling and spent addon grace blocked every version bump, and the roadmap claimed the cut would not ship. After: v0.29.0 cut, 5025 tests passing, every version surface derived from one constant, and CI fails if docs and code disagree. ::
Verification
npm test after #807: 5025 passed, 2 failed (both environmental — Playwright binary absent in the worktree, and a setup-script path assertion under a symlink). npm run typecheck: 0 errors. The 2 failures match the #806 baseline exactly — they are not related to these changes. design-status-contract-policy, check-test-schema-source, and check-migration-numbering all pass.
What’s next: the cut is made, the tag is owed, and the discipline is the point
The substrate constants now read 0.29.0. The roadmap says Cut version | v0.29.0 and Last tagged release | v0.25.0 — honest, because the git tag has not moved. That gap is recorded as owed: 0.29.0 is cut, untagged. The tag and a docs/releases/ record for 0.29.0 are the next obligation.
Renumbering the unshipped releases was a scoped move, not a renegotiation. Four unshipped milestones move up by four — Governed Tools 0.26→0.30, Agent Computers 0.27→0.31, Compounding Project Knowledge 0.28→0.32, Distribution 0.29→0.33. No promise was dropped, delivered, or reworded; the number was the only thing that moved, because the number was the only thing that was wrong.
The spine — the-spine-document-microkernel-for-agent-onboarding — is part of what ships in 0.29.0. The harness that runs it is prime-and-its-story. Understanding how we got here requires mupot-went-live and year-one-what-we-learned-in-twelve-months-of-substrate-first-ai; the state of the system at the June checkpoint is state-of-the-agent-harness-june-2026.
The lesson generalises. When a fact appears in multiple surfaces and each carries its own copy, the copies drift. The fix is always the same: one source of truth, everything else derived, and a test that fails when the derivation breaks. Decorative version claims are not documentation — they are deferred bugs. the-spine-document-microkernel-for-agent-onboarding makes the case that a microkernel approach to onboarding docs prevents the same class of drift at the agent level. The release-truth principle is its mirror at the version level: derive, don’t decorate.
— all claims traced to commits in : Test files verified: (derived pin at lines 104–116), (derived assertions at lines 88–91). Constant: → . Build stamping: . All evidence verified 2026-08-08 against the repository at .
Related links
The Spine: A Document Microkernel for Agent Onboarding
Mumega's spine splits agent truth into law, state, and runtime layers so onboarding becomes minutes instead of archaeology — and drift becomes a tracked defect, not a shrug.
Blog postPrime Agent — The Open Harness Athena Runs On
Mumega's architectural gate runs on Prime Agent — an MIT-licensed harness born from Mario Zechner's pi-mono, built by Prime Intellect. This is the story of the loom, the weaver, and the open model at the gate.