RUL-013 — Specs are authoritative — resolve decisions from them, not guesses

Ruling

Resolve behaviour/contract/version design decisions from specs/SPEC-000..105 + @traces(reqs=[...]) before asking the user to re-decide something a spec already says, and never from a test (tests can enshrine the wrong thing) or a guess.

Evidence

Enforcement

Verification

The evidence half is mechanized; the workflow half stays judgment. Two bullets, deliberately.

scripts/check_spec_authority.py enforces the "never from a test" clause directly: a requirement id cited by a test must be defined by a spec under specs/ — a test may never invent an id to justify its own behaviour. It runs the AST toolkit's trace-lint over tests/, keeps the dangling rule for symbols named test_*/Test*, and grounds each cited id against every REQ- token in specs/**/*.md (a strict superset of the narrow checklist ground truth — measured 5540 vs 5413 ids, narrow-minus-broad = 0 — so the gate can only under-report a fabrication, never manufacture one). Invoked from scripts/health_check.py as "Spec authority (RUL-013)", pinned at --max 0. The discriminator is exact: 11 raw dangling findings reduced to 8, dropping precisely the three non-test fixture classes, with no allowlist.

The gate found 8 real violations at introduction, and they were fixed the RUL-013 way. Eight test functions across six files cited seven ids — REQ-003-FR24-100 … REQ-003-FR24-106 — that no spec defined (tests/e2e/test_full_pipeline.py:85, tests/orchestrator/test_pipeline_e2e.py:51,60,80, tests/orchestrator/test_pipeline_e2e_analysis.py:43, tests/orchestrator/test_pipeline_e2e_docs_session.py:65,90, tests/orchestrator/test_pipeline_e2e_review.py:34). The resolution was to write the requirements into the spec — a new "End-to-end pipeline execution (PipelineRunner, in-process)" block in specs/003-intent/checklists/requirements.md stating each behaviour those tests already prove — not to re-point the tests at unrelated existing ids, which would have made the spec follow the test instead of the other way round. Measured after: 0 ungrounded ids.

Because the backlog is genuinely gone, the ceiling is pinned at --max 0 rather than left at the introduction ratchet of 8 — a ceiling above the real count is 8 free fabrications, which is the same prose-only loophole in numeric form. Non-vacuity verified by planting REQ-003-FR24-107 (exit 1) and swapping it for the real REQ-003-FR24-004 (exit 0).

The remaining bullet is honest: "an agent asked the user to re-decide something a spec already answers" leaves no artifact a lint can see. serialize_codebase_ast.py check-traces and scripts/factory_trace_check.py validate @traces structure, but that workflow discipline is enforced by review.