feat: generate the MANIFEST inventory and gate it against the corpus - #177
Merged
Conversation
MANIFEST.md's record inventory is a pure function of docs/adr/, and it was hand-maintained: it drifted six records before anyone noticed (#131). A guard that merely reported the divergence would still leave a human to hand-edit the table, which is how it drifted, so this generates the block and asserts the tree is unchanged afterwards — the same shape as the schema emit and the committed Action bundle. `scripts/emit-manifest.ts` renders the record table and status counts from `adr graph --format json` between stable markers; `bun run emit:manifest && git diff --exit-code MANIFEST.md` joins the existing guards in `clean-clone-builds`. No public CLI surface is added: the CLI stays read-only and hook-safe, and the writing is a repo-local script (#132). Prose outside the markers — including judgment about what is open — stays hand-written. Every way of generating nothing is an error rather than a silent no-op, since a generator that writes nothing leaves the diff clean and the gate green while checking nothing (ADR-0016): absent, duplicated, or inverted markers and an empty node list all throw. The generator re-sorts nodes with `compareCodeUnits` rather than trusting `buildAdrGraph`'s `localeCompare` order, which ADR-0033 clause 8 pins, so the no-diff gate cannot fail because two contributors ran it under different ICU locales. Also fires `item.tier-absent` when a proposed record has entered review but its routing tier cannot be determined (#111). The spec's carve-out is two-conditioned — `review` block *and* top-level `reviewBy` both absent, on the reasoning that the record has "not yet entered into the review workflow" — but only the first condition was implemented. ADR-0022 therefore sat in the queue as cross-team with `reviewBy: 2027-02-08`, `tier=None`, and no finding, and was ratified without a routing tier. spec.md, research.md, and contracts/kernel.md now state the same rule as the code. Severity stays `info`, so no exit code changes, and a proposed record with neither field stays silent. Observed failing before being relied on, per ADR-0016: the queue case against the pre-fix kernel, and the marker, empty-corpus, and stale-inventory cases as permanent negative tests. Closes #131 Closes #111 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
There was a problem hiding this comment.
🟡 Changes recommended
The committed Action bundle remains stale, and valid multiline or large numeric values can produce incorrect manifest output.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Generates MANIFEST.md inventory from the ADR corpus and fixes missing queue-tier findings.
Changes:
- Adds and gates deterministic manifest generation.
- Handles review deadlines without routing tiers.
- Updates contracts, documentation, and tests.
File summaries
| File | Description |
|---|---|
.github/workflows/ci.yml |
Gates generated inventory drift. |
CHANGELOG.md |
Documents both changes. |
MANIFEST.md |
Adds generated ADR inventory. |
docs/RELEASING.md |
Updates release guidance. |
package.json |
Adds the manifest script. |
packages/core/src/queue/kernel.ts |
Expands tier-absence detection. |
packages/core/test/ordering-contract.test.ts |
Extends deterministic-order scanning. |
packages/core/test/queue/kernel.test.ts |
Covers deadline-only review entry. |
scripts/emit-manifest.test.ts |
Tests generation and failure cases. |
scripts/emit-manifest.ts |
Implements inventory generation. |
specs/007-arb-queue/contracts/kernel.md |
Updates the kernel contract. |
specs/007-arb-queue/research.md |
Clarifies finding conditions. |
specs/007-arb-queue/spec.md |
Clarifies the review carve-out. |
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+125
to
+126
| const enteredReview = review !== undefined || frontmatter.reviewBy != null; | ||
| if (enteredReview && review?.tier == null) { |
Comment on lines
+71
to
+74
| const numbers = ids.map(Number); | ||
| for (let index = 1; index < numbers.length; index += 1) { | ||
| if (numbers[index]! !== numbers[index - 1]! + 1) return null; | ||
| } |
Comment on lines
+93
to
+96
| /** Markdown table cells cannot carry a raw `|`, and titles are free text. */ | ||
| function tableCell(value: string): string { | ||
| return value.replaceAll('\\', '\\\\').replaceAll('|', '\\|'); | ||
| } |
`packages/ci/dist/queue-action.js` embeds the queue kernel, so the #111 change to `computeItemFindings` has to be rebuilt into it. Caught by the existing `git diff --exit-code packages/ci/dist` gate rather than by review, which is the gate working. Built with the repository-pinned Bun 1.3.14; a newer bundler rewrites unrelated runtime helpers throughout both bundles and would fail the same gate in CI. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
Decisions governing this change
|
Two defects found in review of #177, both of which let a *valid* ADR record break or corrupt the generated block it is rendered into. `replaceGeneratedBlock` counted marker substrings anywhere in the file. `title` is `z.string().min(3).max(120)` — free text long enough to hold `<!-- END GENERATED: adr-inventory -->` — so a record carrying a marker in its title would have it rendered into a table row inside the block, and every later run would throw `found 1 and 2`. One corpus record could permanently disable the generator whose job is to render it, and the fix would have been to edit the record. Markers are now recognized only as standalone lines, tolerating indentation, trailing whitespace, and CRLF, so a marker in prose or in a table cell is content rather than a delimiter. `tableCell` escaped `\` and `|` but not CR/LF. A YAML block scalar satisfies the title schema with an embedded newline, and a raw newline splits one table row into two lines of markdown — which regeneration then reproduces byte-for-byte, so the no-diff gate stays green over a broken table, and a multiline title could place a marker on a line of its own. It now follows `@adrkit/core`'s queue formatter exactly (`packages/core/src/queue/format.ts` `escapeCell`): CRLF to LF, CR to space, LF to `<br>`, then `\`, `|`, and a backtick, with the backslash escaped first so it cannot combine with an introduced escape. The two halves compose: newline normalization means content can never start a line, and line-anchoring means an inline occurrence is never a marker. Observed failing before being relied on (ADR-0016): the five new cases fail against the pre-fix script — marker in a cell, marker in prose, multiline title, forged marker line, and backtick escaping — and pass after. A sixth asserts a genuinely indented, trailing-whitespace, CRLF marker is still recognized, so the new anchoring cannot silently become too strict. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
Resolve documentation and generated inventory overlap while preserving trusted gates, queue determinism, and release operations. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Mark Beacom <m@beacom.dev>
This was referenced Aug 27, 2026
Merged
mbeacom
added a commit
that referenced
this pull request
Aug 27, 2026
release: prepare v0.12.0 Cut the unreleased changelog into `## [0.12.0] - 2026-08-27` and move every lockstep version-bearing surface from 0.11.0 to 0.12.0. `@adrkit/spec-kit` stays at 0.1.3 and the agent plugin at 0.2.0: both are independently versioned under ADR-0007 and neither moves with the repository release. Two changelog corrections rather than a mechanical rename. The trusted-gates trio was authored under `### Added` in #179, but #177 later inserted a `### Fixed` heading above it and silently swallowed all three entries into the wrong section; they are restored to `### Added`, and `### Fixed` now follows `### Changed` as in every prior release section. The `docs/repository-trust-operations.md` entry said those controls "cannot be applied until this lands" — no longer true once #180 recorded the deployed state, so it now states what is actually deployed: `trusted-dco` and `gate-integrity` are among the ten required contexts, the pull-request- controlled `dco` context was removed from that set only after the trusted one reported green on real pull requests, and both gates were observed red then green on real pull requests rather than in a fixture (ADR-0016). `bun.lock`'s diff is exactly the four workspace `version` lines, edited directly rather than by regenerating the lockfile, so no transitive drift rides along in the release commit. Validation: 2790 tests pass; typecheck, lint, `adr lint` (35 records, 0 errors), actionlint, and every `check:*` gate green; schema, MANIFEST, and committed Action bundle no-diff gates clean. The committed `packages/ci/dist` bundles were confirmed byte-identical to a canonical Linux Bun 1.3.14 rebuild and were restored after `release:pack`'s non-frozen build rewrote them under local Bun 1.4.0. `release:pack --tag v0.12.0` produced five artifacts (four lockstep at 0.12.0 plus spec-kit at 0.1.3); installed-tarball smoke passed on Node 22.22.2 and 24.16.0; `release:publish --dry-run` exited 0 and skipped `@adrkit/spec-kit@0.1.3` as registry-identical, confirmed independently against the registry shasum; `npm audit` over the packed consumer tree found 0 vulnerabilities against an empty `KNOWN_CONSUMER_ADVISORY_ACCEPTANCES`; and both OCI targets built and smoked, with the MCP image serving both protocol eras read-only and networkless. Signed-off-by: Mark Beacom <m@beacom.dev> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #131. Closes #111.
MANIFEST.md's record inventory is a pure function ofdocs/adr/, and it was hand-maintained: in #129 it was found publishing six records' worth of contradictory governance state. It was corrected by hand, which fixed the symptom. This fixes the cause.What changed
Generated, then asserted unchanged.
scripts/emit-manifest.tsrenders the record table and the status counts fromadr graph --format jsonbetween stable markers, andclean-clone-buildsrunsbun run emit:manifest && git diff --exit-code MANIFEST.mdnext to the existing schema-emit and Action-bundle guards. A guard that merely reported the divergence would still leave a human to hand-edit the table, which is exactly how it drifted.No public CLI surface was added.
adr graph --format jsonalready emitsid,titleandstatus— the whole of the derived state — so this is a renderer plus a gate, following the established read-only-CLI/redirect/gate pattern (site:queue) and the reasoningscripts/check-doc-cli-versions.tsstates for living inscripts/. The CLI's write surface staysnewandmigrate, so the Spec Kit adapter's tested "hooks reach only non-writing commands" invariant is untouched (#132).Real titles, not a label map. Per #131's preference — self-maintaining, and a label map would itself need a completeness check.
Judgment prose stays hand-written. Only the marked block is generated. The repository tree diagram, the planning sources, and anything editorial are outside the markers, and the section says so.
adr queueno longer goes silent on a deadline without a tier (#111).item.tier-absentnow fires whenever the routing tier cannot be determined on a record that has entered review —reviewpresent, or top-levelreviewBypresent.The failure mode the gate had to avoid
regenerate && git diff --exit-codehas exactly one dangerous failure: a generator that writes nothing leaves the diff clean, so the gate reports green while checking nothing — the ADR-0016 shape this repository keeps finding. Every way of writing nothing is therefore an error rather than a silent no-op: absent markers, a single surviving marker, duplicated markers, inverted markers, and an empty node list all throw.The generator also re-sorts nodes with
compareCodeUnitsinstead of trusting the orderbuildAdrGraphsupplied. That order islocaleCompare's, so identical records would render in a different row order under a different ICU locale and the no-diff gate would fail with no record having changed.Observed failing before being relied on (ADR-0016)
Permanent negative cases live in
scripts/emit-manifest.test.tsandpackages/core/test/queue/kernel.test.ts. The two end-to-end observations against the committed tree:1. A stale committed inventory — exit 1, with the drift named:
2. The markers deleted — the no-op that would otherwise pass green:
3. The #111 case, against the pre-fix kernel:
#111 in detail: why this is a contract reading, not a preference
The spec's carve-out has two conditions and a justification that names them both —
specs/007-arb-queue/spec.md:The implementation tested only the first.
contracts/kernel.mdstates the finding's condition with no carve-out at all (review?.tierabsent), andresearch.mdthe same. So the silence rule was applied to a record it was never written to cover: ADR-0022 sat in the queue asblastRadius: cross-teamwithreviewBy: 2027-02-08,tier=None,findings=[], and was ratified without a routing tier. A record carrying an explicit review deadline has entered the review workflow by any reading, and the spec's very next sentence gives that exact combination SLA treatment.The three sources now say the same thing as the code. Severity stays
info, so no exit code changes; aproposedrecord with neither field remainsnot-queuedand silent, and there is a test asserting that carve-out survives.Deliberately deferred
#115's
affects/**remainder is not in this PR.packages/core/src/affects/**is pinned byte-identical by feature 010's FR-004 guard, which names any change there a violation and routes legitimate changes to separately-authorized later work. Unblocking it needs that authorization plus regenerating the freeze-hash pins inpackages/catalog-envelope/— a different change with a different review.graph/build.ts'slocaleComparesorts are not migrated either, and this is the interesting one. ADR-0033 clause 8 pins them explicitly:Migrating them is a change to an accepted decision, not a defect fix, so it is reported rather than made here — this repository governs itself, and quietly reordering a pinned output would be the exact move it exists to prevent. The
MANIFEST.mdgate does not depend on that order (the generator normalizes it), so nothing here is blocked on the answer. Decision needed: should clause 8's "historical locale ordering" be amended to code-unit ordering, given that the id grammar admits mixed-case ULIDs where the two comparators genuinely disagree? It is a one-line change plus a superseding or amending record; it is not mine to assume.What was done on that axis:
packages/core/test/ordering-contract.test.tsnow scanssrc/queueas well, on QueueReport v1's own byte-for-byte contract (SC-001), and states in its header exactly whygraph/is excluded and what a clean run therefore does and does not mean — the same honesty the file already carries aboutaffects/.One thing the existing gates caught
The first CI run failed on
git diff --exit-code packages/ci/dist: the committed queue Action bundle embeds the kernel, so the #111 change had to be rebuilt intopackages/ci/dist/queue-action.js. Rebuilt with the repository-pinned Bun 1.3.14 — a newer bundler rewrites unrelated runtime helpers throughout both bundles and would fail the same gate. Caught by a guard rather than by review, which is the point of having it.Validation
bun test— 2678 pass, 1 skip, 0 failbun run typecheck,bun run lint— cleanbun run adr lint— 34 records, 0 errors, 0 warningsbun run check:changelog,bun run check:deps— okbun run emit:manifestis idempotent: a second run reportsalready matches the corpusclean-clone-buildswith the new gate