APPLY_MANIFEST.md

pipelines/APPLY_MANIFEST.md

D-RESERVED-COLS — APPLY MANIFEST (Option D SPLIT, critic-amended; ruled 2026-07-29)

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.

---

0. READ THIS FIRST — the disk has moved since the brief was written

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 itemDisk state todayWhat this landing actually does
1. rename T0_Questline_Index.classquestline_classDONE. 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.jsonEXISTS (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 resolutionALREADY 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.**

Two live defects found that the brief did not know about — both fixed here

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).

---

1. FLAG FOR THE DIRECTOR — one judgement call, one token, zero rework either way

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").

---

2. THE SHIM CHOICE (amendment 5) — IN-MEMORY REWRITE, chosen after reading the code

**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 integrityCanon CSV never copied; one alias map read by all three tools from one source of truthDuplicates 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 classnoneStaleness — the artifact rots when canon changes, needing its own hash-and-compare gate to detect
Bypass coveragethe aliased column is the ONLY one that can fail to bind, and the §4.2 read-back catches it on every scripted importonly helps a human drag-dropper who already knows to use the generated file instead of the canon one — the same discipline problem, relocated
Costzero new artifacts, zero new gatesone 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.

---

3. FILE-BY-FILE — what changed

Canon repo (C:/dev/humanity-forgotten)

DeliverableTargetWhat changed
canon/docs/row_struct_overrides.jsondocs/row_struct_overrides.jsonFull 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.mddocs/REGISTRY_ROW_STRUCT_SPEC.mdFull 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.pyharness/registry_fidelity.pyFull 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.pyharness/enrich.py, docs/DOC_MAP.mdDeterministic 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.

Game repo (C:/dev/Humanity/Humanity)

DeliverableTargetWhat changed
game/Tools/gen_row_struct.pyTools/gen_row_struct.pyFull 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.pyTools/import_registry_datatable.pyFull 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.pyTools/import_all_registries.pySurgical 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.

---

4. EXECUTION ORDER

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.

Canon landing

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.

Game landing

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.

---

5. VERIFICATION RUN — results and exit codes

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).

#WhatResultExit
V1tests/test_stub_superseded_tooth.py — patched gate, 6 cases6/6 ok: declared==live PASS; header mismatch FAIL; stale declaration FAIL; row-count mismatch FAIL; undeclared stub FAIL; non-stub registry unaffected PASS0
V2Same 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
V3Patched gate vs the LIVE repo, read-only, no --out/--emit-baselineHard gate: PASS — all 5 declared stub_superseded entries match live headers + row counts, questline_class included0
V4harness/stress_test_registry.py in the sandbox with the patched gate79/79 cases behaved. "Ruler has teeth: True"0
V5harness/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 PASS0
V6tests/verify_gen_row_struct.py — 16 cases against real canon16/16 ok (detail below)0
V7tests/control_regen_delta.py — OLD vs NEW toolchain, identical inputsBoth 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
V8tests/test_importer_shim.py — 10 cases, offline10/10 ok (detail below)0
V9scripts/apply_canon_surgical.py --check against the live repoboth anchors unique, would apply0
V10Same script APPLY + re-APPLY on the sandboxAPPLIED, then ALREADY-APPLIED (idempotent)0, 0
V11scripts/apply_import_all_aliases.py --check live / APPLY + re-APPLY sandbox / AST parseanchor unique + preconditions met; APPLIED; ALREADY-APPLIED; AST OK0
V12Positive control on the deleted phrasezero 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-174DataTableUtils.cpp:468-491), not by running it.

---

6. Beyond spec — flagged loudly

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.

---

7. Deliverable tree

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

Generated by harness/site/structure_site.py — the URL path is the repo path. review root