Task — engineering-spec@1

Unify the route-back ceiling - api.py default 2 vs doctrine's 3

doneTASK-CUO-304
module cuo · class product · priority p1 · created 2026-07-23 · shipped null
depends on none · blocks none

TASK-CUO-304: Unify the route-back ceiling - api.py default 2 vs doctrine's 3

Summary

Doctrine (ship-tasks.md §11b) says a task halts for an operator verdict at routed_back_count >= 3 and explicitly says a task at 2 re-enters normally. The machine encodes 2: modules/cuo/cuo/api.py defaults halt_on_repeat_rework: int = 2 and halts when rbc >= 2, and cli.py's flag defaults to 2 with matching help text. A drain run with defaults therefore halts one cycle earlier than the workflow contract promises. This task changes both defaults to 3 and adds a test that pins the Python defaults to the number parsed out of the doctrine text, so the constant can never fork silently again.

Problem

Audit finding H3, verified first-hand: api.py:127 (= 2), api.py:289 (rbc >= halt_on_repeat_rework), cli.py:584 (default=2, help "default 2"). Against ship-tasks.md §11b: halt at >= 3; a task at 2 re-enters normally; the number three is a recorded judgment ("the same task failed three DIFFERENT ways is evidence about the spec"). Two consequences:

  1. Behavioral drift: a zero-touch drain (cyberos-cuo ... run) halts tasks at their second route-back - stricter than doctrine, so tasks that the workflow contract says should get one more attempt instead park for HITL early, and the halt brief cites a threshold the doctrine does not back.
  2. No pin: nothing fails when the two surfaces disagree. The fork happened once already (this finding); without a cross-check it will happen again the next time either side is edited alone.

Proposed Solution

Change the default to 3 in both places (api.py run() signature, cli.py option default + help text). Add modules/cuo/tests/test_doctrine_constants.py which (a) reads the ceiling from ship-tasks.md §11b by regex (routed_back_count >= N in the MUST-HALT bullet), (b) asserts inspect.signature(cuo.api.run).parameters["halt_on_repeat_rework"].default equals that N, (c) asserts the click option's default equals that N, and (d) asserts the help text quotes the same N. Parsing the doc rather than hardcoding 3 in the test means a future deliberate change edits exactly one normative home (the doctrine) and the test then names every stale machine surface. Add a CHANGELOG entry noting the default change (drains now allow the third cycle before halting).

Alternatives Considered

Success Metrics

Scope

In scope: the two default-value changes, the help-text correction, the doctrine-parsing pin test, a CHANGELOG entry.

Out of scope / Non-Goals

Dependencies

None blocking. TASK-IMP-108 (done) authored ship-tasks.md §11b, the doctrine side of this unification. TASK-IMP-140's G11 gate adopts test_doctrine_constants.py as its checker - forward reference only.

AI Authorship Disclosure

1. Description (normative)

2. Acceptance criteria

3. Edge cases

Audit

§1 — Verdict summary

Five §1 clauses, five ACs, six edge cases. A deliberately small task (single-constant unification + conformance pin) audited against the full rubric; the size is justified under the pure-infrastructure exception and the spec is complete, not truncated. The consequential findings were the third-copy trap in the pin test's design and the silent-skip failure mode the parser could have reproduced.

§2 — Findings (all resolved)

ISS-001 — the first-draft pin test hardcoded 3, creating a third copy of the constant

A literal assert default == 3 makes the test itself a fork surface: a deliberate doctrine change to 4 fails the test with a message that reads "test expects 3", inviting a test-side fix while api.py keeps drifting. Resolved: clause 1.3 requires deriving N by parsing ship-tasks.md §11b; AC 3 proves derivation by patching the doc text in-memory and asserting the failure names both sides.

ISS-002 — parser-miss behavior was undefined (silent-skip risk)

If §11b is reworded and the regex misses, a lazy implementation returns None and skips — the exact silently-skipping-conformance-test defect this same batch fixes in test_schema_drift.py (TASK-MEMORY-303). Resolved: clause 1.4 mandates loud failure on pattern miss; AC 4 asserts raise-not-skip.

ISS-003 — semantics of the comparison operator were not pinned alongside the constant

Changing the default 2→3 while someone "helpfully" changes >= to > would reintroduce the off-by-one under a green constant check. Resolved: clause 1.1 pins BOTH the default and the rbc >= halt_on_repeat_rework comparison, and AC 1 asserts behaviorally (rbc 2 re-enters, rbc 3 halts) rather than only reading the signature.

ISS-004 — 0-disables semantics could be broken by an over-eager pin

A naive "default must equal doctrine" test could also assert the flag's value range, forbidding the documented 0 disable. Resolved: edge case pins 0 as untouched (the if halt_on_repeat_rework and ... guard at api.py:289 treats it as disabled) and instructs the pin test not to forbid explicit values.

ISS-005 — the circuit-breaker half of the plan bullet was silently dropped in the first draft

The plan says "test pinning doctrine constants" (plural); the 5-fail breaker has no Python constant to pin (verified by grep), and the first draft simply didn't mention it — an unexplained scope narrowing. Resolved: Alternatives Considered records the deferral with the measurement, and the test module is named as the designated future home so the deferral is discoverable.

ISS-006 — in-flight tasks at rbc 2 needed an explicit migration statement

A reviewer would reasonably ask what happens to tasks that would have halted under the old default. Resolved: edge case states no stored state migrates — routed_back_count is frontmatter compared only at drain time, so such tasks simply re-enter (doctrine-conformant) and halt at 3.

§3 — TRACE-006 semantic sufficiency (per clause)

ClauseVerb demandCited test assertsVerdict
1.1 MUST default 3 + keep >= semanticssignature default AND behavioral halt boundaryAC 1: asserts default via inspect AND rbc-2-re-enters / rbc-3-halts behaviorsufficient after revision (ISS-003)
1.2 MUST default 3 + help text states it + keeps 0-disable docoption default + two help substringsAC 2: asserts all threesufficient
1.3 MUST parse doctrine and compare all three surfaces, naming both sides on mismatchderivation (not literal) + mismatch message contentAC 3: in-memory doc patch flips all assertions, message names 4 vs 3sufficient
1.4 MUST fail loud on parser missraise, never skipAC 4: asserts raise on missing patternsufficient
1.5 MUST gain CHANGELOG entrysubstrings present in top entryAC 5: asserts flag name + 2-to-3 mentionsufficient

§4 — Resolution

Six findings, all resolved in the audited revision. Size exception: pure-constant + conformance-test task, complete at small scale per the authoring discipline's infrastructure exception (all sections present and substantive; no truncation). Score = 10/10.

Status transition draft -> ready_to_implement is authorised by this verdict per STATUS-REFERENCE.md §1.1. The two human-acceptance gates in /ship-tasks are unchanged — this audit clears the spec-correctness gate only.


End of TASK-CUO-304 audit.