Skip to content

release: pm 0.51.0 — tests and records you can trust - #240

Merged
cfdude merged 159 commits into
mainfrom
dev
Oct 7, 2026
Merged

cfdude merged 159 commits into
mainfrom
dev

Conversation

@cfdude

@cfdude cfdude commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Cuts 0.51.0: 30 delivered epics (Theme A test infrastructure, Theme B gate/record integrity, execution profile, release-candidate review); store-owns-claude-md-managed-block deferred. Version bump, CHANGELOG, README/SKILL/CONTRIBUTING currency. Full suite 3170/3170 green; functional certify 1330/1330. Docs site PR (held until this merges): cfdude/pm-docs#58

https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM

…ics, Themes A+B); certification cluster consolidated; leaked test epics removed; merges and re-lanes

Removed alpha and from-caller (a test wrote them into the real record on 2026-09-21). suite-twin-and-certification-discipline declined (Rob). #226/#230/#229/#227 superseded into certification-record-redesign; #190 into #198; #223 into #213; parity-ledger-exemptions into hermes-platform-support. New: test-isolation-guard, emitted-invocations-copy-flake.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
Content-keyed, append-only certification record (#226); certify over an index snapshot via clone --shared (#230); functional trigger derived from what functional tests use (#229); Twin-Unchanged trailer checked by a commit-msg hook (#227). Baseline over the 0.50.0 build's 126 commits recorded in the change dir.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…, 5 rounds)

Whole-subject manifest key (mode+blob) replaces staged-path freshness; staged deletions demand their bucket; Twin-Unchanged parsed by git interpret-trailers --parse --no-divider; import observer with per-child tokens and a code-only NODE_OPTIONS guard over a shared regex-aware lexer (js-lexer.mjs); one indexManifest(root, bucket, {indexFile}) shared by drift, certify and the fixture seed; old record coexists (retirement deferred); sweeps subject widened to sweeps/*.mjs + js-lexer.mjs.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…easured on the day

0.1 ticked: Gate 1 passed, recorded against 48afa41.
0.2 ticked, not applicable at L1: release 0.51.0 holds one spec file (cross-spec-0.2.txt);
re-run at 5.7 when a second lands.
0.3 baseline-before.md: measure.mjs matches every expected value (15 / 70 / 70 / 51 of 126,
closure 132); certify sweeps 25-31 s, functional 190/412/345 s (1,227/1,227) with load recorded;
bare export 0.10 s, shared-clone build 0.17 s; the bare export fails the same four files.
0.4 the four tracker items carry no comment newer than 2026-09-28.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…e index copy (1.1)

certification-record-redesign 1.1 (design D2). indexRunPlan({ indexFile, commonDir, headSha, tmp })
returns the ordered steps: copy the live index once; ls-files -s under GIT_INDEX_FILE = the copy;
clone --shared --no-checkout; update-ref --no-deref HEAD; install the copy as the clone's index;
checkout-index -a -f. unit/certify-index pins that the live index is read exactly once, both the
manifest and the export read the copy, and no step names a worktree, the stash, GIT_DIR or
GIT_WORK_TREE. RED/GREEN and the by-hand functional certify (1227/1227): red-1.1.txt.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…ared clone (1.2)

certification-record-redesign 1.2 (design D2, #230). certify.mjs executes indexRunPlan() in a
pm-certify-run.* directory under the OS temp dir: one copy of the index, a clone --shared whose
index is that copy, checkout-index -a -f, and the bucket run there. The old single-file entries are
now hashed from the copy's blobs, so a partial stage certifies its staged half and an edit or
git add made mid-run is not recorded. The bucket runs in its own process group, bounded at 60
minutes (#220); INT/TERM/HUP kill it and exit, and the run directory is removed on every exit
(removeAtExit). Git's hook variables are scrubbed from every git call and from the bucket
(cleanEnv). certify.mjs stays import-safe: nothing runs outside main(). triggerEntry() takes a
readdir so the sweeps entry reads the copy too.

functional/certify-index (new id; twin unit/certify-index): partial stage, mid-run edit, and
pass/fail/SIGTERM leave the working tree, index, stash and worktree list byte-identical.
RED/GREEN and the by-hand functional certify (1230/1230): red-1.2.txt.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…shared clone (1.3)

certification-record-redesign 1.3 (design D2). A regression guard in functional/certify-index
builds the run directory with certify.mjs's own prepareRun() (now exported) over this repository's
index and runs the four files 0.3(d) found failing in a bare checkout-index export (conductor-13
"16.3", conductor-15, conductor-37, gate-artifact-evidence); they pass. Twin unit/certify-index
pins the plan's half: the export lands inside the clone, never in a --prefix directory.
Mutation (scratch copy, prepareRun building a bare --prefix export) turns the guard red naming
"fatal: not a git repository"; reverted, green. By-hand functional certify 1231/1231:
mutation-1.3.txt.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…ure functions (2.1)

certification.mjs gains the decision the manifest record is judged by (design D1, Gate 1 B1/B2):
demandedPaths()/bucketDemanded() over lazy subject(index)/subject(HEAD) thunks with the subject-root
skip, manifestOf() (subject ∩ index paths), manifestKey() over `path NUL mode SP blob NUL`, and
recordFreshness() — a keyed lookup of one entry, never a combination.

unit/drift-freshness.test.mjs (new): one named test per spec scenario. RED in red-2.1.txt (every
case failed on the missing exports); functional certify run by hand over the staged commit (B9).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
certification.mjs gains the record directory of design D1: writeManifestEntry() names an entry by
its manifest's sha256 under <common>/pm-suite-certification.d/<bucket>/ and creates it through a
unique temp name plus a rename, refusing a manifest with no test file of its bucket; readEntry()
reads one key (an absent directory or file is no entry); pruneRecord() keeps the newest 50 by
ranAt and never the entry just written, and removes *.tmp older than 1 h and pm-certify-run.*
older than 24 h (Gate 1 M6). The single-file readRecord/writeEntry stay until 2.4.

assert/drift-script.test.mjs: five FILE-rung cases, including two writers interleaved through an
injected io. RED in red-2.2.txt; functional certify run by hand over the staged commit (B9).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
… the formats coexist (2.3)

certification.mjs gains freshnessRefusal(): the demand, then (only when demanded) the index
manifest and the ONE entry its key names through readEntry(); and runForBucket(). A common dir
holding only the single-file record yields no entry, the writer leaves that file byte-identical,
and a clone with no record directory demands a run only when a subject path is staged (D5, B5).

RED in red-2.3.txt: the two freshnessRefusal() cases; the byte-identical case is a guard,
verified by a scratch-copy mutation (the writer removing the legacy file). Functional certify
run by hand over the staged commit (B9).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…y over the index (2.4)

drift.mjs judges check 4 per bucket with freshnessRefusal(): a staged path in subject(index), or a
deleted one in subject(HEAD) (ls-tree -r -z HEAD / show HEAD:<path>; PERMITTED_SUBCOMMANDS gains
ls-tree), demands the bucket, and it is fresh only when the ONE entry keyed by the index manifest
passed and holds exactly it. indexManifest(root, bucket, { indexFile }) is the one manifest entry
point (drift, certify with its index copy, seedAgreeingEntry()); gitRead() is the only place
GIT_INDEX_FILE is set, and indexReaders() threads indexFile to its listing and every show (X1).
The interim subjects are path classes (bucketSubject()); certify writes manifests, prunes, and
leaves pm-suite-certification.json alone (D5).

Retired: recordRefusals (dangling-entry/dangling-covers), coversFor, conformanceRows, moduleEntry,
triggerEntry, readRecord, writeEntry, RECORD_NAME, contentHash, stagedHash, drift's hashStaged,
stagedReader and crypto import, ENGINE_SOURCE, certifiedSet, KIND_MODULE, KIND_TRIGGER,
CONFORMANCE_ID, runFor, sweepIds. Stale wording moved in drift.mjs, certify.mjs and the hook's
comments. Tests moved to the manifest record: assert/drift-script, functional/drift-script (plus
the deletion case and X1), unit+functional certify-index (not listed by the task: L1 created it),
conductor-09 (G-I3 and 1.3 seed; IX-j asserts both buckets) and its twin.

RED red-2.4.txt (the deletion passes under f663cbd's drift; X1 mutation). Both buckets certified
over the staged commit; verify-2.4.txt and evidence-2.4.txt hold the Verify output.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…er refuses the other (2.5)

functional/drift-script reproduces #226 in two linked worktrees of one fixture, each running the
real certify runner over its own index, B's content holding a functional test only B has, in both
orders: A's drift accepts A's commit and B's stays fresh. The worktrees are removed and pruned in a
finally, and the test asserts only the main tree is left. assert/drift-script gains the FILE-rung
twin (both write orders into one common dir).

mutation-2.5.txt: restoring "resolve the covers of every entry" in a scratch copy refuses A in both
orders; reverted, both pass. Functional certify run over the staged commit (the gate demands it).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…lexer (3.1)

certification.mjs gains functionalSubject({ root, readFile, readdir, paths }): the import closure
(static from, export-from, bare side-effect and literal dynamic imports, relative only) of every
functional test file and of scripts/conductor.mjs; the functional files; every assertion-half file a
closure file under scripts/test/ spells in CODE as a quoted assert|unit/<name>.test.mjs (executed,
so a closure root); every tracked scripts/, .githooks/ or hooks/ file whose name a closure file
spells; the shipped roots a closure file spells, whole; README.md, CLAUDE.md and the parity ledger by
name. The record (openspec/, .conductor/, CHANGELOG.md, the rest of docs/) is reached by no rule.
Over this repository's index it yields 181 paths, measure.mjs's headSizes.widened. It is not yet the
bucket's subject: drift and certify switch to it in 3.3.

lex() and KEYWORDS_BEFORE_REGEX move from sweeps/output-interpolations.mjs to the new
scripts/test/js-lexer.mjs (the sweep imports it; its --all output is byte-identical). lex() gains
two additive results, comments and misparse; stripComments() throws on any misparse, naming the
file and line, so the executed-file match fails closed. Every fixture that copies certification.mjs
now copies js-lexer.mjs too: helpers.mjs (runHookAgainstFixture), IX-k's extraFiles, and the
certify-index and drift-script fixtures (the last two found by rg, not listed by the task). Twins
edited: unit/certify-index, unit/conductor-09, assert/drift-script. The functional twin case reads
every tracked scripts/ .mjs/.cjs/.js from the index with zero misparses.

RED red-3.1.txt (functionalSubject missing; the three lexer cases misread IN by a tokenizer with no
regex state). Also stages red-2.4-sweeps.txt (2.4's sweeps RED, captured after the fact). Both
buckets certified over the staged commit.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…3.2)

certify.mjs functional now runs the half under NODE_OPTIONS=--import of the RUN TREE's
scripts/test/fixtures/observe-reads.mjs (new; appended to the inherited value; configured by its own
URL so a stacked observer keeps its own run). The observer records module resolutions
(module.registerHooks), fs reads (readFile*, createReadStream, read-mode open, copy/cp sources) and
child_process script paths under the run tree, one file per process; it patches child_process
(+ syncBuiltinESMExports) so a direct Node child gets a token, arrives synchronously at load, and a
failed spawn cancels its expectation. After a pass, certify refuses any observed tracked path outside
functionalSubject() (the record excluded by rule: isRecordPath) and any unarrived child, naming them,
and records nothing. Before the bucket, nodeOptionsRefusals() (certification.mjs, shared lex(),
fails closed on a misparse) refuses a functional/fixture file whose code assigns NODE_OPTIONS a value
not carrying process.env.NODE_OPTIONS.

Tree edits before the report-only run: conformance.test.mjs:210 appends to the inherited
NODE_OPTIONS; conductor-14 spells its three rules-0.26.0 baselines as literals. Twins edited:
unit/conformance, unit/conductor-14, unit/certify-index, assert/drift-script. The certify-index and
drift-script fixtures copy observe-reads.mjs. observer-report-3.2.txt: 0 derivation gaps, 0 unarrived
children, 1236/1236; the out-of-subject reads are all the record (openspec/changes/archive, the live
.conductor/state.json, CHANGELOG.md, and 7 docs/superpowers/plans files the live-record tests reach).

RED red-3.2.txt (no nodeOptionsRefusals/observationRefusals; the unarmed certify records all five
refusal cases). Functional certify over the staged commit passed under the armed observer.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…rves (3.3)

bucketSubject("functional") is functionalSubject(): drift's freshness check, certify's recorded
manifest and seedAgreeingEntry() all reach it through indexManifest(), so they move together.
certifiedModules() (the gitOps( scan and its ENGINE_ENTRY special case) and the interim MACHINERY
list retire; over this index the subject is 185 paths and holds archive-gate.mjs (#229).

Moved with it: assert/drift-script's subject test (now the observed shapes: an imported module with
no gateway call demands both buckets, an unimported gateway caller only sweeps) and its 6.1 test
(the derivation over this repository); IX-j asserts a sweeps demand naming zz-probe.mjs and NO
functional demand (twin unit/conductor-09 edited); the certify-index and drift-script fixtures'
functional test IMPORTS their module instead of naming it in a comment (twins edited). Stale
comments rewritten: drift.mjs indexReaders(), fixtures/helpers.mjs's "empty certified set".
X1's two-index case and the seeded G-I3 and 1.3 re-run green unedited.

verify-3.3.txt holds the Verify lines with drifted line numbers corrected, and the certify over the
staged commit.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
… is retired (3.4)

scripts/lib/store.mjs's diskStore() comment existed only because the retired certifiedModules()
scan counted comments, so a comment spelling the gateway call enrolled the module. It now says the
scan is retired and the functional subject is what the half observes (functionalSubject()), which no
comment moves. `rg -n "certifiedModules" scripts` returns nothing.

Also appends to verify-3.3.txt the post-commit measure.mjs over 762d564 (HEAD~1..HEAD): the 3.3
commit is in the observed subject (B_widenedShippedSurface 1) and the retired scan would not have
demanded it (A_certified_today 0).

store.mjs is in both subjects; both certified over the staged commit: sweeps 25/25 (57s), functional
1245/1245 under the observer (586s, 185 subject paths, 80 record reads excluded by rule).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
… the real index (3.5)

A functional/certify-index case derives the functional subject over this repository's own index
(indexReaders(): git ls-files and git show :<path>, never an injected reader), takes b4ffe16's two
changed paths (scripts/lib/archive-gate.mjs, scripts/lib/store.mjs) as staged against an empty
record, and requires a functional freshness demand naming archive-gate.mjs — the module the half
imports that never reaches git (#229). It reads the index, not `git ls-tree b4ffe16`, which only the
presquash/pr-234 tag reaches. Twin unit/certify-index edited.

mutation-3.5.txt: restoring the retired gitOps( derivation in a scratch shared clone turns it red
("b4ffe164's change demanded nothing") — neither module calls gitOps(, so #229 exactly — and green
again when reverted. Functional certified over the staged commit (1246/1246, 619s).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…nged trailers (4.1)

certification.mjs gains parseTwinExemptions(parsedTrailers), over the OUTPUT of
`git interpret-trailers --parse --no-divider` (design D4, Gate 1 B3): git decides what is a trailer,
this reads only the Twin-Unchanged lines, the key matched case-insensitively as Gate 2's
%(trailers:key=) matches it. The id is the value's first token and only a spaced separator
(' — ', ' -- ', ' - ') begins the reason (round 2 I1), so `conductor-09` and a reason holding ' - '
are read whole; no separator is an empty reason, kept so 4.2 can refuse it.

New unit/drift-coupling.test.mjs (unit rung, pure values). RED red-4.1.txt: 8/8 failing on
"certification.mjs exports no parseTwinExemptions()".

Also folds the L3 bookkeeping into tasks.md: 3.2's expected out-of-subject record reads now name the
seven docs/superpowers/plans/*.md files the live-record tests reach through plansDir() — record by
the spec's docs/ rule (observer-report-3.2.txt), excluded, not a gap.

certification.mjs is in the functional subject: certified over the staged commit, 1246/1246 (second
run; the first hit the known emitted-invocations ENOENT flake, logged).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…cks() (4.2)

couplingRefusals({ …, exemptions, isMerge }) (design D4): a declared id with a reason, whose
functional file is staged, passes without its twin; a declaration naming an id whose functional file
is NOT staged is refused as { id, declaration: "not-staged", functional }; an empty reason is refused
as { id, declaration: "no-reason" } and exempts nothing; an undeclared unpaired file is refused in
exactly today's shape; with isMerge (MERGE_HEAD present) nothing is judged.

phaseChecks(phase) with PHASES: pre-commit runs enrolment, twins and record; commit-msg runs coupling;
no phase runs all four (Gate 1 M5), and coupling with no message uses no exemptions. An unknown phase
is refused naming the two. drift.mjs does not use either yet (4.3 wires them).

RED red-4.2.txt: 5 of 6 new unit/drift-coupling cases failing (the undeclared case is today's
behaviour and passed as expected). Certified functional over the staged commit, 1246/1246.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…ared exemption (4.3, L4)

.githooks/commit-msg (new, 100755) captures GIT_INDEX_FILE and makes $1 absolute before the scrub,
exports its own index snapshot, and runs the snapshot's drift.mjs (the working tree's only when the
index holds none) with --phase commit-msg --message <abs>. .githooks/pre-commit passes
--phase pre-commit and no longer claims coupling. So check 3 runs in exactly one hook (design D4).

drift.mjs: --phase/--message; checkAll(root, { phase, message }) runs phaseChecks(phase) only.
Coupling reads git's own parse of the message (interpret-trailers --parse --no-divider, added to
PERMITTED_SUBCOMMANDS, pin moved) and skips a merge (MERGE_HEAD through rev-parse
--path-format=absolute --git-path). An accepting run prints every declaration as
"drift: coupling exemption <id> — <reason>"; refusals name the id and the twin's path, or the
refused declaration. The ABORT closing line names only the phase's checks.

functional/conductor-09: 11 cases through BOTH real hooks with real git commits (hookedRepo(),
hookedGit() in fixtures/helpers.mjs, stdout+stderr one ordered stream), each seeded with
seedAgreeingEntry() over the index its commit is made from; every refusal asserts pre-commit's
success line THEN commit-msg's coupling line. Covered: plain, -a, <path>, linked worktree;
declared/not-staged/no-reason; --no-ff merge accepted (main and linked worktree), squash refused;
subject-only, prose-paragraph, after-scissors refused, above-scissors accepted; the --- divider both
ways, agreeing with %(trailers) in a hookless repo; the snapshot-vs-working-tree drift fallback.
Twin assert/conductor-09: the commit-msg hook's shape pinned, pre-commit's drift line and wording
moved; unit/conductor-09 omission note 8.

RED red-4.3.txt: 11/11 failing on the missing hook, and with a stub commit-msg the old pre-commit
refuses coupling itself so its success line never prints. Certified functional over the staged
commit, 1257/1257 (186 subject paths; .githooks/commit-msg is in the subject).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
functional/conductor-09 gains the regression guard: with both hooks installed and an agreeing entry
seeded, a staged functional file without its twin is refused, pre-commit's success line printed
first, and the coupling line reported exactly once, by commit-msg. Twin assert/conductor-09: the
PHASES table puts coupling in commit-msg only, the pre-commit phase runs the other three, no check
runs in both, and the two phases together equal a bare run.

mutation-4.4.txt (scratch copy of scripts/ and .githooks/): coupling re-enabled in the pre-commit
phase fails the guard on "pre-commit's success line was not printed" with the coupling line printed
once (git runs no commit-msg after pre-commit fails); coupling disabled in commit-msg fails it on
"the commit must be refused" (the commit was made); reverted, green. It also records the L4 commit
0b2ad82's own output: pre-commit phase ok, pre-commit 1581/1581, commit-msg phase ok.

Certified functional over the staged commit, 1258/1258.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…d (5.1)

call-site-sweep-5.1.txt, every list rg-derived: no retired name keeps a caller; freshness, the
subject derivation and the Twin-Unchanged exemption each hold in one function, with the places they
do not hold (commit-msg: no freshness; pre-commit: no exemption; CI: no record) justified. Two
findings routed: CONTRIBUTING's stale four-checks wording (6.1), and .githooks/commit-msg not
chaining `git secrets --commit_msg_hook` (5.9 b).

tasks.md: line citations L4 moved get their post-L4 value beside the c96240a one
(pre-commit :146-159/:150-154/:155, drift.mjs :231).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…ailer (5.2)

data-references-5.2.txt: the retired single-file record's id-holding fields (entry ids, covers,
conformanceRows) — written by writeEntry(), read by recordRefusals(), removed by nothing (#226's
second failure mode); the manifest record, whose fields are content (paths, modes, blob ids) and
informational provenance, removed only by pruning; and the Twin-Unchanged trailer, which lives in
the commit object and is stored by nothing pm owns.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…5.3)

inverses-5.3.txt: entry write/prune, temp-file rename/prune, the run directory's build and its removal
on exit, signal and (after SIGKILL) a 24 h prune, the observer's files and tokens, the declared
exemption (amend before, Gate 2's audit after), the hook install, the phase split, fixture seeding —
each shipped or existing; the derived subject has no inverse by design (D3); the old record file's
removal is deferred to the epic 8.2 registers (D5).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…e tree (5.4)

commit-verification-5.4.txt: for 1.1-4.4 and 5.1-5.3, `git show --stat` and each claimed file (code,
tests, evidence file, tasks.md, each functional test's twin or its Twin-Unchanged trailer) matched
against the commit's own name list. 20 of 20 pass. 3.3's claimed certify.mjs is absent from 762d564
and justified: 2.4 had moved certify onto drift's indexManifest(), so 3.3's bucketSubject() edit in
certification.mjs moved certify with it. .githooks/commit-msg is 100755 in 0b2ad82 and at HEAD.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…8.3 (5.5)

`rg -n "pm:lifecycle"` over tasks.md hits the 8.2 and 8.3 task lines and 5.5's own continuation line,
which names the marker inside a code span; the engine's countCheckboxes() reads checkbox lines only
and strips code spans first, so exactly those two tasks are excluded as lifecycle.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…ot run (5.8)

disposition-5.8.txt maps design.md's five non-goals, its open question and its one deferral onto
8.2: four declined deferrals, one deferral to the planned retire-single-file-certification-record
epic, and two non-goals (CI's buckets, the engine) justified as not deferrals. The add-epic and
archive commands are written out with flags checked against `--help`; 8.2 runs them after Gate 2.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…me the mechanism (5.9)

PROCESS: an-uncertified-module-change-skips-the-functional-half moves from "habit — no mechanism" to
drift check 4 over functionalSubject() (3.3), certify's observer (3.2) and the 3.5 guard, with its
trigger and rule rewritten for the observed subject and certify-over-the-index; a-one-off-sweep's
enforced_in, record path and quoted refusal now describe the manifest directory and today's
describeRefusal() text; the README index rows follow.

routing-5.9.txt names each item's kind. The certify-then-plain-commit PRACTICE is not registered:
the gate enforces it from L4. The openspec "no new change verb" premise is wrong: 1.13.2 has
`openspec new change <name>`, and it makes the same .openspec.yaml, so there is nothing to file. The
ENOENT flake's three occurrences went to its epic's notes. commit-msg's missing git-secrets chain is
drafted as feedback, not filed. One process lesson is drafted for the orchestrator; the other gets
no lesson, because its trigger is only recognisable after the fact.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
… index, parallel worktrees (6.1)

"Pre-commit hook" now says the pre-commit run checks enrolment, twin coverage and freshness (against
a whole-subject manifest), and the commit-msg run checks diff coupling. It covers the
`Twin-Unchanged: <id> — <reason>` trailer (the committer declares it, git parses it, Gate 2 audits
it) and the order: stage exactly, certify, then a plain `git commit`. The triggered-bucket paragraph
gains the two derived subjects and how certify runs (index copy, shared clone, one manifest entry,
the observer). A new "Parallel worktrees" section: no lock, pm-suite.lock kept for load only, a
hand-rolled pm-certify.lock is obsolete.

CONTRIBUTING has no "The triggered buckets" heading, which task 6.1 names. Its content went into
the paragraph that already covers the triggered buckets.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…rved subject (6.2)

The pre-commit hook runs drift's pre-commit phase (enrolment, twin coverage, record freshness) and
then the assertion half. The commit-msg hook runs diff coupling, and a `Twin-Unchanged: <id> —
<reason>` git trailer exempts a subject-free change there, which Gate 2 audits. A bare drift run
still runs all four checks. The functional demand fires for a staged path in what the half observes,
and certify runs over a copy of the index, so the order is: stage, certify, plain commit.

CLAUDE.md is in the functional subject, so the commit was certified first, over the staged commit:
`node scripts/test/certify.mjs functional`, exit 0, 574 s, load { 136.04 66.02 63.68 } ->
{ 182.71 202.51 145.96 }: 1258/1258, observer saw 2427 Node processes, 186 subject paths recorded
as 195d7599…b982.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…elease-0-51-0-cut

flaky-test-quarantine-and-targeted-rerun, hook-friction-0-51 and
lean-subagent-bootstrap archived delivered after one converged review round
(1 Critical + 3 Important fixed in 9b57a8e, a100466). Known flakes now name
owning epics; four follow-up epics registered. A Theme B epic is active so the
live-record tests do not read 0.51.0 as delivered (hook-friction item 5).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
… 1 digests, write-time refusals

One commit for six epics (gh#216 #237 #233 #232 #198 #205); the files overlap and each epic would
otherwise need its own functional + sweeps certify run.

- 237: a commit already attributed (or repeated in one call) is a no-op, said on stderr
- 216: staleness already read the set; fix the position-readers (stale-Gate-2 remedy wording,
  integrity's merge-commit date uses the latest-dated commit)
- 205: attributing past a passing Gate 2 says it went stale and names --withdraw-commit; the
  ordering rule is documented in the attribution task item
- 198: Gate 1 records artifactDigests (sha-256, change-relative via the one archive resolver);
  amending a reviewed artifact reads stale, absence reads unverifiable
- 233: --no-deferrals with --deferral/--declined-deferral is refused
- 232: --plan/--spec refuse a trailing slash or an existing non-file (update-epic, add-epic)

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
Twin-Unchanged: conductor-36 — the I3 fixture now seeds a legacy duplicate the verb no longer writes; the asserted behaviour is unchanged
…gest, add-many #232, #205 advisory, wording

- Gate 1: a live change's tasks.md is a progress ledger, listed but never digested/compared
- add-many: planPath/specPath use the shared #232 refusal
- update-epic: #205 advisory also fires from none-attributed/attribution-withdrawn
- spec/changeset/docs wording; stale test file pointer

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…arker, truthful archive and status reporting

One commit for nine epics because they share scripts/lib/integrity.mjs and its test registry:
gh-200 (--plan/--spec names an OpenSpec change), gh-201 (late-failing-gate-review),
gh-231 (tracker-item-held-by-two-epics), gh-215 (archived-change-also-live),
archived-delivered-fails-delivered-obligation, update-epic --status undone by the heal,
dispositionInvocation() deferral placeholder, set-aside archive dirs in integrity,
release --deliver/--undeliver marker read by delivered-release-epic-left-open.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
Twin-Unchanged: conductor-30 — only the closed list of valueless flags gained deliver/undeliver; the assertion twin does not assert that list
Twin-Unchanged: emitted-invocations — new Layer B builders only; the twin's registry check reads that file's source and passes, and the new checks are asserted per commit in assert/theme-b2.test.mjs
…ceholder, not a bare --no-deferrals

Covers deliveredArchiveInvocation() and the superseded-epic remedy; the source scan now covers every printer file. The markerless delivered-release finding states the mid-flight way out.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
Twin-Unchanged: emitted-invocations — fixture regexes updated to the printed placeholder only; what the test observes (remedies clear what printed them) is unchanged
…earliest-commit recovery, verb-table guard, planning-completeness rule

One commit for five epics: they share constants.mjs, subcommands.mjs, the command docs and the functional baselines.
- gh#167 sync --dry-run (writes nothing) and repeatable --only <id>; dry run skips the detached-tree warning
- gh#206 assertion-half guard naming every missing per-verb table row in one run; CONTRIBUTING documents the seed hook
- spec-sync-waive-for-skip-specs: update-epic --spec-deltas-waived "<why>", inverse --clear spec-deltas-waived
- created-at-recovery-dates-too-late: full-history pickaxe, earliest match by date
- openspec-planning-completeness-instruction: obligation paragraph in the emitted rules, no command named

Twin-Unchanged: conductor-13 — functional-only exercise-table row for the new update-epic flag; the twin's subject is unchanged
Twin-Unchanged: conductor-31 — functional-only baseline row and closed-list entry for sync's new flags; the twin's subject is unchanged
Twin-Unchanged: output-text-integrity — functional-only poison recipes for the two new value-bearing flags; the twin's subject is unchanged
Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
… spec-deltas waiver, review minors

- recover-created-at prints the AUTHOR date (%aI); the real knowledge-store cause was the rewritten
  committer date, not history simplification. Functional fixture rewrites only the committer date.
- --spec-deltas-waived is refused outside the check's scope (naming it); the briefing lists waived epics.
- sync --only: flip lines print only after the unmatched-id check passes.
- rules paragraph names the artifacts the OpenSpec schema requires; commands/sync.md passes --dry-run/--only.
- strengthen the inSpecSyncScope test (in-memory resolver) and replace the constant-only dry-run test
  with a detached-tree functional test.

Twin-Unchanged: detached-suppression — the new case needs a real detached checkout, which only the functional rung can make; the unit twin's roots are all unanswerable trees
Twin-Unchanged: conductor-13 — fixture setup only: the exercised epic is put in the waiver's scope; what the file tests is unchanged
Twin-Unchanged: output-text-integrity — fixture setup only: a new in-scope epic for the waiver recipe; what the file tests is unchanged
Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…Gate 2 re-recorded for conductor-tells-the-truth

B3 epics archived delivered (docs carried to release-0-51-0-cut); planning-completeness
remainder held by planning-completeness-orient-and-autonomy-boundary. conductor-tells-the-truth
Gate 2 extended to eb787d9 after an Opus review of bd2b1e6..eb787d9. store-owns-claude-md-managed-block
active so 0.51.0 stays in flight.

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…not Date.now()

The +400-day CI job moves Date.now() but not the filesystem, so the start read
'before'. Also rewords gh-172's description so TruffleHog stops flagging the
quoted test name as a verified Lob key (the false positive gh-172 tracks).

Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
…ce's commits; 5.9 probe accepts any non-zero

The certify run directory (clone --shared) copies only local branches and tags, so a detached CI checkout left the attributed commits unreachable and 9.14 failed inside 1.3. Fetch the source's refs into refs/certify-source/*. The 5.9 fixture asserted exit 1 for a missing command; dash exits 127.

Twin-Unchanged: conductor-09 — the change loosens a fixture precondition (exit 1 -> any non-zero); what the test asserts, runs and imports is unchanged
Claude-Session: https://claude.ai/code/session_01BoqzgrFwRC6QTUKuBkw8kM
@cfdude
cfdude merged commit af14c61 into main Oct 7, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant