core/tests/test_flag_register.py
Claude 6c67e88df1
feat(config,specs,tests): PR-5 — the flag register, profiles as units, and the daemon's missing fourth flag (R-3 + R-4)
Rank 3 on the docket. RuntimeConfig carries 32 boolean fields and nothing in
the repository stated that set, distinguished a capability flag from a posture
flag from a deployment knob, or recorded what evidence would flip any of them.
Two independent counts of the same set differed by eleven — the assessment said
"seventeen capability flags," N-7 measured 28 default-off plus 4 default-on —
and neither was wrong about what it looked at. They counted different things
because nothing declared what the set was.

R-3 (ruled A — incomplete flag set). accrue_realized_knowledge joins
CONTINUOUS_LIFE_CONFIG_FLAGS. Step D consolidates the realized facts Step B
writes, and the daemon forced the consumer without the producer: a continuous
life consolidating an empty set, which the mastery framework calls "garbage at
high speed." It shipped that way for six weeks.

The decisive evidence was in the code, not the documents. BOTH flags' comment
blocks in core/config.py already described the corrected profile —
accrue_realized_knowledge says "the production L10 process enables it alongside
persist_session_state", consolidate_determinations says "...alongside
accrue_realized_knowledge + persist_session_state". Three records (two
docstrings and the 07-25 verification doc) agreed with each other and disagreed
with four lines of code. Dormancy was a coherent ruling and would have cost
correcting three records that were RIGHT about the design; incompleteness cost
one line.

So the planned "N-6 docstring correction" deliverable DISSOLVED rather than
shipping: adding the flag made both docstrings true. Recorded explicitly,
because a PR that quietly ships less than it promised is the same divergence
class this arc exists to close.

R-4 (ruled A) — docs/specs/flag_register.md. All 32 booleans with class,
governing ADR or an honest "none", recorded rationale, and what evidence would
flip it. The classification is the load-bearing half and is stated as a
taxonomy rather than a label:
  CAPABILITY — what CORE can do; engineering may flip it on named evidence
  POSTURE    — what CORE serves or refuses AS TRUE; the wrong=0 boundary,
               ruling-only
  DEPLOYMENT — cost, lifecycle, process shape; per-deployment
estimation_enabled and composed_surface are both "= False" and are not the same
kind of thing. Nothing said so before.

Profiles are declared as the UNIT of decision (R-4's mechanism): one-shot/eval
and continuous-life. The absence of a SERVING profile is stated deliberately —
the four POSTURE serving flags each ride their own license, and grouping them
before the ledger is sealed would license by association, the outcome R-8
exists to prevent.

tests/test_flag_register.py, on the gate. BIDIRECTIONAL, because either
direction alone would have passed through both failures it exists to prevent:
a flag added to config.py and not registered fails; a register row outliving
its flag fails. The counts the register's prose states (32 / 4 ON) are pinned,
since a stale count is how this register came to be needed. Four sabotages
observed red — new unregistered flag, deleted row, orphaned row, and a flag
silently flipped ON, the last caught by two independent pins.

THREE FINDINGS THE ITEM DID NOT ANTICIPATE, all from measuring rather than
reading.

1. Accumulated permissiveness, not just hesitancy. Of the four default-ON
   flags, ONE has a governing ADR (deduction_serving_enabled / ADR-0256) and
   TWO have no recorded reason of any kind — allow_cross_language_recall and
   use_salience carry no comment block, no ADR, no criterion. In an
   architecture built on earned licenses, two permanently-on capability flags
   with no recorded decision is G-8's question inverted. Registered and
   deliberately NOT fixed: writing a rationale after the fact would invent a
   decision nobody made, which is worse than recording the absence.

2. A hollow gate inside the governance pin itself.
   test_default_on_flag_is_not_governed_by_a_proposed_adr is parameterized over
   (default-ON flag x cited ADR), so the three uncited ON flags contribute ZERO
   cases — it covered exactly one flag. Its non-vacuity guard asserted only
   that SOME flag cites an ADR, which default-off flags satisfy in abundance.
   One reformatted comment away from zero coverage with everything green: the
   failure state indistinguishable from the success state, inside the module
   written to prevent exactly that. Guard tightened to require a non-empty
   parametrization, and observed red by stripping ADR-0256's citation.

3. The daemon's own tests ran on no gate. R-3 changes what the always-on
   process does every beat, and tests/test_l10_always_on_daemon.py was in no
   curated suite — so this behaviour change would have shipped guarded solely
   by tests no gate invokes, which is the hollow-gate pattern landing on the
   exact file covering the change. Promoted to smoke WITH the change it guards.
   Measured +9.4s, +4.3% of smoke — paid deliberately, not estimated. Removed
   from full_only_baseline.txt in the same edit (both-directions ratchet; the
   baseline shrank 747 -> 746).

Red before green throughout: the assertion cfg.accrue_realized_knowledge is
True was added to the daemon test and OBSERVED FAILING before the flag existed.

H-8(c) closed by AMENDING the correction note rather than deleting it, and the
amendment is the more useful artifact. PR-1's note said the 07-25 document's
claim was false; R-3 then made that claim TRUE, so the correction note itself
went stale — in the direction nobody guards, a correction made wrong by its own
subject being fixed. Both notes now carry the commit window they apply to. A
dated correction survives its subject changing; an undated "this is false" does
not.

Also delivered, from the 2026-07-28 external-assessment triage: §5, the
declared-table index. Eight single-source-of-truth tables and the pin that makes
each true. A reader's aid, explicitly NOT a central contracts.toml — that would
add a fifth copy needing agreement with four generators and put four different
authorities in one merge surface.

Handed forward to PR-11: the 5000-beat soak recorded in
evals/l10_always_on/contract.md (2026-07-19) predates this change and therefore
describes a configuration that NO LONGER SHIPS. PR-11's re-run is the first soak
of the corrected profile, and Step B feeding Step D is exactly what a long
horizon stresses.

Closes G-6, G-8, H-6, H-8(c), H-8(d) — and H-8 in full. Wave 4's F-6 half-gate
is lifted; the Track C half is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wcw2pnMBwyvmNyQg4uPEt4
2026-07-28 06:47:21 +00:00

173 lines
7.5 KiB
Python

"""PR-5 / G-8 / H-6 — the flag register names exactly the flags that exist.
`RuntimeConfig` carries 32 boolean fields and, until `docs/specs/flag_register.md`,
nothing in the repository stated that set. Two independent counts of it differed by
eleven — the assessment said "seventeen capability flags," N-7 measured 28 default-off
plus 4 default-on — and neither count was wrong about what it looked at. They were
counting different things because nothing declared what the set *was*.
**Why a register needs a pin at all.** A document listing 32 flags is a 33rd copy of
the flag set, and a copy that nothing checks is exactly the artifact this arc keeps
finding: it looks like a source of truth right up until it silently falls behind the
thing it describes. `CLAIMS.md` published a superseded digest for two days that way
(G-22); `DOMAIN_PACKS` and the pack manifests drifted that way (G-23). A register
without this pin would be the third instance, shipped by the arc that catalogued the
first two.
So the check runs in **both directions**, which is the only version that means
anything:
* a flag added to ``core/config.py`` and not to the register **fails** — this is the
direction that stops the register decaying into a historical document;
* a register row whose flag no longer exists **fails** — this is the direction that
stops it accumulating entries nobody can act on.
Neither direction is the interesting one on its own. A one-directional version would
have passed happily through both of the failures it exists to prevent.
"""
from __future__ import annotations
import re
from pathlib import Path
_ROOT = Path(__file__).resolve().parents[1]
_CONFIG = _ROOT / "core" / "config.py"
_REGISTER = _ROOT / "docs" / "specs" / "flag_register.md"
#: ``name: bool = True|False`` in the ``RuntimeConfig`` dataclass body.
_FIELD_RE = re.compile(r"^\s*(?P<name>\w+)\s*:\s*bool\s*=\s*(?P<default>True|False)\b")
#: A register row whose FIRST cell is a lone backticked lower_snake_case name.
#: Lower-case is required on purpose: the declared-table index in §5 lists tables
#: (``TEST_SUITES``, ``GATE_SUITES``, ``CONTINUOUS_LIFE_CONFIG_FLAGS``) in the same
#: shape, and they are not `RuntimeConfig` flags. §5 is also cut off before parsing —
#: two guards, because a parser that silently over-matches would turn this pin into a
#: source of false failures, and a pin people learn to distrust is worse than none.
_ROW_RE = re.compile(r"^\|\s*`(?P<name>[a-z][a-z0-9_]*)`\s*\|", re.MULTILINE)
#: The section that stops being about individual flags.
_INDEX_HEADING = "## 5."
def _config_flags() -> dict[str, str]:
"""``flag -> "True"|"False"``, derived from the source. Never restated."""
out: dict[str, str] = {}
for line in _CONFIG.read_text(encoding="utf-8").splitlines():
match = _FIELD_RE.match(line)
if match:
out[match.group("name")] = match.group("default")
return out
def _register_text() -> str:
text = _REGISTER.read_text(encoding="utf-8")
cut = text.find(_INDEX_HEADING)
return text[:cut] if cut != -1 else text
def _register_flags() -> set[str]:
return {m.group("name") for m in _ROW_RE.finditer(_register_text())}
def test_the_register_exists() -> None:
assert _REGISTER.exists(), (
"docs/specs/flag_register.md is missing — G-8 reopens the moment it does"
)
def test_neither_parse_is_vacuous() -> None:
"""Guard both derivations before asserting anything about their relationship.
If either side parsed to an empty (or tiny) set, the equality assertions below
would pass on nothing and this module would go quietly blind. Silence reading as
success is the exact failure mode the register was written to expose, so it is
asserted here rather than assumed.
"""
config = _config_flags()
register = _register_flags()
assert len(config) >= 30, (
f"parsed only {len(config)} bool fields from core/config.py — the field "
"regex has drifted from the dataclass"
)
assert len(register) >= 30, (
f"parsed only {len(register)} flag rows from the register — the table "
"format changed and this pin can no longer read it"
)
assert any(d == "True" for d in config.values()), (
"no default-True flag parsed — the field regex has drifted"
)
assert _INDEX_HEADING in _REGISTER.read_text(encoding="utf-8"), (
"the declared-table index heading moved; the parser's cut-off is now wrong "
"and §5's table names would be read as flags"
)
def test_every_flag_is_registered() -> None:
"""A new flag cannot land unregistered.
This is the direction that keeps the register alive. If it fails on a flag you
just added: add a row with its class, its governing ADR (or an honest "none"),
and **what evidence would flip it**. The last column is the one that matters —
a flag whose criterion is unrecorded is an open question wearing a default.
"""
unregistered = sorted(set(_config_flags()) - _register_flags())
assert not unregistered, (
"these RuntimeConfig booleans are not in docs/specs/flag_register.md: "
f"{unregistered}"
)
def test_no_register_row_outlives_its_flag() -> None:
"""The other direction: a removed flag must leave the register.
Same discipline as the suite-membership, reachability and domain-pack ratchets.
A register that keeps rows it no longer needs stops being a measurement and
becomes a story about one.
"""
orphaned = sorted(_register_flags() - set(_config_flags()))
assert not orphaned, (
"the register describes flags that no longer exist in core/config.py: "
f"{orphaned}"
)
def test_the_recorded_shape_of_the_set_matches() -> None:
"""Pin the counts the register's prose states.
The register opens by asserting "32 boolean fields: 28 default False, 4 default
True." That sentence is the thing readers quote, so it is the thing most likely
to go stale — and a stale count is how this whole register came to be needed.
"""
config = _config_flags()
on = sorted(f for f, d in config.items() if d == "True")
assert len(config) == 32, (
f"RuntimeConfig now has {len(config)} boolean fields, not 32 — update the "
"register's opening paragraph and §1 in the same change"
)
assert len(on) == 4, (
f"the default-ON surface is now {len(on)} flags, not 4: {on}. §1 of the "
"register is a per-flag table of exactly this set and must be updated — it "
"carries the highest justification burden in the document"
)
def test_the_default_on_surface_is_named_in_the_register() -> None:
"""§1 tables the ON flags by name; each must actually be ON.
The ON surface is the only part of the register where being wrong is immediately
dangerous: a flag listed as ON that is off understates what ships, and a flag
that is ON but absent from §1 ships unjustified.
"""
on = {f for f, d in _config_flags().items() if d == "True"}
section = _register_text()
start = section.find("## 1.")
end = section.find("## 2.")
assert start != -1 and end != -1, "the register's §1/§2 headings moved"
named = {m.group("name") for m in _ROW_RE.finditer(section[start:end])}
missing = sorted(on - named)
overclaimed = sorted(named - on)
assert not missing, f"default-ON flags absent from the register's §1 table: {missing}"
assert not overclaimed, (
f"the register's §1 table names flags that are not default-ON: {overclaimed}"
)