Every mutation emits the whole file's truth, not just its section's
TASK-IMP-116: Every mutation emits the whole file's truth, not just its section's
Summary
backlog-mutate retallies the section header of the row it touches and leaves the file-top Totals: line alone, by an explicit contract three tests pin. That line is the first number any reader sees, and it has no maintainer: the regenerator that owns it cannot run. Widen the declared mutation from two lines to three, so every write emits the whole file's truth - the completion of TASK-IMP-092's own argument.
Problem
An external review found Totals: 336 draft, 4 ready_to_implement, 176 done at line 10 of a file whose improvement section alone read 67 draft, 9 ready_to_implement, 39 done. The index of truth had a lying header. Consumers reading the header report wrong queue counts.
TASK-IMP-092 replaced incremental +1/-1 header adjustment with a full retally, and its rationale is decisive: "incremental adjustment faithfully preserves an inherited lie forever (the 086 incident's 34 vs true 20), while the retally makes every mutation emit the section's truth." It then stopped at section headers. The same lie was free to grow one line higher, and it did.
The line has no owner. regen_backlog writes it and cannot run - it reads docs/improvement/memory/backlog.yaml, retired in the 2026-07-08 migration. So the only mechanism that maintains Totals: is a script that raises FileNotFoundError.
Proposed Solution
retallyTotals(lines): find ^Totals: , count every parseable row in the file by status, rewrite the line in the file's own convention. Called by both flip and insert after the section retally, reported in the result as totals_line. Absent or unparseable Totals returns null and is not an error - a file without the line is legal.
The contract widens deliberately: a backlog-state-update@2 write now declares one status cell + its section header + the file Totals. That is the amendment this task exists to make, and it is the reason this is a task rather than an edit: three tests pin the current boundary and the skill's contract states it.
Alternatives Considered
- Fix the regenerator instead (keep the footprint at two lines). Rejected on the operator's call: it leaves the line correct only when someone remembers to regenerate, which is precisely the "nobody ran it" failure that let the 086 rows sit wrong. A number maintained by a ritual is a number that rots between rituals.
- Leave it; it is correct on disk today. Rejected: it is correct by accident (one flip ran before the fix was reverted) and re-rots on the next mutation.
- Move the pins without amending the contract. Rejected explicitly: t06/t07/t11 encode the byte-discipline boundary of the audited write path. Editing that boundary by rewriting its tests is how a gate quietly stops gating.
Success Metrics
- Primary: after any flip or insert,
Totals:equals an independent count of the file's rows - suite-asserted, and true for every status. Baseline: the line is maintained by nothing and was wrong by 4 ready_to_implement and 4 done when the review found it. - Guardrail: the mutation stays minimal and auditable - exactly 3 lines change (row, section header, Totals) and never a fourth.
Scope
In scope: retallyTotals in backlog-mutate, its wiring into flip + insert, the backlog-state-update@2 contract amendment, the three pinned tests.
Out of scope / Non-Goals
- Repairing
regen_backlog(a real defect, its own task - this task makes the mutation self-sufficient, which is what removes the urgency). - Any change to row grammar, placement, or the uniqueness gate.
- Retallying anything else in the file (a
Totals:line is the only global counter; inventing more is not this task's job).
Dependencies
None. retallyHeader, parseRow, and STATUS_ORDER all exist.
AI Authorship Disclosure
- Tools used: Claude (Fable 5) running the CyberOS task-author skill inside Cowork.
- Scope: spec drafted from the 2026-07-17 Greptile P1 and the suite's refusal of a first, unamended fix attempt; implementation under ship-tasks supervision.
- Human review: the contract-extension decision is the operator's recorded 2026-07-17 answer; both HITL gates are recorded human verdicts.
1. Description (normative)
- 1.1
backlog-mutateMUST exposeretallyTotals(lines), rewriting the^Totals:line from a full count of EVERY parseable row in the file, in the file's own rendering convention (lifecycle order, zero-count statuses omitted). - 1.2
flipMUST call it after its section retally, and MUST report the rewritten line astotals_linein its result and message. - 1.3
insertMUST call it on the same terms - a stale global total is stale whichever mutation caused it. - 1.4 An absent or unparseable
Totals:line MUST return null and MUST NOT be an error: a backlog without the line is legal and MUST NOT be given one. - 1.5 Rows whose status token is outside the enum MUST NOT be counted, matching
retallyHeaderandregen_backlog- one counting rule, three callers. - 1.6 The declared mutation footprint MUST be exactly three lines (row, section header, Totals) and MUST NOT grow further;
backlog-state-update-author's contract MUST state the new footprint. - 1.7 A mutation whose Totals line is already correct MUST leave it byte-identical and MUST NOT report
totals_line- a no-op is not a write.
2. Acceptance criteria
- [ ] AC 1 (traces_to: #1.1, #1.2) - after a flip, Totals equals an independent count of the file's rows, and the result reports
totals_line- test:tools/install/tests/test_workflow_helpers.sh::t06_counts_maintained - [ ] AC 2 (traces_to: #1.3) - after an insert, the same holds - test:
tools/install/tests/test_workflow_helpers.sh::t06_counts_maintained - [ ] AC 3 (traces_to: #1.6) - the flip footprint is exactly 3 lines: row + section header + Totals, never a fourth - test:
tools/install/tests/test_workflow_helpers.sh::t07_json_and_determinism - [ ] AC 4 (traces_to: #1.4) - a backlog with no Totals line mutates cleanly and gains no Totals line - test:
tools/install/tests/test_workflow_helpers.sh::t11_footprint_holds_with_retally - [ ] AC 5 (traces_to: #1.7) - a mutation against an already-correct Totals leaves it byte-identical and reports no
totals_line- test:tools/install/tests/test_workflow_helpers.sh::t06_counts_maintained - [ ] AC 6 (traces_to: #1.5) - a row carrying an out-of-enum status is excluded from the Totals count - test:
tools/install/tests/test_workflow_helpers.sh::t06_counts_maintained - [ ] AC 7 (traces_to: #1.6) - the skill's contract states the three-line footprint - verify: recorded grep in the gate log (a prose contract; same rationale as TASK-IMP-090 AC 1).
3. Edge cases
- A file with TWO
Totals:lines (hand-edited): the FIRST is rewritten and the second left alone - the tool must not guess which one is canonical, and a second is a corpus defect for a human to see, not for a mutation to silently resolve. - A
Totals:line inside a fenced code block (a doc example):^Totals:at column 0 inside a fence would match. Accepted risk, named: BACKLOG.md is a generated index, not prose, and a fence at column 0 there is already a corpus defect. Revisit if a real file trips it. - A backlog whose rows are all out-of-enum: the tally is empty; per 1.4's shape the line is left alone rather than rendered as
Totals:with nothing after it. - Placeholder-only sections (
(none yet)): parse as no row, count nothing - the same semanticsretallyHeaderalready has. - Concurrency: mutations are serialised by the workflow, and
atomicWritealready governs the file. Widening the write from 2 lines to 3 does not change that; the whole file is rewritten either way. - Security-class: counts rows already in the file and rewrites one line. No untrusted content is executed; no path is taken from input.
Audit
§1 - Verdict summary
Spec is 96 lines, 7 §1 clauses, 7 ACs, 6 edge cases. It amends a contract three tests pin, which is the reason it exists as a task rather than an edit. Every claim is verified against the live tree: the P1's numbers, TASK-IMP-092's rationale at backlog-mutate.mjs:146, the three pins, and the regenerator's FileNotFoundError. Passes after 6 findings.
§2 - Findings (all resolved)
ISS-001 - the task could read as overruling a gate rather than amending a contract
Rewriting t06/t07/t11 to let a fix through is how a gate quietly stops gating, and the first attempt at this fix did exactly that before the suite refused it. Resolved: Alternatives records "move the pins without amending the contract" as explicitly rejected; §1 #1.6 makes the new footprint normative and requires the skill's contract to state it; AC 7 verifies the prose moved with the code.
ISS-002 - widening a mutation's footprint invites further widening
Two lines became three; nothing stopped it becoming five. Resolved: §1 #1.6 caps it at exactly three and says "MUST NOT grow further"; AC 3 asserts the footprint, so a fourth line reds.
ISS-003 - a no-op write would churn the file
Rewriting an already-correct Totals on every mutation adds a diff line to every commit that changes nothing - the churn TASK-IMP-082 spent a task removing from the status page. Resolved: §1 #1.7 requires byte-identical + no totals_line report when already correct; AC 5 pins it.
ISS-004 - three counting rules could drift apart
retallyHeader, regen_backlog, and this would each decide what counts as a row. Resolved: §1 #1.5 binds all three to one rule (out-of-enum statuses excluded), and AC 6 tests it - the same single-source discipline TASK-IMP-104's t05 enforces on ver_lt.
ISS-005 - "fix the regenerator instead" was the cheaper option and is not obviously wrong
It keeps the footprint minimal. Resolved: recorded in Alternatives with the operator's reasoning - a number maintained by a ritual rots between rituals, and "nobody ran it" is exactly how the 086 rows sat wrong. Repairing regen_backlog stays a real task; this one removes its urgency rather than its need, and Non-Goals says so.
ISS-006 - a Totals line inside a fenced block would match ^Totals:
A doc example at column 0 would be rewritten. Resolved: §3 names the risk rather than hiding it - BACKLOG.md is a generated index and a column-0 fence there is already a corpus defect. Named, bounded, revisitable. An accepted risk stated is not the same as a risk missed.
§3 - Resolution
All 6 concerns addressed. The machine floor (task-lint) ran FIRST and was clean before any judgment family was applied, per TASK-IMP-084. Score = 10/10.
End of TASK-IMP-116 audit.