--- id: CB-WP-0048 kind: product title: "A configuration, not a variant" status: active state_hub_workstream_id: "44ceb8d8-e6e2-46d6-a694-fd275804b771" --- # Purpose ``` structural tier M (changes the state and therefore the recording, and replaces the selector every consumer names) declared tier M ``` Implements [ADR-0022](../decisions/ADR-0022-a-configuration-is-a-point-in-aspect-space.md), adopting ground-game catalog **schema 2** per [CB-RES-0010](../research/CB-RES-0010-game-aspects-and-design-space.md). ## The gap, stated exactly `scoped_plus_attack_soothe` is a profile in the vendored catalog **today**. clay-borg cannot name it — not "has not implemented", *cannot express*. `Variant` has three arms; H1 bundles flat pressure with self-soothe and cannot be taken apart, H2 gives scoping alone, and their product has no arm. Every future combination has the same problem. The kernel is not the obstacle: **four sites branch on the variant and each belongs to exactly one aspect** (ADR-0022 Context). The selector is the blob. ## Task: the mirror is held by the gate ```task id: CB-WP-0048-T00 status: done priority: high state_hub_task_id: "fe1a39f4-d123-4c83-be64-35901603edcd" ``` **Done 2026-08-08.** The schema-2 mirror (`catalog.yaml`, `ASPECTS.md`, `CATALOG.md`, `modules/`) arrived vendored with **no recorded digests** — 19 files — and `make edition-check` was red on the working tree. - Digests are now **generated by walking `editions/`**, not typed. Two reviews (CB-REV-0002 #8, CB-REV-0003 #8) already found hand-written lists that made their own controls vacuous; a mirror that grows a *directory* is precisely the case a maintained list loses. - `editions/PROVENANCE-catalog.md` was a file **inside the mirrored tree that upstream does not have**, which the gate correctly refused as unexplained. Folded into `editions/ground-darvo-r0/PROVENANCE.md` — provenance *about* the mirror is ours, so it does not live inside the thing it describes. **Noted, not fixed:** `vendored_files()` matches only `.csv` inside the edition directory, so a non-CSV added *there* is invisible to both the digest and the freshness check. The sibling walk has no such hole. The exposure is one file today (`PROVENANCE.md`, ours) — but this is F26's shape again and should be closed on its own terms. ## Task: `Configuration` and `Rules` ```task id: CB-WP-0048-T01 status: done priority: high state_hub_task_id: "4b8bfc25-0e4d-4fdc-9dad-5cdb66798207" ``` Per ADR-0022 D1. Identity as data, behaviour exhaustive, `resolve()` between them. **Done 2026-08-08.** `catalog.rs` reads schema 2 (aspects, modules, profiles) and **refuses a schema it does not understand by name** — a reader that silently accepted schema 1 would answer questions about aspects over a file that has none. `config.rs` holds `Configuration` (identity), `Rules` (behaviour) and `resolve()`. Nothing about the aspect list, the defaults, or which modules are proposed is written in our source; all of it is read. The tests locate the proposed module **by searching the catalog for `status: proposed`**, so if ground-game implements it upstream the test looks elsewhere rather than going stale. | mutation | what went red | |---|---| | the two errors become one sentence | *"a module the catalog HAS was reported as unknown"* | | one-per-aspect check removed | *"two modules on one aspect must be refused"* | | identity refuses what it cannot run | *"selecting a catalog module must NAME a configuration"* | | legacy lookup stops reading the catalog | *"h2 and profile h2 are not the same point in aspect space"* | `scoped_plus_attack_soothe` now resolves — `problem_stress.scoped` × `attack_relief.self_soothe_ge4`, the combination the three-armed enum could not express. **Controls:** - **a proposed module is refused BY NAME**, distinguishably from a typo — the two-error requirement is the reason this shape was chosen, so it is the first test; - **two modules on one aspect are refused** (GAME↔MODEL validation); - **an aspect the catalog has and the kernel does not know still parses**, because a configuration must be nameable before it is runnable; - **`Rules` has no catch-all arm**, so a new module cannot fall through silently (CB-WP-0034's exhaustiveness, which caught two commands before any test ran); - mutation-proven, each control against its own defect. ## Task: legacy ids alias forever ```task id: CB-WP-0048-T02 status: todo priority: high state_hub_task_id: "3232a451-b60a-42f6-8200-b8115d4b35b9" ``` Per ADR-0022 D2, through the catalog's own `legacy_experiment_id`. **Controls:** - **all 26 recordings replay byte-identically**, which is the whole constraint — `make sim` is the authority; - **`--variant h2` and `--profile h2` produce the same resolved configuration**, asserted on the resolved object rather than on behaviour, so the equivalence is exact and not a coincidence of outcomes; - **the expansion comes from the catalog**, not a table in our source: a second copy of ground-game's mapping is F25's shape. ## Task: the resolved configuration is recorded ```task id: CB-WP-0048-T03 status: todo priority: high state_hub_task_id: "5be13b74-bcc9-4697-96bb-91f1966c760b" ``` Per ADR-0022 D3 and CB-RES-0010 §5.3: `cb-play` sessions, replays, trial logs and panel cells carry the **resolved** module list. **Controls:** - **the recorded configuration is the applied one**, proven the way CB-WP-0046 proved the trial-log stamp — from the state, not the flag; - **the trial-log marker carries the configuration**, closing the gap CB-WP-0047 left open (variant stamped, scenario and mode not); - **a bare field write cannot leave a module inert** — the H2 defect had three call sites and this is the generalisation of it. ## Task: the page and the panels speak aspects ```task id: CB-WP-0048-T04 status: todo priority: normal state_hub_task_id: "ff135f86-2265-4db5-b412-23fcb68df489" ``` **Controls:** - **the table names every live module**, not a legacy id — the CB-WP-0044 family: a rules change the page cannot name is reported as "no changes"; - **panels facet by aspect** (CB-RES-0010 §5.4), so "hold all aspects fixed, vary one" is what the harness does rather than what a reader reconstructs; - **a module nothing measured is reported unmeasured**, not absent. ## Not done here - **No policy reads the configuration.** F27 already records that the three scoring modes produce identical play because `GreedyPolicy` never consults `state.mode`; the same will be true of every aspect. Selection without aspect-aware policies gives configurations we can run and cannot evaluate — CB-RES-0010 §5.4's last bullet is F27 restated. **This is the next pass, and it is a prerequisite for measuring any new module**, not a nicety. - **`end_condition` and `problem_deal` get no kernel path here.** Both their non-default modules are `status: proposed`; T01 makes them nameable and refusable, which is what ADR-0022 asks for and all it asks for. - **`specs/Taxonomy.md` does not yet distinguish aspect from stratum.** ADR-0022 D0 states the distinction; the taxonomy is where it belongs, and without it the two vocabularies will be crossed in exactly the way the taxonomy exists to prevent.