v2.6.0 and v2.6.1: the reconciliation cycle and the defects the sweep found #97

Merged
john merged 98 commits from cadence/v2.6.0 into main 2026-08-09 19:53:36 +00:00
Owner

Two milestones on one integration branch, shipping as 2.6.1.

v2.6.0 — the reconciliation cycle. 73 commits across five phases: the capture queue triaged from 213 open items to 28, per-role token accounting with every phase dispatch bracketed, a runaway-loop bound on all 19 rung agents, named phase dirs and an ignored run record, and the first end-to-end doc sweep at 547 claims with a committed ledger.

v2.6.1 — the defects the sweep found. The four defects that sweep filed rather than reworded away (DFC-01..04), each closed at its source with a check watched to fail against the unpatched code first, plus the exact-budget flip so a surface that shrinks below its recorded byte count fails like one that grows.

The pre-ship review. A cross-model pass over the branch returned eight findings and all eight held up against the tree, including a blocking review gate that could not fire at all on the inline path and a technical-debt harvester reporting itself. Re-firing the same gate against the fixed tree caught two more defects in those fixes. All folded into the 2.6.1 notes.

Closes #87. Closes #72.

Gates on the tip: 1472 tests pass / 0 fail, self-verify problems:[] across 20 checks, tsc -p tsconfig.ci.json exit 0, /cad-audit 0 broken.

Two milestones on one integration branch, shipping as 2.6.1. **v2.6.0 — the reconciliation cycle.** 73 commits across five phases: the capture queue triaged from 213 open items to 28, per-role token accounting with every phase dispatch bracketed, a runaway-loop bound on all 19 rung agents, named phase dirs and an ignored run record, and the first end-to-end doc sweep at 547 claims with a committed ledger. **v2.6.1 — the defects the sweep found.** The four defects that sweep filed rather than reworded away (DFC-01..04), each closed at its source with a check watched to fail against the unpatched code first, plus the exact-budget flip so a surface that shrinks below its recorded byte count fails like one that grows. **The pre-ship review.** A cross-model pass over the branch returned eight findings and all eight held up against the tree, including a blocking review gate that could not fire at all on the inline path and a technical-debt harvester reporting itself. Re-firing the same gate against the fixed tree caught two more defects in those fixes. All folded into the 2.6.1 notes. Closes #87. Closes #72. Gates on the tip: 1472 tests pass / 0 fail, self-verify problems:[] across 20 checks, tsc -p tsconfig.ci.json exit 0, /cad-audit 0 broken.
john added 98 commits 2026-08-09 19:53:30 +00:00
The four phases carried over from v2.5.0 kept their old numbers, so the new
cycle opened at phase 3 with no phases 1 or 2. Renumber them 1-4 and move
phases/3/ to phases/1/.

Every reference moves with them: the ROADMAP list and detail sections, the
Depends-on lines, the phase files' own headings, frontmatter, paths and
cross-phase pointers, and the STATE cursor. References to v2.5.0's shipped
phases 1 and 2 are requalified 'v2.5.0 phase N' so a bare 'phase N' now means
one thing in the live docs.
/.planning/phases/*/triage-work/ was anchored to one fixed parent. A milestone
close moves phases/<N>/ to _archive-<label>/<N>/ and commits everything under
it, so the working copies of the planning queue would have stopped being
ignored at exactly the moment they got staged, publishing the candid text that
.gitignore:23 exists to withhold.

/.planning/**/triage-work/ matches the directory name at any depth under
.planning, which covers the archived path and the tracked tasks/ and spikes/
trees as well.
AC7 requires the list to live in SUMMARY.md so phases 2 and 3 consume it
without re-reading the queue. It had been left only in reports/plan-1.md,
which SUMMARY pointed at rather than carrying.
Also seeds REC-01/REC-02's traceability rows, which no writer had created:
the phase carried over from v2.5.0 already planned, so /cad-plan's seed-reqs
never ran for it in this cycle and the audit read 0/9 traced.
Issues #29 (review minimalism lens), #30 (executor YAGNI posture) and #31
(in-code shortcut marker to a debt ledger) were one idea at three points in
the lifecycle. Filed separately they read as three unaddressed proposals,
which is how the 2026 harness dossier scored them at improvement priority 4.

Folded into #95 and tracked here as MIN-01, deferred out of v2.6.0 for the
same reason CTX-02 was: parts 1 and 2 add resident prose, and this cycle's
live-friction surfaces already sit at exactly their byte budgets.
The 2026 harness dossier audited Cadence at v2.4.0 and ranked it #1 of 35,
but capped Caching, Context and Security on one identical reason: direct
runtime proof remains incomplete, operationally verified not run. Those are
the only three categories whose stated blocker is evidence rather than a
defect, and together they carry 33% of effective weight, so no amount of
code closes them.

Phase 4 makes the runtime record publishable (EVD-01, an export with a
stated redaction rule so the raw trace keeps its ignore line) and commits
one real phase's evidence with the command that regenerates its measurable
half (EVD-02). The doc sweep moves to phase 5 and now depends on it, since
it is what would document the artifact.
Cadence runs on ten projects, five of them tagged and shipped, but 135 open
capture items across the live ones mention Cadence in 5 lines total. Field
use generates almost no Cadence feedback, which is why five consecutive
cycles were scoped from Cadence auditing itself. Tracked as XCP-01 (#96),
deferred - v2.6.0 is full.

EVD-02 now takes its trace from a project that is not Cadence. Cadence
measuring Cadence is the weakest available evidence for the one thing the
dossier could not check, and the shipped projects are right there. Stated
prerequisite: none has a trace yet, because none has run a phase since
QW-02 shipped in v2.5.0.

Phase 4 also gains a criterion for a defect found while checking this:
execute.md:226 asserts .planning/trace.jsonl 'is gitignored' as the reason
a worktree trace cannot ride a merge back, but nothing in Cadence writes
that line. It holds here only because it was added by hand, so every other
Cadence project would commit its run record.
Audited the cycle against one question: is each phase here because something
is broken for someone, or because a self-audit found it?

Phase 3 was Parser defects, scoped from reading planning-files.mjs. Running
that parser over every plan file in five live projects - burnrate, hindsight,
assistant, jcrenshaw.dev, placer - produced zero frontmatter or undeclared
issues. Those defects are real in the code and have never given anyone a
wrong answer. PRS-01 and most of PRS-02 are deferred; PRS-02 keeps only the
REQ_ID_EXACT regression, the one with a recorded field occurrence.

The same survey found two things actually broken, neither on the roadmap:
tempest and atmos use named phase directories that no --phase seam can
address, and execute.md:226 asserts trace.jsonl is gitignored when nothing
in Cadence writes that line, so every other Cadence project commits its run
record. Those become FLD-01 and FLD-02, and phase 3 becomes Field friction.

FRI-03 narrows to the /cad-audit arm; QW-04 already ships a published-version
guard and a cad-health rule, so the self-verify arm was detector three for an
incident already caught. D-16 in phase 2's CONTEXT records the cut and keeps
the design for whoever revives it.

Runtime evidence, added earlier today from an external audit, is cut as a
phase and demoted to one task in the doc sweep; EVD-01's export machinery
defers with it. FLD-02 carries the part that is actually broken.

Four phases, not five. The rule applied throughout: a defect that is real but
has never hit anyone waits behind one that is hitting someone now.
Part 3 of #95 only - the in-code marker naming a shortcut's ceiling and
revisit trigger, plus a harvest into the deferred-work queue and a test
proving it finds a planted marker and invents none. Parts 1 and 2 stay
deferred under MIN-01: they add resident prose on the dispatch path, and
that objection does not reach a comment convention plus a seam.

Two measurements taken before writing it, both constraining:

The tree already carries 19 conventional markers (14 TODO, 2 NOTE, 1 each
XXX/HACK/FIXME) and nothing lints any of them, so a harvest keyed on TODO
returns 19 false positives on its first run. The convention needs its own
token and the phase criterion says so.

.planning/CAPTURE.md is gitignored here and in burnrate but tracked in
hindsight and assistant, so the queue's durability varies by project. The
marker in tracked code is the durable record and the queue is a regenerable
view of it, which is why the harvest is an idempotent seam with a CONTRACTS
row rather than a documented grep.
7 tasks, one plan. The multi-plan directive in CONTEXT was overridden by the
planner's own independence analysis and the checker agreed: weight-budgets.json
is written by all three requirements and planning.mjs by two, so a split would
create shared-file leases and cross-slice CI ordering.

Checker round found 6 issues, 2 blockers; all fixed in one revision. The plan
review then found 8 more. Triaged: 1, 2 and 3 applied, 4-8 recorded and left
alone.

Applied:
- The walk bar sent every (human-verify: needs <tool>) item into pass 1, where
  the absent tool failed the command and recorded blocked - a terminal status
  no later run recovers, so the phase could never reach Complete. Those items
  are now exempt from the bar by their CONTEXT-time suffix.
- version_drift compared the doc version against the plugin manifest, which
  exempted the exact state of issue #87. Replayed at tag v2.4.0: docs Active
  v2.4.0, tag v2.4.0, manifest 2.4.0 - the predicate would have been silent on
  the incident FRI-03 exists for. The comparand is now the git tags plus the
  cycle's own open/closed state, which separates #87 from an interrupted close
  where the manifest cannot.
- pluginVersion() resolves relative to the SCRIPT, so a downstream project
  would have been judged against Cadence's release number - confirmed by
  running criteria-coverage against hindsight, which reports plugin 2.5.0 for
  a tree with no manifest. D-03 forbade this; the manifest is no longer read.

Also carried: a sixth regression test replaying this repo's v2.4.0 state so
issue #87 has a test naming it, and the tag fixture moved off leaseRepo, which
cannot tag on a machine with commit.gpgsign set.
Nothing in Cadence measures dispatch cost. weight.mjs counts static prose
bytes (surface-weight.mjs:137 is Buffer.byteLength over files on disk), the
trace's provider family records no numeric field at all, and a repo-wide grep
for subagent_tokens/tool_uses/input_tokens/output_tokens across cadence-core,
skills and agents returns zero. The figures exist only in subagent return
metadata that nothing consumes, so every efficiency claim is a guess.

TOK-03 records tokens on a lifecycle return AND brackets the sites that have
none. The second half is the larger one, measured on this session: only
execute.md and verify-deep.md bracket anything today, while context.md,
plan.md and review-triggers.md have no lifecycle bracket at all. That is 71%
of subagent spend at unbracketed sites - 206,901 for the assumptions
analyzer, 346,882 for planner plus checker plus revision, 219,068 for two
reviewers, against 310,503 for the two that were bracketed. A token field
without the missing brackets would have measured the cheap 29% and reported
it as the whole.

TOK-04 records the read-set each dispatch is told to consume. PROJECT +
REQUIREMENTS + ROADMAP + CONTEXT is 85,413 B here and five dispatches were
each told to read it for phase 2's planning; whether that ~107K of identical
bytes is 15% of the phase or 40% is currently unanswerable, and the answer
decides whether trimming the planning docs is worth a requirement.

Its own phase rather than joining phase 3, which already holds four
requirements against a ceiling of 8. It also gates the next cycle's plan of
running v2.6.0 against hindsight, whose three-doc set is 27,596 B against
this repo's 85,413, so exploration cost is visible there rather than swamped.

Ids are TOK-03/TOK-04; TOK-01 and TOK-02 belong to the shipped tokenizer work.
The capped dispatch returned the agent's own partial text, not an error
envelope, and two Bash calls completed against maxTurns: 2 - so the cap
degrades a run rather than destroying it and counts tool-use rounds. The
sixteen frontmatter fields and the Agent tool's six parameters re-enumerate
unchanged, so D-12 stands and the bound belongs in the 19 agent files.
The blocking gate said "resume only after they are fixed (re-run fire)" with
nothing bounding the fire. The fix that clears a blocking FAIL is itself
reviewable work, so the trigger re-arms on its own fix with no terminal state.

Cap it once, in triage-gate.md's blocking arm - the consequence gate all five
fire sites (execute, task, debug, verify, git-guard) reach through
review-triggers.md step 6, so a per-workflow cap would bound one path and leave
four unbounded. ONE re-arm maximum, the second fire narrowed to the fix's own
diff plus the blocker list it confirms, and a surviving blocker becomes a
terminal ask naming its reason rather than another round. The round count is
orchestrator-context prose state, not persisted, and that limit is stated.

review-triggers.md is deliberately untouched: step 6 already delegates this arm
whole and two skills plus seams.md hardcode its 15,134 B figure.

execute.md's argument against a bounded re-dispatch loop cited the re-arm as
unbounded; that clause is now false, so it names the cap instead. The argument
it carries (the fallback is not a loop) is unchanged.
The maxturns-cap-behaviour spike closed `validated`: a capped dispatch returns
the agent's own partial text rather than an error envelope, and the cap counts
tool-using rounds (two Bash calls completed against `maxTurns: 2`, with the
return's own `tool_uses: 2` agreeing). A value is therefore safe to ship.

Execute its RECOMMENDATION verbatim: `maxTurns: 400` on all 19 files in
`agents/`, one uniform value for every role family. It clears the largest
observed Cadence run - cad-executor at 59 tool-use rounds for 3 of 7 tasks,
~140 extrapolated for a full plan - by roughly 3x, so it can only bind on a
genuine runaway, never on a long legitimate run. No per-family split, because
only the executor family has an observed turn count to size against.

It goes in the 19 files and not the spawn-agent seam: `maxTurns` is per-FILE
frontmatter and cannot vary by dispatch the way `model` can, so a value
resolved at the seam would be the resolved-then-dropped shape already
documented for per-trigger effort. Frontmatter only - no agent body carries
behaviour, and self-verify's rung checks stay green.

new-project.md's research step dispatches a generic host agent Cadence owns no
file for, so it is the one path no frontmatter bound reaches. Name it there
with its reason and the bound it does have, rather than let it be counted as
bounded by silence.
Issue #87: v2.4.0 was planned, branched and worked under a number this repo had
already tagged, and nothing mechanical said so. /cad-health's prose rule reports
it; the ship gate could not see it.

cmdAudit now emits `version_drift: {doc_version, published_as, cycle_state}`
when a TAG carries the planning docs' Active version AND the cycle is still
open - at least one phase whose artifacts do not read complete. cmdAudit still
computes no verdict; the FAIL is audit.md's arithmetic over the key (next task).

The predicate is deliberately not `docs != manifest`. pluginVersion() resolves
relative to the SCRIPT while the skill invokes the seam through
${CLAUDE_PLUGIN_ROOT}, so a manifest test would judge a downstream project
against Cadence's own release number. It also could not have caught #87: at tag
v2.4.0 the manifest read 2.4.0 too, byte-identical to an interrupted close. Only
the phase artifacts separate those two states, which is why cycle completeness
is half the predicate.

Both omission arms are states, not gaps: a doc version no tag carries is the
ordinary ahead-of-manifest mid-cycle state, and a tagged version with every
phase complete is a close interrupted between milestone.md steps 2 and 4.
Membership is preserved - a version merely sorting below the newest tag was
published by nothing.

No second readers. branch-decision.mjs exports the prose reader and the
membership test it already had, for drift DETECTION only (REL-03's ban on
deriving a release from prose stands), and readTags moves to lib/git-tags.mjs
so the branch seam and the audit ask git the same question once.

Six tests on a real tagged scratch repo, including the v2.4.0 state as a
regression pin naming #87.
The cap shipped in references/triage-gate.md, but nothing carried a reader to
it from a blocking fire: task.md, debug.md and git-guard.md pointed only at
review-triggers.md, whose step 6 delegated declaratively, and execute.md
restated the blocking arm inline with no cap while telling the reader the
risk_surface arm was untouched by the triage rule. Every RE-READ imperative in
the tree was scoped to an adjudicated arm, so the one trigger that is blocking
at every stakes level was the one the bound did not reach.

Step 6 is now a read imperative naming the cap, each of the four fire sites
carries its own RE-READ, and execute.md distinguishes exemption from the triage
rule (real) from exemption from the cap (not). review-triggers.md grew to
15,376 B, so the two skills that state that measurement are updated with it and
weight-budgets.json is regenerated.
The walk treated a why_human item as already judged and sent it straight to
the ask, but the verifier contract writes human_checks for every UNCERTAIN
truth as well as every human-only check, and a truth is UNCERTAIN whenever no
probe was observed. The union is strictly larger than "the model cannot execute
it", so the items FRI-01 exists to stop interrogating the user about were
skipping pass 1 on the strength of a field that does not mean what the bar
means.

The walk now reads the reason and applies the bar to it: pass 2 only when it
names irreversibility against real data or an out-of-reach resource, everything
else executed. The contract states the same predicate from its side, so the two
files cannot drift back apart.
The exemption tested "every phase derives complete", and uatComplete refuses a
blocked item while verify.md makes blocked terminal, so a phase parked there
could never satisfy it. Once a tag carried the docs version that repo FAILed
the ship gate forever, with audit.md offering "complete the close so no phase
is left open" as a remedy no walk could reach.

The predicate is now "nothing left that can be ANSWERED" - every item pass,
skipped-with-reason, or blocked - which is the close's own definition of
finished work minus the arm the walk cannot revisit. #87 is untouched: a cycle
being worked under a published number has pending or failed items, or no
checklist at all, and the new complement fixture pins that. audit.md states the
widened terms and puts the always-reachable exit first.
activeVersion() returned the first version token anywhere in the PROJECT.md
### Active body, so an Active section that names the closed predecessor before
the open cycle reported the predecessor. Since the predecessor is by definition
tagged, version_drift then hard-FAILs the ship gate on correct docs, and this
repo is one clause reorder from that state.

The comparand is now the milestone DECLARATION - the first line-anchored token,
past markdown furniture - with the old whole-body scan kept as a fallback so a
section that only mentions its version mid-sentence answers rather than going
silent. Branch naming reads the same function, which is the point of the single
reader: both surfaces move together.
`String(Number(x))` is not a round trip for a directory component:
`--phase 1.10` read `phases/1.1` and `--phase 08` answered about
`phases/8`, both with an ok:true envelope. One shared reader
(`requirePhaseArg`) now returns the caller's trimmed spelling beside the
number, and all three independent `--phase` shape rules use it - the
`requireCursorNumber` family, `uat`/`plan-overlap`'s bare `Number()` +
NaN test, and route.mjs's local `PHASE_RE`, which is deleted.

The split is one rule with two halves: paths and any diagnostic naming a
directory use the raw spelling, arithmetic and echoed phase fields use
the number. `trace append` carries the raw spelling as the event phase,
which is what stops `1.1` and `1.10` sharing one trace key and one
correlation id; `lib/trace.mjs` needs no edit because `key()` already
stringifies both sides of every comparison, so traces written before
this still join.

Two normalizations survive deliberately and are queued in CAPTURE.md:
`cursor set` still writes the number (`parseCursor` returns a Number
that renumber, cmdStatus and cursorPhase consume), and `seed-reqs`'
Traceability rows stay numeric because that cell is compared against
ROADMAP phase numbers.
`conventions.md` stated "no zero-padding, no slug suffix" and then, one line
later, told the reader to match an existing directory's name if one was
already present. That sentence is deleted and the grammar is stated whole: a
bare integer or an `N.M` sub-phase, resolved nowhere else, with no second
legal form and no migration.

`planning.mjs status` now returns one `phase-dir-grammar` drift entry per
colliding group, so a named, zero-padded or prefix-colliding directory is
NAMED instead of silently skipped. Entries sharing a leading numeric prefix
are reported together and the legal directory of that prefix is named as what
they collide with, which is the part a reader would otherwise have to notice
alone. One kind covers all three shapes: no writer can produce
`14-data-depth-x`, so a separate shadowing rule would report a hazard no code
path reaches. The two `phases/` listing filters are untouched (D-09), and a
legal tree still reports no drift at all.
`execute.md` asserted "`.planning/trace.jsonl` is gitignored" while nothing in
Cadence wrote that line - the premise held in this repo only because it was
added by hand, and five already-initialized projects would commit their run
record on the next `git add .planning`. `planning.mjs trace ignore --root .`
now writes it at `/cad-new-project` time (append-if-absent, creating
`.gitignore` when there is none, a no-op on re-run) and `--check` REPORTS for
/cad-health without editing a file it did not create.

`-v` rather than `-q` on `check-ignore`, and the reason is the whole point:
`check-ignore` also consults `core.excludesFile` and `.git/info/exclude`, and
neither is cloned, so a machine-local exclusion would answer ignored:true and
leave the project with no line of its own. Only a `.gitignore` inside the root
satisfies the check; a global or info/exclude match writes the line anyway. The
git arm is not decoration either - a project ignoring `.planning/` wholesale is
already correct, and a literal-only test would tell it to add a line it does
not need.

`--root` present with nothing usable after it is refused rather than answering
about the cwd. `detect-commands`' identical fall-through is deliberately left
alone: no requirement here covers changing a shipped seam's behaviour, and the
queue item stays open naming it.
`2FA-01`, `3DS-02` and `A11Y-01` are how a real project spells requirement
ids, and the head-anchored admission test held the first two out of `unpicked`,
out of `unseeded.active_ids` and out of `audit`'s counts entirely - an `##
Active` requirement no phase picked up, silently absolving the traceability
gate. A letter is now required SOMEWHERE in the category instead of at its
head, with the 2-8 character window preserved by a lookahead.

`14-01`, `08-02` and `2026-08` stay refused: `ACTIVE_BULLET` reads ANY bold
span as an id and narrowing it is off the table, so this is the only filter
between a bolded date and a phantom `orphans.plan_ids` break. `REQ_ID_TOKEN` is
left untouched per its own comment, which makes the two regexes deliberately
asymmetric - an unbolded `2FA-01` is still not a prose token - and both halves
are pinned so a later "consistency" edit has to break one on purpose.

Implemented over D-05's stated RULE rather than its literal regex, which
cannot match `A11Y-01` - an id the same decision names as admitted.
A corner cut on purpose gets a one-line marker at the line it was cut, naming
what it does not handle (ceiling) and what should prompt revisiting it
(trigger). Both fields are required; the harvest names an incomplete marker
rather than dropping it. The token is namespaced so it cannot collide with a
marker another tool introduces, and it is fixed rather than provisional: once
markers are planted across a tree, changing the token means editing every one.
Measured over this tree, SHORTCUT/DEBT/CORNER/TRIPWIRE/CADENCE-DEBT/CAD-DEBT
all return zero from `git grep -w`, while CUT returns 9 and CEILING 1.

METHOD.md and the verifier contract keep their TODO/FIXME/XXX/HACK enumeration
exactly as it was and name the token as exempt under the follow-up-marker
clause already there: its ceiling and trigger fields ARE the reference that
clause requires. Adding the token to the scan list instead would make every
planted marker a verification gap on the phase that plants it, and put two
Cadence surfaces in direct disagreement about one token.

The new section describes the fields in prose and deliberately writes no
literal marker line - the harvest scans tracked source, so a documented example
would be ingested as a real marker.

The `debt-harvest` CONTRACTS row lands here rather than with the subcommand:
self-verify's check 2 lints prose invocations against the table, so a row that
arrived a commit later would make this commit's own docs a lint failure.
The grammar and the rendering are a pure module (lib/debt-markers.mjs, no I/O
and no throw); the seam owns the walk, the reads and the write. A marker
missing ceiling or trigger is returned with the field null and `malformed`
naming it, never dropped - an incomplete marker is worse hidden than shown.

Enumeration is `git ls-files -z`, which omits untracked files and so keeps an
ignored node_modules out in the ordinary case. That is NOT "every ignored file
for free" and the code does not claim it: an ignore rule does not remove an
already-tracked path, so a force-added node_modules file is still enumerated
and there is an explicit segment skip beside the `.planning/` one. Both are
tested the way the claim actually breaks. A root that is not a git repo is
ok:false - an unenumerable tree must never report zero markers.

`## Debt markers` is the harvest's own section, rewritten wholesale and
deliberately absent from parseCaptureSnippets' walk list: idempotence is
impossible under append when /cad-capture and execute.md both append to
`## Todos` by hand, and a marker planted in code must not start steering
recall without anyone choosing it. The stated cost is that a harvested marker
reaches recall only when promoted by hand. `sectionBound` is exported and
reused rather than duplicated, and the write happens only when the rendered
text differs from disk, so a second run is byte-identical.
check-ignore answers "would this path be ignored if it were untracked", so a
path in the index matched nothing at all: ignored came back false with the rule
sitting in .gitignore, and the write arm - which keys off that value - appended
the comment and the line again on every run. --no-index asks the question the
seam actually has, and leaves TRACKED to traceTracked, which needs git rm
--cached rather than another line. cad-health named one command as the fix for
both flags; it now names the remedy each one takes.
sectionBound fixed the END boundary, so replaceSection still found the heading
itself with a bare findIndex, which carries no fence state. A fenced EXAMPLE of
## Debt markers in an earlier section became the rewrite's anchor; the scan then
resumed mid-fence, read the block's closing fence as an opener, found no
boundary, and replaced every later section with the new body - CAPTURE.md lost
## Seeds and ## Notes outright. sectionSpan returns both ends from one scanner
walk, so a start found fence-blind is no longer possible to pair with a
fence-aware end.
A single indexOf found the first marker on a line and then ran the field loop to
the end of it, so a second marker's ceiling and trigger overwrote the first's:
one entry came back reporting another cut's ceiling, which is worse than the
bullet it dropped. markerSegments bounds each marker at the next one's head.

The walk used statSync and readFileSync, both of which follow a link, so a
tracked src/link.js -> /tmp/outside.js filed the external file's marker under the
in-tree path. lstatSync classifies the link and skips it; a tracked link's target
is either in the tree, where it is enumerated on its own path, or outside it.
The advisory diff review over 53011b8..HEAD returned six findings, all six
confirmed against the tree. Five were errors this phase introduced:

- docs/EVIDENCE.md named /cad-trace, a command that does not exist anywhere in
  the tree. The trace is written by planning.mjs trace append and rendered by
  /cad-progress --trace. A fabricated self-claim in the artifact built to be the
  trustworthy one, in the phase whose goal is that claims match behaviour, on a
  surface no linter walks.
- CONTRIBUTING.md replaced one false dependency claim with another: npx fetches
  typescript, never @types/node, and tsconfig.ci.json sets types:[node], so the
  documented local recipe fails TS2688 on a clean machine.
- docs-verify-run-2.md tallied 529 accurate ledger rows against the ledger's
  actual 509, and against its own 547 total nine lines earlier.
- The ledger's line-provenance note gave one +3 offset for METHOD.md when three
  edits moved rows by 3, 4 or 6, and named none of the four workflow files that
  also moved.
- README-50 was resolved corrected against a commit whose frame is scoped to the
  preceding sentence and never reached that claim; it is a divergence.

The sixth is a real defect rather than stale prose, so DOC-03 sends it to a
filing: DFC-04, the wiring table's risk_surface row omitting shape (a) for the
one already-committed fire site.
METHOD.md:281 claimed phase_diff is the one trigger off at solo. route-table.json
also has diff:off at solo, and METHOD.md:292 says so nine lines later, so the
correction that fixed the gate row introduced a self-contradiction in the same
paragraph.

task.md's risk_surface artifact was corrected from shape (a) to shape (c) to
match the wiring table, but shape (c) needs a file and the replacement named no
path, no never-stage rule and no cleanup - where the sibling site at
execute.md:452 names its transient diff and forbids staging it. Takes the second
exit DFC-04 names: /cad-task now writes a named transient diff and carries the
same never-stage and delete rules. DFC-04 stays filed for the wiring-table
question itself.

Budget regenerated for task.md in the same commit.
All five phases are done, so their list lines and detail sections come out of
the live ROADMAP and their phases/<N>/ dirs move to _archive-v2.6.0/. Moved
rather than deleted: git.create_tag is false here, so there is no tag naming
this milestone and git history would otherwise be the only copy.
v2.6.0 closed with all fourteen requirements delivered, so its rows move to
REQUIREMENTS ## Shipped and ## Traceability starts clean for the next audit.
DFC-01..04 promote out of ## Deferred into ## Active: they are the defects
v2.6.0's own doc sweep found, and filing them was a refusal to convert a bug
into a documentation edit, so leaving them for a convenient cycle would undo
the point.

Two of the four sit in review-triggers.md, which is at exactly its byte budget
with zero slack and has that figure quoted inline by two skills, so either fix
is three coordinated edits. Written into PROJECT.md as something to plan for
rather than discover.
v2.6.1's only phase. One phase rather than four because every fix is located
and close to a one-liner, and because DFC-02 and DFC-04 both edit
review-triggers.md: splitting them would move that file's byte budget and the
two skills quoting its size inline twice instead of once.

Criterion 6 is the one that matters. Each fix ships with a check proved failing
against the unpatched code, so a defect this cycle exists to close cannot be
closable again next cycle.
Also corrects the DFC-04 requirement row in place. It described 044806c's state
of task.md, but 716fb60 superseded that during /cad-verify 5 by taking the
second exit DFC-04 itself named, so the workflow half of the filing is already
closed and only the wiring-table row remains. Left uncorrected, the row would
have told the planner to restore prose that is present.
The two literal U+0000 bytes inside the `worker` key's template literal at
lib/trace.mjs:336 become the two-character `\0` escape. Behaviour is
byte-identical - the separator is still U+0000 - but GNU `grep -rn` and `rg`
stop treating the whole file as binary and skipping it silently, which had
already cost one debugging detour. `git grep` never caught it: its binary
heuristic inspects only the head of the blob and the NULs sat at offset ~14.8k.

self-verify gains check 15 to keep it closed. `binFiles` takes `{ every: true }`
- extension-blind and exclusion-free - so the guard also covers `*.test.mjs`,
`lib/config-merge.mjs` and `weight-budgets.json`, which check 12's walk skips
and which go dark under `grep` exactly as loudly. Check 12's input is unchanged.
Scoped to `cadence-core/bin/**`, not the tree: `.planning/_archive-v2.5.0/1/PLAN-2.md`
carries the same two bytes inside an immutable phase record.

trace.test.mjs pins the separator itself: two brackets whose corr/phase/plan
parts concatenate alike must not pair, so deleting or emptying it fails loudly
instead of silently merging worker rows.
Check 4 compared `bytes > budget`, so a surface was free to SHRINK below its
entry with CI green. Nothing regenerates these entries - there is no
`--write-budgets`, they are hand-copied from weight.mjs - so "93 surfaces at
exactly their byte count, total slack 0" was a maintenance convention
docs/EVIDENCE.md published as though it were enforced, and the slack a shrink
banked was spent invisibly by the next growth.

Now `bytes !== budget` fails, as two kinds so triage reads the direction off the
kind: `budget-overrun` keeps its existing detail, `budget-undershoot` names the
shortfall and says to re-pin. The old `at or under` test is rewritten as exactly
at budget, and a one-byte-under falsifier pins the new arm. METHOD.md's
description follows the code.
`<dimensions>` says "Check six dimensions" and enumerates six ending in
Proportionality; `<success_criteria>` still asked for five. A checker could
therefore report VERIFICATION PASSED having skipped the one dimension that
bounds plan size, and be inside its own contract for doing it. Six is the
correct direction - correcting downward would delete the dimension v2.5.0
shipped a cycle early to install and contradict METHOD.md:91. `<returns>` says
"one line per dimension" with no number and is left alone.

The edit shrinks the file to 5,343 B, which task 1-2 just made a failure, so
its weight-budgets.json entry is re-pinned in the same commit - the worked
example that motivated the exact comparison.

New cadence-core/bin/prose-agreement.test.mjs holds the check: prose copying a
machine-readable fact must still match it. Its first test reads all three
numbers out of the contract - the declared count, the claimed count and the
enumerated items - and fails when any disagrees. Tests rather than a
self-verify check because the next subjects are Wiring-table rows, and
self-verify.mjs:927 records the standing decision not to parse that table.
DFC-02: the Wiring table stated `phase_diff` as `off / off / adjudicated` while
the resolver returns `off / advisory / adjudicated` - route-table.json's shipped
row and config.schema.json's own default. That one wrong cell was the single
source of four wrong claims across two documents; its copy in docs/WORKFLOW.md
is corrected with it. METHOD.md and workflows/execute.md were already corrected
in b2bad1a and 044806c and are untouched, and METHOD.md:282-283 stays true -
`diff` is still `off` at solo.

DFC-04: the `risk_surface` row admitted "(c) the flagged-diff FILE path THE
CHECKPOINT RETURNED", and /cad-task's fire produces exactly that file with no
checkpoint at all, since its commits already exist. Dropping the producer
qualifier is the smallest correct fix - section 2's own shape-(c) definition is
already broad enough. workflows/task.md is deliberately NOT reverted: 716fb60
restored the named transient path, the never-stage rule and the delete-on-return
cleanup, and the tests here pin all three.

Editing the reference moves its budget, which 20c59b6 now fails on in either
direction, and two skills quote that figure inline - so weight-budgets.json,
cad-land and cad-plan-review go from 17,733 to 17,714 in this same commit. The
value stays five digits, so both skills keep their own entries.

Two tests in prose-agreement.test.mjs. The first drives route.mjs resolve
through the per-repo --file layer once per stakes level and asserts both
documents against what the RESOLVER returns, not against what route-table.json
stores, so level mapping and schema defaults are inside the check. The second
parses the risk_surface row alone - one named row, never the table's shape,
since self-verify.mjs:927 records the decision not to parse this table.
Re-run against 98be3d2 and every differing figure updated, not only the named
ones. Two of them were already stale BEFORE this phase touched anything:
`cadence-core/workflows/` (200,050 -> 200,209), the grand total (475,412 ->
475,551) and `/cad-task`'s turn-one bytes (5,221 -> 5,380) were each 159 B
light, which is exactly 716fb60's task.md growth committed after f8f22cf last
re-measured them. That gap is not introduced here. The rest follow this cycle's
own edits: `review-triggers.md` 17,733 -> 17,714 with its est-token column
4,433 -> 4,429, the references subtotal, six reachable-column figures whose
one-hop set includes it, and the four cad-plan-checker dispatch rows, one byte
each from the contract's own fix.

The "total slack zero" sentence now states what 20c59b6 made true: any
DIFFERENCE fails, added bytes or removed.

Three tests in prose-agreement.test.mjs measure through weighAll - the same lib
self-verify enforces with and weight.mjs reports from, so the check cannot
diverge from the enforced number. They cover all four sites naming
review-triggers.md's size, EVERY row of the twelve-largest table in both
columns (checking one row of twelve leaves eleven and the whole est-token
column free to stale), that the table really is the twelve largest, and that
the five directory subtotals and the grand total are the measured sums. Est
tokens come from the seam's own measurement, never bytes/4 - `estTokens` counts
characters. This closes a drift class that had no check at all, while
references/seams.md:240-242 requires every future deferral to quote its
reference's measured bytes inline.
The `## Defects filed out of this sweep` block asserted three open filings that
have now shipped, and the `+ DFC-0k` suffix - documented as a row's only link
to its filing, so a future diff can tell a corrected copy from a fixed source -
could not answer the one question it is asked. Both are updated: each bullet
names what closed it and at which commit, and the five suffix rows carry
`DFC-0k closed <sha>`.

DFC-04 gets its link on TASK-01, the row whose claim the filing came out of.
No DFC-04 bullet is invented: run 1 never filed one in this block, and adding
a row would make the ledger assert a finding the sweep did not make. Without
the TASK-01 link, though, the ledger would record three of this phase's four
closures. No verdict cell is touched anywhere - those record what run 1 found.
The step told the model to write the flagged diff to
.planning/tasks/{slug}/risk-task-{slug}.diff, but {slug} and that directory are
only ever created by planned_path step 1, which itself declines to create them
when .planning/ is absent. On the inline path, or in a brownfield repo with no
.planning/, the redirect fails with No such file or directory. risk_surface is
blocking at every level, so that is a blocking gate that cannot fire at all.

Derive the slug here when the inline path did not, mkdir -p before the
redirect, and fall back to ${TMPDIR:-/tmp} when .planning/ does not exist.
Still shape (c), which since 2.6.1 admits a flagged-diff file however produced.

task.md 4,643 -> 5,531 B, so its budget entry and EVIDENCE's workflows subtotal
(200,209 -> 201,097), grand total (475,551 -> 476,439), /cad-task turn-one
(5,380 -> 6,268) and the 23-command total (279,223 -> 280,111) move with it.
markerSegments' JSDoc spelled the token followed by a colon, so debt-harvest
found exactly one marker in the whole tree and it was this file: markers:1 with
a malformed entry for ceiling and trigger, a corner-cut that does not exist,
landing in the queue a human triages.

Both the note above DEBT_TOKEN and conventions.md:57-60 already state that
documentation never writes a literal marker line. This file was the one place
breaking it. Harvest now returns markers:0 on a clean tree.
2.6.1 made check 4 exact, so a shrink is budget-undershoot. METHOD.md was
updated in that phase; this was the one remaining prose site still telling a
contributor that only growth fails. Deleting two sentences from a workflow and
reading this file would tell you nothing will break, then CI reports the
undershoot from the very job this paragraph describes.
context.md's measured block prints `cad-planner 146,405` three lines above the
--tokens order that copies it, so the grouped form is the transcription the
prose itself models. requireInt refused it, and by design nothing at all was
appended, so the dispatch half stayed open and the worker was stranded in
renderTrace's unpaired[] forever. That escalates a recording error into loss of
the bracket it was recording.

Grouping is stripped only in the strict 3-digit shape, so 1,2,3 and 146,40 are
still malformed calls and still refused. requireInt itself is untouched: --total
and --attempt do not model grouping. New test verified failing against the
unpatched reader (50 pass / 1 fail), passing after (51 / 0).
The byte checks added in 2.6.1 cover the twelve-largest table and the
per-directory subtotals, both measured by weighAll. The turn-one table (23
rows plus its total), eager-vs-reachable (10 rows), zero-resident (3 rows plus
its total) and dispatch (19 rows) are measured by residentWeight and were
pinned by nothing.

That is the same failure class EVIDENCE.md:171-177 claims to have closed, one
table over: add a sentence to any workflow, re-pin its budget entry, and
self-verify goes green while /cad-execute's turn-one and reachable figures are
both silently wrong. The file promises at :6 that you can check the tree out,
run the command and compare, so every measured figure it prints is now checked.

Each of the four verified failing against a one-digit mutation of its own
table and passing on restore.
It said "v2.5.0 is the current release" while the manifest reads 2.6.1, and
v2.6.0 appeared nowhere in the cycle list. The v2.6.0 close rewrote ### Active
and never touched this paragraph, and the doc sweep could not catch it:
DOCS-CLAIMS.md:15 scopes its 25-file surface to README/METHOD/INTERNALS/
CONTRIBUTING plus the 21 workflows, and PROJECT.md is not in it. Anyone reading
the repo's own north star was told the plugin they installed is two releases
older than it is.
The close ran the manifest bump and the phase prune and stopped there, which
left DFC-01..04 sitting in ## Traceability pointing at a Phase 1 the prune had
just removed from ROADMAP. audit reported 4/4 broken with phase-missing, a hard
FAIL by audit.md 4, and status reported the interrupted-close drift. The next
/cad-milestone would have halted at its own gate before it could tag.

The four rows move to ## Shipped at their phase, ## Traceability starts clean,
and the cursor resets to the between-milestones state. Both files now say
v2.6.1 shipped.

What this deliberately does NOT do is scope the next milestone. Opening one is
its own decision and it has not been taken, so ## Active is empty in both files
and says why, and every candidate stays in ## Deferred unpromoted. audit: 0
broken, 0 unpicked. status: no drift.
ROADMAP's Overview still declared v2.6.1 OPEN with scope DFC-01..04 while
PROJECT, REQUIREMENTS and STATE all said it shipped. 741751c moved three of the
four documents. A next session following STATE's own '/cad-phase add' would
have appended a phase under a milestone whose requirements are already rows
under ## Shipped, and seed-reqs would find nothing in ## Active to seed.

99be508 told the INLINE path to mkdir -p .planning/tasks/{slug}/, but the
cleanup rule deletes only the .diff, so every risk-surface inline task left an
empty slug directory behind - against this workflow's own 'Zero planning
artifacts for inline tasks' criterion. The planned path keeps the named path
because it already owns that directory; inline and no-.planning both go to
TMPDIR.

EVIDENCE.md's provenance line named commit 98be3d2, and 99be508 moved four
figures past it, so the file's own check-out-and-compare promise failed at the
commit it named. It now names no commit: the figures are asserted against the
live measurement by prose-agreement.test.mjs, so a stale one is a red test.

task.md 5,531 -> 6,104 B; budget, workflows subtotal, grand total, /cad-task
turn-one and the 23-command total re-pinned with it.
docs: fold the pre-ship review's eight fixes into the 2.6.1 notes
All checks were successful
test / node-test (22) (pull_request) Successful in 1m38s
test / node-test (24) (pull_request) Successful in 1m12s
test / self-verify (pull_request) Successful in 13s
test / typecheck (pull_request) Successful in 18s
276e6f93b1
The section was written at the close and said 'One phase, six commits'. The
pre_ship gate then returned eight findings, all of which held up, so the tag
carries 14 commits including two user-visible changes (the /cad-task risk fire
and --tokens grouping) that no released section described. The notes now
describe what the tag actually carries.
john merged commit 23cfd1a3c7 into main 2026-08-09 19:53:36 +00:00
Sign in to join this conversation.
No description provided.