Checklists
requirements.md
Requirements Quality Checklist: Sleep-count assertions survive concurrent sleepers
Purpose: gate spec.md before /spec-kitty.plan. Checks requirement hygiene (IDs, status, measurable thresholds), evidence provenance (no invented numbers), and — the load-bearing one for this programme — that no success criterion is satisfiable by an implementation that fixes nothing. Created: 2026-08-05 · Last run: 2026-08-05 (iteration 3, against spec revision R2) Mission: spec.md · Directive: analysis-report.md
Run log:
CHK021 and CHK026 against a spec whose live evidence was falsified and four of whose criteria were satisfiable by a one-line rewrite. A post-spec adversarial squad found 6 of 13 criteria passes-while-broken and two blockers. The checklist's own miss is recorded below under ## Why iteration 2 passed a spec that was 6-of-13 broken.
because R2's operator rulings changed what the item should assert, and 8 items added (CHK035– CHK042) covering the failure modes iteration 2 had no item for. 3 of the allowed 3 iterations used — no further iteration is available, so the residuals in ## Residual risk are carried to plan rather than fixed here.
- Iteration 1 — 7 failures (CHK008, CHK013, CHK019, CHK021, CHK024, CHK027, CHK034); fixed.
- Iteration 2 — all items re-run, clean. This verdict was wrong: it passed CHK012, CHK019,
- Iteration 3 (this run, against R2) — 8 items re-failed and were fixed, 3 items amended
Structure and identity
- ✅ CHK001 The three scaffold sections (
## User Scenarios & Testing,## Requirements,## Success Criteria) appear with unmodified headings, in scaffold order. Verified iteration 3:grep -n '^## '→ 131, 302, 358. - ✅ CHK002 Additive sections sit before the first scaffold section (
## Problem, line 11) or after the last (## Out of scope796,## Corrections809,## Unverified865,## Open questions895) — none interleaved. - ✅ CHK003 Requirement types are in separate tables with type-unique IDs:
FR-001..FR-010,NFR-001..NFR-007,C-001..C-010. No ID reused across types.FR-010added in R2 for the R-1 alias seam. - ✅ CHK004 Every requirement row has a non-empty
Status. Measured: 27 rows total (FR 10 + NFR 7 + C 10) — 25Open, 2Retired (R-1)(FR-006,FR-007), retained with reasoning and not renumbered, so no successor reads a gap. - ✅ CHK005 Every open requirement row has a non-empty
Priority. Measured: FR open rows — 6 High / 1 Medium / 1 Low. NFR — 6 High / 1 Medium (NFR-005 Medium; NFR-007 raised from Medium to High in R2, because it is the criterion that makes<census>trustworthy and three criteria depend on<census>alone). C — 8 High / 2 Medium. Amended in iteration 3: the item now scopes to open rows, because a retired requirement carriesPriority: —by design. Rewriting a retired row's priority asLowwould imply it is still work. - ✅ CHK006 Success criteria carry stable IDs
SC-001..SC-016and every delivery criterion maps to at least one FR/NFR/C. SC-001→FR-001/002, SC-002→FR-002, SC-003→FR-004, SC-004→FR-003, SC-005→NFR-001, SC-007→FR-005, SC-008→NFR-004, SC-009→NFR-002/003, SC-010→FR-008, SC-012→NFR-006, SC-013→FR-010, SC-014→NFR-005, SC-015→NFR-007, SC-016→C-002/C-004/C-008/C-010. This also closes the squad's finding thatSC-006was the only criterion with no requirement anchor — it is retired, so no unanchored criterion remains.
Measurability
- ✅ CHK007 Every NFR states a numeric threshold with a unit: NFR-001 ≥ 100 calls; NFR-002 10/10 runs; NFR-003 6 runs / identical pass sets; NFR-004 0 errors + pin count pinned at 12; NFR-005 ≤ 5.0 s / ≤ 60 s; NFR-006 0 findings, 0 added suppressions, 0 added config ignores; NFR-007 AST-derived + committed control fixture.
- ✅ CHK008 Every NFR threshold is measurable against a baseline that exists or is required to be captured. Iteration 1: FAIL (NFR-005 had no baseline). Iteration 3: re-failed — NFR-005 still had no criterion at all, and its command was
python3 -m pytest, which on this machine is 3.14.4 without project dependencies while CI pins 3.12. Fixed: SC-014 added as NFR-005's criterion; both arms pinned touv run --python 3.12, withpython -Vreported per arm and required identical. NFR-007 also had no criterion and<census>is the sole instrument for three criteria — fixed by SC-015. - ✅ CHK009 Every delivery criterion is a command plus its expected output, not prose. 14 of them are. Amended in iteration 3:
SC-006(retired) andSC-011(demoted) are prose entries that explain their own removal from the criterion count; the item now scopes to delivery criteria and CHK038 checks that the exclusions are honest. - ✅ CHK010 Expected outputs are exact strings or numeric comparisons, not "should look right". Iteration 3 fix:
SC-003's pinned text was[1.35, 3.0, 6.6], which is unsatisfiable — production computes6.01.1 = 6.6000000000000005 (verified this session). An unsatisfiable literal is worse than a vague one: it can never be reported as met, so it gets silently downgraded. Corrected, with the note that the unmutated[0.9, 2.0, 4.4]is exact.* - ✅ CHK011 Where a criterion names an artifact this mission will create, the criterion pins the output, not the path.
<census>,<guard>,<gate>,<census-control>,<guard-rationale>,<wp-notes>— all placeholders, defined above SC-001. Production symbols named in FR-010 are the R-1 exception, not artifact paths; see CHK026. - ✅ CHK042 Every quoted provenance command reproduces its quoted number. NEW in iteration 3, and it fails on R1:
spec.md:370-371quotedgrep -c 'patch("specify_cli.tracker.saas_client\.'as the source of the 15-vs-14 pair, and that command returns 68 (verified this session). Fixed: replaced with the command actually run,grep -rc 'patch("specify_cli.tracker.saas_client\.time\.sleep")' <two files>→ 14 + 1 = 15. A number with the wrong command attached is indistinguishable from an invented one, which is exactly what CHK012 exists to prevent — so CHK012 was passing on a technicality.
Evidence provenance
- ✅ CHK012 Every count in the spec traces to a command that was run, or is marked
[UNVERIFIED]. Iteration 3: re-failed on three counts — the 15-vs-14 provenance command (see CHK042),NFR-001's "33× above 48" (arithmetically impossible:100/48 = 2.08, and33.3 = 100/3is computed against the expected count, not the observed inflation), andSC-006Arm A's unattributed2116 passed, 11 skippedsplit. All three fixed. Counts re-verified this session by command: 22 files / 14 patch sites (13+1) / 4 nodes / 5 assertions / 53E-rows / 73 collected / 28-vs-13 / 15-vs-14 / 68 (the wrong command's real answer) / 12_PinnedLeak(entries (AST) /6.6000000000000005/grep -cEover two files printing per-file counts /per-file-ignorespresent in both config files /upstream/main=98198e980/ PR #3209 head =5e98c2bb7(gh pr view). Six[UNVERIFIED]entries now recorded, up from two. - ✅ CHK013 The census probe was controlled against a target whose answer is known independently, and the control is shown. Iteration 1: FAIL. Iteration 3: re-failed for a different reason — the control was described in prose and commanded nowhere, while
<census>is the sole instrument for SC-001, SC-002 and SC-013, so a hardcoded output table satisfied all three; and every positive case in it was decorator-shaped, which is why the census missed nine instances. Fixed: SC-015 commits the fixture as a test with ground truth pinned in-test, adds context-managerpatch()andside_effect=-kwarg positive cases, and adds a census self-mutation arm. - ✅ CHK014 The two code facts the brief asked to verify are verified with the command shown:
saas_client.py:19bareimport time; the patch-decorator census. - ✅ CHK015 Established findings reused from
#3115/#3136are cited, not re-derived, and marked as such (### Established, reused, not re-derived). - ✅ CHK016 Facts the spec declines to trust are named as such. C-007 + FR-008 on the inventory verdict column; six
[UNVERIFIED]entries, three of which are squad-sourced CI-log figures this session did not re-fetch (28,11/18,39%) with their load-bearingness stated for each. - ✅ CHK041 No claim rests on a line number that was not opened. NEW in iteration 3, and the highest-value item in the set — it is the failure mode that produced this whole programme. R1 failed it twice:
test_saas_client.py:804was cited as an assertion when it is aside_effectstimulus (the only assertion is thepytest.raisesat:806), and the claim was inherited from a docstring at:55-57. The sibling mission failed it identically attests/integration/test_coord_loop_workspace.py:611. Iteration 3 opened and read every load-bearing citation:saas_client.py:18-19,:104-106,:439,:478-481,:484,:515-518;test_saas_client.py:24,:55-57,:513,:532,:550,:559,:762,:784,:786,:787,:804-807;test_saas_client_origin.py:22,:229,:261;test_final_sync_diagnostics.py:27,:298-312;test_git_metadata.py:28,:218,:226,:242,:249,:274,:281,:471,:530;sync/batch.py:11;sync/git_metadata.py:13-14;_leak_guard.py:333,:424;conftest.py:461-500;ci-quality.yml:1155-1172;charter.md:504-516. - ✅ CHK017 Every
file:linecitation resolves on98198e980. Subsumed by CHK041 this iteration, which is strictly stronger — resolving is necessary but not sufficient.
Adversarial soundness — the load-bearing section
- ✅ CHK018 For every success criterion, an implementation that satisfies it without fixing anything is constructed and recorded.
### Adversarial Analysis of Success Criteria: 40 rows across 16 criteria plus two that apply to all. Rows marked[R2]are the squad's cheats; one row is marked[R2-open]— a cheat acknowledged as not fully closed, with the residual stated rather than hidden. - ✅ CHK035 Cardinality is measured, not only required in prose. NEW in iteration 3 — the sharpest hole the squad found.
assert 3.0 in [c.args[0] for c in mock_sleep.call_args_list]is honestly non-corruptible, honestly value-checking, satisfies SC-001/SC-002 and both R1 mutation arms, and silently drops cardinality.FR-002forbade it in prose and measured it nowhere;SC-001pinned nodes only. Fixed two ways:sleep_assertions: 5added as an SC-001 denominator, and SC-003 Arm 3 added as a cardinality mutation (duplicate the_sleep(...)call atsaas_client.py:439; add a fourthpendingresponse) requiring red on the count with observed-vs-expected counts in the failure text. SC-002 additionally requiresn=to be derived from the assertion's cardinality expression, not from the printed list's length. - ✅ CHK019 Every criterion whose form is a negative has a positive twin that the instrument demonstrably catches. Iteration 1: FAIL (SC-008, SC-010). Iteration 3: re-failed on five negatives — (a)
SC-008's pin grep'^\+._PINNED_LEAKS' is structurally inert: entries are_PinnedLeak(...)calls and the token appears only at_leak_guard.py:333and:424, so a diff adding a real entry matches nothing (fixed:^\+\s_PinnedLeak\(plus an AST count pinned at 12); (b)SC-008's twin is serial-only —conftest.py:483-492prints a different line under xdist and:467-468documents a real-n 4run printinginspected 0 test(s)(fixed:-n0pinned, property stated); (c)SC-012's "0 added# noqa" had a config escape hatch (fixed: config diff reported as text); and two negatives I introduced in this revision and had to twin — (d) SC-007 arm 4's "0 direct calls" (twin: report the 3 alias definitions and 5 rerouted call sites, so a checker that parses nothing fails loudly), (e) SC-016's C-008 "no output" (twin: a siblinggit diff --statonsaas_client.pymust be non-empty, proving the ref resolves) and SC-013 sub-4'sgrep → 0(twin:grep -c 'side_effect stimulus'≥ 1, so the fix is a correction and not a deletion). - ✅ CHK037 Every negative-form pin or diff check is structurally capable of matching what it claims to refuse. NEW in iteration 3, generalising the
_PINNED_LEAKSlesson: a check whose pattern cannot match a real violation is worse than absent, because it reports success. Applied to everygrep -c … = 0in the spec: the_PinnedLeak(pattern (fixed), the# noqadiff pattern (matches —^\+.# noqa does match an added suppression), theE-number patterns (match — verified this session), theis the assertiondocstring pattern (matches — returns 1 today, which is the proof it can), andruff formatin WP notes (matches by construction). Each was tested against a case it must refuse, not assumed.* - ✅ CHK020 No criterion is satisfiable by a test pass at any breadth. Iteration 2 passed this on the claim that every arm is "an injection or a full-shard composition". The full-shard half is now known to be worthless: pristine main reddens 11 of 18 jobs including at
98198e980, so a clean full shard is the pre-fix outcome 39% of the time. Fixed: every surviving arm is an injection (SC-003/004/005), a static measurement (SC-001/002/007/008/010/012/013/015/016), or a repetition-counted determinism arm (SC-009). The one probabilistic criterion was retired, not weakened. - ✅ CHK021 "The victim tests pass" does not appear as a criterion in any form. Iteration 1: FAIL (SC-006 Arm A). Iteration 2 "fixed" it by labelling Arm A a control — which was factually false, since main is not green there. Iteration 3:
SC-006retired in full, both arms, with the reasoning recorded in place and in## Out of scope. Story 1's acceptance scenario 3 (the shard-composition scenario) likewise withdrawn and replaced with the base-branch red. - ✅ CHK022 "No assertion keys on a process-global counter" cannot be satisfied by deleting the assertions. SC-001's four denominators plus
sleep_assertions: 5; SC-002's four contracts at the same values with cardinality-derivedn=; SC-003's three mutation arms. - ✅ CHK023 The red-first proof is a genuine failure of the current assertion shape, not a paraphrase. Iteration 3: re-failed —
SC-004enumerated four pre-fix forms for five census assertions;assert delays == [0.9, 2.0, 4.4](:786) had no pre-fix red arm and is the member most likely to be cosmetically "hardened" while still reading the unfiltered recorder. Fixed: SC-004 is now five tabulated rows, one per census assertion, each with its literal pre-fix expression, and the guard prints the five row identifiers so a reviewer can count them. - ✅ CHK024 The instrument that produces the red is itself proven to be firing. SC-005's mutual dependency with SC-004 arm (b). Both squad lenses attacked this criterion and failed; it is the best-built one in the set and is carried forward substantively unchanged.
- ✅ CHK025 The gate (FR-005) is non-vacuous. Iteration 3: re-failed on two counts — (a) the gate was keyed on
time.sleep, somock_randbelow.call_count == 1three lines from a census assertion, andmock_run.call_count == 2, walked past it; (b) its floors were counts (scanned_files >= 22), satisfiable by globbing any 22+ files undertests/while never openingtests/sync/tracker/. Fixed by R-2: mechanism-keyed predicate,tests/sync/enforcement, named-file membership sets, the13 + 1split, the four node-ids verbatim, three self-mutation arms (decorator /subprocess.run/ context-manager-with-side_effect=), and a seam-routing arm. - ✅ CHK036 Every floor is a membership set where a count would be gameable. NEW in iteration 3. Applied to SC-007 (names four files, the 13/1 split, four node-ids), SC-004 (five row identifiers), SC-001 (lists the 9 non-tracker instances with dispositions), SC-015 (per-count observed-vs-ground-truth). A floor of the form
N >= kis only acceptable where the identity of the counted things is pinned elsewhere. - ✅ CHK040 The charter's red-on-base requirement is addressed, or the exception is documented. NEW in iteration 3, and R1 failed it silently: the charter's ATDD-First Discipline (
charter.md:504-513) requires the test RED onplanning_base_branchand GREEN on the final commit, and R1 mentioned neither (grep -c 'ATDD\|planning_base_branch' spec.md→ 0). Injection-red is not base-branch-red — arm (b) is designed to raise and be caught, on any branch. Fixed:### Charter red-on-base — the exception, namedtabulates three arms that are red on98198e980for structural reasons, the load-bearing one being thatpatch("…saas_client._sleep")cannot be set up because the R-1 alias does not exist at that SHA. TheAttributeErrortext is marked[UNVERIFIED](no test bodies were run); the fact of the red is structural.
Scope discipline
- [~] CHK026 The spec says WHAT and WHY; no code seam is prescribed. AMENDED — deliberate exception, not a pass. Iteration 2 recorded this as passing and it did. R-1 overrides it: the operator has ruled the product-side module-local alias, so the spec now names
_sleep/_monotonic/_randbelow, their five call sites, the ADR, and the_poll_jitter_multiplierresolution (FR-010). Recorded as an operator ruling, bounded to those items, and justified in## Corrections to the incoming brief#5: the seam's three conditions (ADR, gate arm, dead-seam resolution) are what stop it becoming the next_poll_jitter_multiplier, and none of them is a plan-level choice. The plan retains freedom over<census>,<guard>,<gate>and every artifact path. Marked[~]rather than[x]so the exception stays visible to a reviewer. - ✅ CHK027 Operator decisions are treated as settled and not re-opened. Five now: the three from the brief (the lever is the assertion class; the contention reproduction is a non-goal; the inventory verdict column is unverified) plus R-1 and R-2 from the post-spec squad. All five named as settled where they appear.
- ✅ CHK028 At most 3 clarification markers. Now 0 — all three R1 markers settled by the rulings and recorded struck with resolutions. Not asserted as a grep: any command quoting the marker string matches its own quotation.
- ✅ CHK029 Each surviving clarification marker is a decision the operator must make, not a gap the plan can close. Vacuously satisfied (none survive), and each retirement records why it was an operator decision and how it was decided — including that marker #2 carried a false blocker (
301.0"IS its assertion"), and that deferring on a non-existent blocker would not have been a legitimate decision. - ✅ CHK030 Non-goals are recorded with reasoning, not just listed.
## Out of scope — explicitly, 8 entries (up from 6). The contention reproduction carries all three reasons and is duplicated into C-005 so it survives a partial read; SC-006's retirement carries its full derivation; the acknowledged residual (seam check not widened beyondsaas_client.py) is listed rather than hidden. - ✅ CHK031 Constraints the brief specified are all present:
tests/sync/tests/clinever concurrent and window-holder check (C-001);ruff checkonly (C-002);#3130not fixed here (C-003). - ✅ CHK039 Every constraint has an enforcement check, and every command is runnable in the target environment. NEW in iteration 3, failing on both halves. (a)
C-002,C-004,C-008,C-010had no enforcement anywhere, two of them High — fixed by SC-016, one command each, withC-004restated so it can actually fail (R1's form rested on agit checkout --revert, which a clean revert satisfies even if a different line shipped changed; and under R-1 its impliedgit diff … emptycheck is false by construction). (b) every SC command waspython3 -m pytest, unrunnable here: the mission venv is Python 3.11.15 with no pytest and no ruff, the ambient interpreter is 3.14.4, and CI pins 3.12 — three interpreters, none of them the right one. Fixed: all commands areuv run --python 3.12 python …(uvverified present), and the first WP provisions the environment and records the resolved versions before any acceptance arm runs. - ✅ CHK032 Findings that exceed the brief are folded in rather than silently dropped. Six entries in
## Corrections to the incoming brief, two of which correct this spec's own R1 — the withdrawn composition claim and the un-prescribed-seam exception — plus the docstring-as-constraint failure mode recorded as a programme-level rule. - ✅ CHK038 The criterion count is honest: retired and demoted criteria are excluded from it and say why. NEW in iteration 3.
SC-011was vacuous — measured3 / 3 / 5against the spec as committed, before any work, so it graded the spec's prose and no implementation could fail it.SC-006discriminates nothing. Both are retained as IDs with in-place explanations and excluded from the stated count, which is now "14 delivery criteria: SC-001–005, SC-007–010, SC-012–016". Renumbering was rejected: a successor readingSC-006in the plan or a WP must find it, not find a gap. - ✅ CHK033 The spec keys on the class, and where it lists node-ids it says why the list is safe to state. Strengthened in R2: the class is now defined by the mechanism predicate (penultimate segment resolves to a
ModuleType) as a Key Entity, with the census demoted to a derived instance list. R1's claim that the class is "confined totests/sync/tracker/" is struck — it was a statement about R1's census, which inspected decorators only and could not match aside_effect=kwarg, and nine further instances undertests/sync/were each opened and read. - ✅ CHK034 No two requirements read as contradicting each other. Iteration 1: FAIL (FR-001 vs FR-002). Iteration 3: re-failed —
C-004("no change tosaas_client.py") directly contradictsFR-010(changesaas_client.py). Fixed:C-004restated as "changed only by the declared alias seam and the_poll_jitter_multiplierresolution, with the retry behaviour unchanged", with SC-016 enumerating the permitted hunks. One numeric disagreement is left standing deliberately and with an owner:C-003says "11 confirmed leaks" and the registry holds 12 (_leak_guard.py:333, AST-measured). SC-008 pins 12 and requires the WP to state which reading of 11 was wrong. Two numbers coexisting with a named reconciliation owner is honest; two numbers coexisting silently is what CHK034 exists to catch.
Why iteration 2 passed a spec that was 6-of-13 broken
Recorded because a checklist that cannot explain its own miss will repeat it.
1. *It checked that a cheat was constructed, not that the closure held. CHK018 counted 22 cheats and passed. It had no item asking whether the recorded closure actually closes — and four of them did not, all against the same one-line in-form rewrite. CHK035 now targets that specific class; CHK037 generalises it. 2. It trusted the spec's own live evidence. CHK012 checks that counts trace to a command. R1's 2116 passed, EXIT=0 did trace to a command — it just was not representative, and CI's own logs said the opposite at the same SHA. No item asked whether a measurement's variance had been characterised before it was used as a control. That is why CHK020 now refuses a pass at any breadth rather than trusting the "full-shard composition" escape. 3. It treated a passing grep as a satisfied check. CHK019's iteration-2 fix added two positive twins, and one of them (_PINNED_LEAKS) was a pattern that cannot match a real violation. CHK037 now tests every negative pattern against a case it must refuse. 4. It read line numbers instead of lines. CHK017 asked whether citations resolve*. Every one of R1's did. test_saas_client.py:804 resolves and is not an assertion. CHK041 replaces resolution with reading, and it is the item most worth a reviewer's own hand-check. 5. It had no environment item at all. Every one of the 13 criteria was unrunnable on this machine and nothing noticed, because "is this command runnable here" was not a question the checklist asked. CHK039.
Residual risk — carried to plan, not fixed here
The iteration budget is exhausted (3 of 3). These are known and deliberately not closed:
the tracker cone is not covered by SC-007 arm 4, whose AST check is scoped to saas_client.py. Widening the seam check to src/specify_cli/ is left to a successor and is recorded in ## Out of scope.
they support a criterion retirement, which fails safe — but NFR-001's 3.57× ratio depends on 28, and a reviewer who wants that number should re-fetch the logs.
were each opened and read, so they are verified as instances; no command has produced a census total over the widened scope, because <census> does not exist yet. SC-001's non-tracker listing is therefore a requirement on the artifact, not a reproduction of a measured figure.
WP (SC-008) rather than resolved here, because resolving it requires deciding which of the 12 pins is not a #3130 leak — a judgement about #3130's scope, which C-003 puts outside this mission.
- The
[R2-open]row in the adversarial table. A production call added to a different module in - Three squad-sourced CI figures are
[UNVERIFIED](28,11/18,39%). None gates the mission — - The committed census's own total over
tests/sync/is unverified. The nine non-tracker instances C-003's "11" versus the registry's "12" is left as a named reconciliation for the implementing