pipelines/APPLY_MANIFEST.md
Author pass. Both repos were READ-ONLY for this work; every artifact below lives under this
scratchpad and nothing in C:/dev/humanity-forgotten or C:/dev/Humanity was modified. The
director executes the steps and owns both commits.
Ruling: docs/spine/DECISIONS_PENDING_JOSH.md twenty-second sitting item 1. Apply spec:
docs/OPEN_DECISIONS_BRIEF_2026-07-22.md ▶ D-RESERVED-COLS ▶ ">> OPERATIVE RECOMMENDATION
(critic-amended)", all six amendments.
---
The brief and its critic describe a 2026-07-22 snapshot. Verified on disk today, **three of the
ruled items are ALREADY LANDED**, in a shape the ruling then corrects:
| Ruled item | Disk state today | What this landing actually does |
|---|---|---|
1. rename T0_Questline_Index.class → questline_class | DONE. The live CSV header reads questline_id,name,questline_class,... and docs/fidelity_baseline.json stub_superseded already declares it. QuestlineIndexRow.h already emits questline_class. | Nothing. Verified, not re-applied. No --emit-baseline is needed for the rename — it was already refreshed. |
3. mint docs/row_struct_overrides.json | EXISTS (513 B, 2026-07-22) — but in the RETIRED per-registry shape {"T0_Worldstate_Variables": {"__aliases__": {"default": "variable_default"}}}, which is exactly the contract collision the critic's amendment 3 rules against. | Replaced with the two-namespace shape. |
| 4. reorder the RESERVED check after alias resolution | ALREADY DONE in the live gen_row_struct.py (resolve_aliases at line 146, RESERVED at 157). The critic's "line ~144 precedes override handling" is stale. | Preserved and documented as load-bearing; not "fixed". |
Consequently the two [DECISION] refusals were already gone before this landing — the
control run (tests/control_regen_delta.py) shows the LIVE toolchain also reports
0 need a decision. The honest description of this landing is not "unblock two registries" but:
**close the contract collision, close the gate hole, close the read-back hole, fix a live crash,
delete a false engine claim, and write the standing law down.**
1. Tools/import_registry_datatable.py::build_csv_string line 86 is a live NameError.
It calls _apply_aliases(header, registry_stem) and registry_stem is never defined or passed.
The aliased import path in that tool has therefore never once executed. Proven, not
inferred: tests/test_importer_shim.py case 1 runs the live file and catches
NameError: name 'registry_stem' is not defined.
2. Tools/import_all_registries.py is a THIRD consumer of the overrides file (line 58, 182,
264, 330, 431, 469) — the BULK driver that imports all the row structs. It is not named in the
ruling. Migrating only the other two tools would leave load_alias_maps() returning {} for
every registry, so the bulk import would write the canon header verbatim against a struct member
named variable_default — a silently-dropped column with a green row count, i.e. the exact defect
the whole ruling exists to prevent, reintroduced by a half-migration. **In scope, not
gold-plating.** Patched (step G2).
---
RESOLVED AT THE FIX ROUND (director ruling, critic-verified; this block originally presented the
choice — the original prose contrasted the brief's example token default_value with the live
on-disk member and was rewritten here as the record after a blind token-sweep briefly made its
comparison sentences self-identical, which is itself recorded honestly):
The alias target is variable_default. The brief's example token was default_value; the
director ruled variable_default at review because (a) it is already the live on-disk member
(WorldstateVariablesRow.h:26) and the emitted alias in registry_import_manifest.json:577 from
the retired __aliases__ era — so the flip is the ZERO-REWORK direction: step G3's regeneration
changes only the alias comment block, and the step-G4 module rebuild + DT_WorldstateVariables
re-import requirement DISAPPEARS (the member never changes); and (b) under the NAMING LAW minted in
this same landing (<qualifier>_class / <qualifier>_default, matching the six declared
instances), variable_default is the conforming shape while default_value is default_<noun>,
the shape the law does not use. The critic verified the flip's full touch-list (one load-bearing
token in row_struct_overrides.json; the rest prose/fixtures) and that variable_default collides
with nothing live. Josh delegated exactly this judgment ("okay with B too if that's better").
---
**Choice: keep the in-memory header rewrite. Reject the generated build/dt_csv/<registry>.csv
artifact.** Loudly flagged because the critic named it as possibly the correct implementation of
step 3.
| In-memory rewrite (CHOSEN) | Generated CSV artifact | |
|---|---|---|
| Canon integrity | Canon CSV never copied; one alias map read by all three tools from one source of truth | Duplicates canon rows into a second location, against spec §1 "the canon repo owns the rows" — a shadow copy that can be edited, imported stale, or diverge |
| New failure class | none | Staleness — the artifact rots when canon changes, needing its own hash-and-compare gate to detect |
| Bypass coverage | the aliased column is the ONLY one that can fail to bind, and the §4.2 read-back catches it on every scripted import | only helps a human drag-dropper who already knows to use the generated file instead of the canon one — the same discipline problem, relocated |
| Cost | zero new artifacts, zero new gates | one emitter + one gate + 62 duplicated CSVs |
Decisive detail from reading the actual code: build_csv_string already materialises a CSV
file — it writes the rewritten string to %TEMP%/hm_import_*.csv for the UE-python bridge to read.
So the "artifact" the variant proposes already exists as an ephemeral, always-fresh one. Making it
durable buys only the drag-drop vector and pays for it with a staleness class and a canon shadow
copy. Recorded in the spec (§6) so it is not re-proposed.
---
C:/dev/humanity-forgotten)| Deliverable | Target | What changed |
|---|---|---|
canon/docs/row_struct_overrides.json | docs/row_struct_overrides.json | Full replacement. Two disjoint top-level sections column_types / column_aliases (amendment 3); the single alias T0_Worldstate_Variables.default → variable_default; the naming law recorded in _naming_law; the UE engine-source citation recorded in _doc. |
canon/docs/REGISTRY_ROW_STRUCT_SPEC.md | docs/REGISTRY_ROW_STRUCT_SPEC.md | Full replacement, v1.0 → v1.1. New §2.1 NAMING LAW (six verified added_columns instances cited; minigame_class explicitly excluded as an enum value). §4 reserved-word hard failure amended to "UNLESS a declared alias exists", plus three new hard failures (alias target reserved/illegal, stale alias, unknown/retired override section). New §4.1 with the UE 5.8 engine-source citation so option E cannot re-open. New §4.2 documenting the three read-back assertions. New §6 recording the rejected generated-CSV variant. §5 now points at the stub_superseded refresh. |
canon/harness/registry_fidelity.py | harness/registry_fidelity.py | Full replacement, built deterministically by scripts/build_registry_fidelity.py (three anchored edits, each asserted unique). Closes the stub_superseded emit-only hole: observed-vs-declared comparison (header AND row count) → column_extension, plus stale-FAIL → baseline_stale. Strictly stricter only — no existing check was weakened, no FINDING_KEYS entry added, no scorecard shape change. |
scripts/apply_canon_surgical.py | harness/enrich.py, docs/DOC_MAP.md | Deterministic surgical edits (tmp + os.replace, anchor-must-be-unique, idempotent). (a) the stale "T0_Questline_Index is a stub" note — live at line 524, not the brief's 451 — becomes "POPULATED (23 rows … questline_class)". (b) the DOC_MAP row for the overrides file, which still described the retired __aliases__ shape. |
C:/dev/Humanity/Humanity)| Deliverable | Target | What changed |
|---|---|---|
game/Tools/gen_row_struct.py | Tools/gen_row_struct.py | Full replacement. New load_overrides() returning (column_types, column_aliases) with load-time validation: retired __aliases__ shape → hard fail with a migration message; unknown/mistyped section → hard fail; alias target that is illegal or itself reserved → hard fail; a type override keyed by an alias TARGET → hard fail. generate() / generate_spine() signatures extended to the two namespaces. Type overrides now key on the RAW canon column name (they were looked up by the aliased name). Stale-declaration checks run against the raw header for BOTH sections. The RESERVED-after-alias order is preserved and documented as load-bearing. The refusal message now names the NAMING LAW. New run summary line listing declared aliases. |
game/Tools/import_registry_datatable.py | Tools/import_registry_datatable.py | Full replacement. Fixes the live NameError (registry_stem now derived from the CSV path by registry_stem_from_path). Reads column_aliases; retired/unknown shapes hard-fail. build_csv_string returns a read-back PROBE contract. Three assertions, all non-zero exit (amendment 4): (a) per-aliased-member column binding via GetDataTableColumnAsString — count-based, order-independent, catches both an unresolvable property (empty array) and a dropped column (N empty strings); (b) row-count mismatch now EXITS (was WARNING + exit 0); (c) fill_ok asserted (was captured and never checked). The false export-name-metadata parenthetical is DELETED and the engine-source citation (DataTable.cpp:811-816 / DataTableUtils.cpp:431-438 + 447-453 / DataTableFunctionLibrary.cpp:314-338) recorded where it stood. |
scripts/apply_import_all_aliases.py | Tools/import_all_registries.py | Surgical patch (~12-line load_alias_maps) — the third alias consumer, migrated to column_aliases with the same legacy/unknown hard fails. Full replacement was rejected: the file is 50 KB and the change is one function. |
---
Two landings, one per repo. Canon first — the game tools read the canon overrides file, so
landing the game side first would leave gen_row_struct.py hard-failing on the old file shape.
S=<scratchpad>/reserved_cols
C=C:/dev/humanity-forgotten
# C1 dry-run the surgical edits (writes nothing)
python $S/scripts/apply_canon_surgical.py --root $C --check # expect exit 0
# C2 place the three replacement files
copy $S/canon/docs/row_struct_overrides.json -> $C/docs/row_struct_overrides.json
copy $S/canon/docs/REGISTRY_ROW_STRUCT_SPEC.md -> $C/docs/REGISTRY_ROW_STRUCT_SPEC.md
copy $S/canon/harness/registry_fidelity.py -> $C/harness/registry_fidelity.py
# C3 apply the two surgical edits
python $S/scripts/apply_canon_surgical.py --root $C # expect exit 0
# C4 prove the new gate tooth on the file that will ship
python $S/tests/test_stub_superseded_tooth.py \
--module $C/harness/registry_fidelity.py # expect exit 0, 6/6
# C5 the full ritual, UNPIPED (memory: gate-verification-gotchas)
python harness/run_gates.py # expect exit 0
--emit-baseline is NOT required by this landing. The rename it would have covered is already
declared, and the patched gate runs GREEN against the live repo as-is (verified, §5 below). Run it
only if run_gates.py reports a stub_superseded finding — which would mean some OTHER registry
edit landed without its refresh, and the new tooth just caught its first one. In that case:
`python harness/registry_fidelity.py --reference _source/00_Tier_0_Master_Indices --candidate
registries --fk-spec docs/fk_spec.json --baseline docs/fidelity_baseline.json --emit-baseline
docs/fidelity_baseline.json` and read the git diff before committing it.
Stage explicitly (memory: shared-tree-never-git-add-dash-A):
`docs/row_struct_overrides.json docs/REGISTRY_ROW_STRUCT_SPEC.md docs/DOC_MAP.md
harness/registry_fidelity.py harness/enrich.py`
⚠ A CONCURRENT LANE IS DIRTY IN THE CANON TREE. At author time git status showed
uncommitted docs/registry_extensions.json, `registries/T0_Theme_Registry [DRAFT v0.1]/
T0_Theme_Registry.csv, docs/5090_ASBUILT_BENCHMARKS.md`,
docs/proposals/MUSIC_COMPOSITION_DOCTRINE.md, and an untracked
docs/pipeline_review/motif_roster_apply_2026-07-29/ — the motif-roster apply. **None of this
landing's five target files is among them**, so there is no collision. But a registry edit is in
flight: if run_gates.py at step C5 goes red on fidelity, read the finding before assuming it is
mine — a T0_Theme_Registry cell/column finding belongs to the other lane and needs ITS
--emit-baseline, not this one's.
G=C:/dev/Humanity/Humanity
# G0 dry-run the surgical patch
python $S/scripts/apply_import_all_aliases.py --root $G --check # expect exit 0
# G1 place the two replacement tools + patch the third
copy $S/game/Tools/gen_row_struct.py -> $G/Tools/gen_row_struct.py
copy $S/game/Tools/import_registry_datatable.py -> $G/Tools/import_registry_datatable.py
python $S/scripts/apply_import_all_aliases.py --root $G # expect exit 0
# G2 offline proof of the importer shim (no editor needed)
python $S/tests/test_importer_shim.py --tool $G/Tools/import_registry_datatable.py
# expect exit 0, 10/10
# G3 regenerate the aliased struct ONLY (see the warning below)
python $G/Tools/gen_row_struct.py --registry T0_Worldstate_Variables --apply
# G4 rebuild the module, then re-import so the renamed member is repopulated
# (DT_WorldstateVariables.uasset already exists, filled under the old member name)
python $G/Tools/import_registry_datatable.py --csv "$C/registries/T0_Worldstate_Variables [ACTIVE v0.1]/Sheet1.csv" \
--struct /Script/Humanity.WorldstateVariablesRow --dest /Game/Data/Registries/DT_WorldstateVariables
# the three read-back assertions now gate this; a non-zero exit is real
⚠ Do NOT run gen_row_struct.py --all --apply as part of this landing. The registry tree has
grown to 62 CSVs against 48 generated headers: 19 headers are stale and 23 registries have no
header at all. That is PRE-EXISTING regeneration debt — proved by tests/control_regen_delta.py,
which runs the OLD and NEW toolchains over identical inputs and shows the identical debt under
both. --all --apply would sweep ~42 unrelated header changes into this commit. The debt is real
and worth its own task; it is not this ruling's.
---
All runs are read-only against the live repos or execute inside verify_out/sandbox
(a full copy of the canon repo: 62 registries, 684 docs, 257 harness files).
| # | What | Result | Exit |
|---|---|---|---|
| V1 | tests/test_stub_superseded_tooth.py — patched gate, 6 cases | 6/6 ok: declared==live PASS; header mismatch FAIL; stale declaration FAIL; row-count mismatch FAIL; undeclared stub FAIL; non-stub registry unaffected PASS | 0 |
| V2 | Same test vs the UNPATCHED live gate (negative control) | 4 of 6 go GREEN — B/C/D/E all pass on a broken gate. The hole is exactly as the critic described. | 1 |
| V3 | Patched gate vs the LIVE repo, read-only, no --out/--emit-baseline | Hard gate: PASS — all 5 declared stub_superseded entries match live headers + row counts, questline_class included | 0 |
| V4 | harness/stress_test_registry.py in the sandbox with the patched gate | 79/79 cases behaved. "Ruler has teeth: True" | 0 |
| V5 | harness/run_gates.py --only fidelity,registry_extensions,stress,lint in the sandbox (post-surgical-edits) | PARTIAL-PASS; fidelity PASS, stress PASS, lint PASS, registry_extensions PASS | 0 |
| V6 | tests/verify_gen_row_struct.py — 16 cases against real canon | 16/16 ok (detail below) | 0 |
| V7 | tests/control_regen_delta.py — OLD vs NEW toolchain, identical inputs | Both emit 62 structs, 0 decisions. Exactly ONE header differs: WorldstateVariablesRow.h — at author time (package token default_value) the diff was the alias comment block + the member rename; AFTER the ruled variable_default flip the member matches live and the diff is the alias comment block ONLY. 19 stale + 23 absent headers confirmed pre-existing under BOTH. | 0 |
| V8 | tests/test_importer_shim.py — 10 cases, offline | 10/10 ok (detail below) | 0 |
| V9 | scripts/apply_canon_surgical.py --check against the live repo | both anchors unique, would apply | 0 |
| V10 | Same script APPLY + re-APPLY on the sandbox | APPLIED, then ALREADY-APPLIED (idempotent) | 0, 0 |
| V11 | scripts/apply_import_all_aliases.py --check live / APPLY + re-APPLY sandbox / AST parse | anchor unique + preconditions met; APPLIED; ALREADY-APPLIED; AST OK | 0 |
| V12 | Positive control on the deleted phrase | zero occurrences across all deliverables; control confirms 1 occurrence in the live file today | — |
V6 detail (the four the ruling names, plus the law's tooth):
--all --apply exit 0 · zero [DECISION] refusals · QuestlineIndexRow emits questline_class
and carries no alias comment (renamed, not aliased) · WorldstateVariablesRow emits
FString variable_default and the alias comment block · canon CSV column 4 still reads default
byte-exact · a NEW bare-keyword column (register) still refuses, and the refusal names the
NAMING LAW, and no struct is emitted for it · a STALE alias hard-fails (exit 2) · the RETIRED
__aliases__ shape hard-fails (exit 2) · a mistyped section name hard-fails (exit 2) · only the
aliased struct differs from the old toolchain · a second --all --apply writes 0 (deterministic).
V8 detail: the live file's NameError reproduced · header rewritten default → variable_default
with every other cell untouched · row values byte-identical to canon · probe contract computed
correctly (2 non-empty of 3 rows) · stale alias exits non-zero · retired shape exits non-zero ·
banned phrase absent · engine citation present · all three assertions present with no surviving
WARNING: source rows.
NOT verified here, and it cannot be: the three read-back assertions require a live UE editor
with NeoStack MCP on :9315. Step G4 is their first real execution. GetDataTableColumnAsString was
confirmed to exist and behave as assumed by reading UE 5.8 source
(DataTableFunctionLibrary.cpp:166-174 → DataTableUtils.cpp:468-491), not by running it.
---
1. Tools/import_all_registries.py patched though the ruling does not name it. Rationale in
§0; without it the bulk vector silently drops the aliased column. Reviewable as ~12 lines.
2. Extra hard failures added to gen_row_struct.py beyond the ruled reorder: stale-alias,
alias-target-reserved, alias-target-illegal, unknown-section, retired-shape, type-override-keyed-
by-alias-target. All are the "stale declaration is a silent no-op" class spec §4 already
hard-fails on, applied to the new namespace. **Strictly stricter; none can pass something that
previously failed.**
3. The fidelity tooth compares candidate_rows as well as candidate_header. The ruling says
"the same comparison the other classes have", and added_rows is compared, so row count is in
scope. Consequence the director should know: any future row add to a stub-superseded registry
(T0_Boss_Encounter_Registry 281 rows, T0_Weapon_Registry 72, T0_Familiar_Registry 22,
T0_School_Registry 13, T0_Questline_Index 23) now requires an --emit-baseline refresh in
the same commit. That IS the declared CLAUDE.md discipline, but those five registries have been
exempt from it by accident until now. Green today (V3/V5).
4. **harness/check_registry_extensions.py (rule I1) trusts the declared stub_superseded
candidate_header as ground truth.** It had no way to know the declaration was unpinned. The new
tooth protects that gate's premise too — noted in the code comment.
5. A self-catch worth recording. My own first drafts of the spec and the importer docstring
QUOTED the deleted export-name phrase while documenting its deletion, so a repo-wide grep would
still have returned hits. Caught by test case 6, reworded, positive-controlled to zero (V12).
Same class as the Gate-30 catch in the v1.3 fold: the banned token must not survive in the prose
that retires it.
6. The brief's enrich.py:451 citation is stale — the live line is 524. Recorded so the next
reader does not think the note was already fixed.
Citations in this manifest follow amendment 6: ws_writes is on T0_Boss_Encounter_Registry, and
the qualified-column convention has six verified added_columns instances.
---
reserved_cols/ APPLY_MANIFEST.md <- this file canon/docs/row_struct_overrides.json <- full replacement canon/docs/REGISTRY_ROW_STRUCT_SPEC.md <- full replacement (v1.1) canon/harness/registry_fidelity.py <- full replacement (deterministically built) game/Tools/gen_row_struct.py <- full replacement game/Tools/import_registry_datatable.py <- full replacement scripts/apply_canon_surgical.py <- enrich.py + DOC_MAP surgical apply scripts/apply_import_all_aliases.py <- import_all_registries.py surgical apply scripts/build_registry_fidelity.py <- regenerates the gate deliverable from source tests/test_stub_superseded_tooth.py <- REQUIRED fidelity test (a/b/c + 3 guards) tests/verify_gen_row_struct.py <- generator verification (16 cases) tests/control_regen_delta.py <- OLD-vs-NEW toolchain control tests/test_importer_shim.py <- offline importer proof (10 cases) verify_out/ <- logs, sandbox, control outputs