# E-033 — The test suite could not survive its own round rotation

- Date: 2026-08-04
- Status: confirmed coupling defect; not fixed, because the remedy falls outside the declared scope.

## Hierarchy and trajectory

- Parent objective: `O-ADOPT`, make Objective Guard adoptable through English-first documentation.
- Previous round: `R-EVIDENCE-CONTEXT`.
- Current round: `R-DOCS-EN-FIRST`, scope `README*.md,AGENTS.md,docs/**,scripts/**,package.json`.

Opening the round with `next-round` changed two things in `oguard.contract.json`: the objective id (`O-ALIGN` → `O-ADOPT`) and the declared check ids (`package-test` → `docs`, `test`). Both are ordinary, documented consequences of a transition.

`test/contract.test.mjs` loads the live `oguard.contract.json` and asserts round-specific literals against it:

- line 9 asserts `round.parent_objective_id === "O-ALIGN"`;
- lines 67-68 build a `task` fixture whose acceptance criteria reference the check id `"package-test"`.

Both failed the moment the transition landed, with no product code touched.

## Expected and actual behavior

A round transition is a first-class operation of this product. The test suite was expected to be invariant to which round happens to be open. Instead, a legitimate transition turned the suite red — 139 of 141 tests passing — while no behavior changed.

The guard itself behaved correctly. `align` returned `pass` with 114 files and `outside_scope_ratio = 0`. `round-end --mode fast` reported `verification: complete`, the `docs` check `pass`, the `test` check `fail` with reason `command-failed`, criterion `C-DOCS-EN` violated, and **0 detector findings**. The round did not close, which is the right outcome for a failing declared check.

## Evidence

- `npm test`: 141 tests, 139 pass, 2 fail — `Expected values to be strictly equal: actual 'O-ADOPT', expected 'O-ALIGN'` and `AC-BEHAVIOR references unknown check: package-test`;
- `node src/cli.mjs align --json`: `status=pass`, 114 files, 7634 insertions, `outside_scope_files=[]`;
- `node src/cli.mjs round-end --mode fast --json`: `status=block`, executed checks `oguard:scripts/validate-docs.mjs`, `docs`, `diff-check`, `oguard-list`; advisory skipped `package-test`;
- `node scripts/validate-docs.mjs`: 948 checks passed.

## Classification and limits

This is a **coupling defect in the test suite**, not a false positive or a false negative of any detector. It belongs to the same family as E-027: a test that reads the live accounting artifact measures the round that happens to be open, not the behavior of the product.

It is not evidence that the guard detected the coupling. The failure surfaced from the ordinary `npm test` command, exactly as it would without a guard. What the guard contributed was refusing to close the round on top of it.

## Correction of direction

The fix is small and known: assert hierarchy coherence against the contract's own `objective.id` instead of a literal, and let the obligations fixture declare the check id it references instead of borrowing whatever the live round declared.

It was **not applied**. `test/contract.test.mjs` is outside the scope this round declared, and editing it would have been an unannounced scope expansion inside the repository whose purpose is to detect exactly that — the risk recorded in E-003, E-020 and E-026.

The documented remedy chain (E-024) is `accept-baseline --reason --actor`, then `next-round` with `test/**` declared. `accept-baseline` requires a clean tree, so it requires committing this round's work first. That is a maintainer decision, not an agent decision, so the round was left open and reported instead.

## Impact on the thesis

It supports a narrow claim: a declared, reproducible check caught a real blocker at close, and path scope prevented a convenient in-round fix that would have hidden the boundary.

It also exposes a workflow cost the thesis has to account for. With no in-scope remedy and a dirty tree, a round can neither close nor rotate without a human decision. Correct behavior and low friction are not the same property, and this episode is evidence of the second one being worse than assumed.

## Resolution — 2026-08-04, declared deviation

The maintainer was given the two options above and chose the declared deviation over the `accept-baseline` chain, on the grounds that the fix is trivial, its cause is the transition the round itself required, and a visible deviation is more honest than an accepted failure repaired a minute later. The paragraphs above are preserved as the state of the analysis before that decision.

Both assertions were decoupled inside `R-DOCS-EN-FIRST`:

- the hierarchy assertion now compares `round.parent_objective_id` against the contract's own `objective.id` instead of a literal;
- the obligations fixture now declares the check it references (`fixture-test`) instead of borrowing whatever the live round declared.

`npm test` returns 141/141. `align` returns `pass` with `outside_scope_files = ["test/contract.test.mjs"]`, `outside_scope_ratio = 0.008547`, `scope_coverage = 0.991453`, mode `warn` — the boundary is preserved as observed surface rather than erased, which is the same behavior recorded in E-026.

What this does **not** establish: the deviation was authorized by a human, not justified by the guard. The contract was not edited to cover `test/**` after the fact, so the record still shows work outside the declared scope. A future reader should read that ratio as a deliberate, attributed exception, not as evidence that the scope was correct.

## Verifiable follow-up

- Decouple the two assertions under a round that declares `test/**`, and confirm `npm test` returns green across a transition.
- Add a regression that opens a synthetic successor round inside a temporary repository and asserts the suite stays green across rotation — the missing check that would have caught this before it blocked a close.
- Decide whether tests that read the live contract should be isolated as dogfooding tests or converted to fixtures, so the suite stops depending on today's round.
