From 02bbf62eb18c090ade3fbc8f91b8cf1c08c48d5a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 15 Sep 2026 23:27:03 +0200 Subject: [PATCH] Map the library extraction cuts The Imagination pass maps plan step 15 L0 against d83534c: - Cut 0: base captures (469 bin tests, and a world.cc written at base). - Cut 1: a single Ghostlight commit that moves world/ into a library crate with the exact public boundary (compile_fail doc tests prove the sealed items) and a test-support feature for cross-boundary fixtures. - Cut 2: the gamecult-ops cargo invocations. The map was probed with scratch cargo checks on the Windows workstation, so it proves Windows only. Six operator questions are open. No code has moved. Co-Authored-By: Claude Opus 5 --- .../ghostlight-library-extraction-cut.md | 540 ++++++++++++++++++ 1 file changed, 540 insertions(+) create mode 100644 docs/architecture/ghostlight-library-extraction-cut.md diff --git a/docs/architecture/ghostlight-library-extraction-cut.md b/docs/architecture/ghostlight-library-extraction-cut.md new file mode 100644 index 0000000..9867769 --- /dev/null +++ b/docs/architecture/ghostlight-library-extraction-cut.md @@ -0,0 +1,540 @@ +# Ghostlight library extraction — cut map (plan step 15, L0) + +Status: cut map. Ends are owned by `ghostlight-library-extraction.md`; this +document owns the means. Written 2026-09-15 against `d83534c` on +`codex/ghostlight-dungeon-mvp`. Nothing landed. + +Rulings (operator, 2026-09-15, carried from the target): +- Dungeon is a consumer of the library; no Dungeon concept, type, route, or + dependency enters it. +- Kernel first; lenses, detail rules, evidence binding and Draft sessions + are L1+. +- L0 moves code and changes no behavior, schema string, digest preimage, or + store layout. +- Delete before adding; no re-export facade preserving old paths unless the + map names the external contract it protects. + +Open: Q1-1 through Q1-6 below. Q2-1 (gamecult-ops) waits on Q1-6. +Follow-ups outside this migration: see "Findings not assigned to a cut". + +## What the probes established (scratch worktree at `d83534c`, removed) + +Every mechanism claim below comes from one of these runs. Logs are in the +session scratchpad (`probe1*-check.log`, `probe3*-visibility.log`, +`base-test-list.log`); they are not repo artifacts. + +- **Base test census** (`cargo test -p ghostlight-dungeon --bin + ghostlight-dungeon -- --list`, repo target dir): 469 unit tests in the bin, + of which 420 are under `world::` (action 34, clock_tests 45, consumer 30, + controllers 81, cover 18, custody_tests 17, elaboration 14, journal 24, + knowledge_tests 21, mailbox 15, patch 34, sdk_inference 23, + soul_knowledge_tests 9, tests 32, vault 6, witness_tests 17) and 49 are + Dungeon's own (app_session 7, eve 5, heimdall 5, idunn_health 4, mesh 4, + runtime 24). `tests/build_provenance.rs` has 1. `ghostlight-persona- + projection` has 13 unit, 0 doc. Two tests are `#[ignore]`: + `world::controllers::tests::real_codex_connector_cognition_modes_commit_speech` + and `runtime::tests::live_smoke_seeds_then_ticks_a_world_against_the_connector`. +- **Base warning**: `mod.rs:28` re-exports nine names nobody in the crate + uses (`ControllerOpenError`, `NarrativeCapture`, `NarrativeDecision`, + `NarrativePending`, `OperationalCapture`, `OperationalDecision`, + `OperationalPending`, `SourceRange`, `TranslationGapSummary`). +- **The move compiles.** Moving `src/world/*` to a new lib crate + (`mod.rs` → `lib.rs`), rewriting `crate::world::` → `crate::` inside it and + `crate::world::` → `::` in Dungeon, and moving + `VerifiedPrincipalEvidence` into the library root: `cargo check` on the + library, the library's tests, the Dungeon bin, and the Dungeon tests all + pass (probe 1d). Two mechanical faults were needed: the 94 `pub(super)` + sites in the former `mod.rs` are illegal at a crate root (become + `pub(crate)`), and `consumer.rs:62` needs `serde_bytes` in the library + manifest. +- **What Dungeon needs public** (probe 3, four compiler rounds starting from + every item `pub(crate)`): 54 production items, 22 test-only items, + 38 fields on 10 structs, 36 methods, the 3 opaque IDs + `AffordanceId`/`CommandId`/`SubjectId`, and the `FictionalMinutes(u64)` + tuple constructor (one Dungeon test). Listed under Cut 1 "Adds". Nothing + in the list is sealed: `WorldKernel`, `WorldState`, `reduce`, + `apply_effect`, `CommandEnvelope`, `AuthenticatedCaller`, the journal, + `*::issue()` and `ScopeDigest::fixture` never appear. Under the narrowed + library `cargo check -p ` emits 646 dead-code warnings: a second, + independent enumeration of the same boundary (an item used only by Dungeon + is dead inside the library until it is `pub`). +- **Cross-boundary test fixtures**: Dungeon's `runtime.rs` tests need + exactly the `#[cfg(test)]` re-export block at `mod.rs:66-72` plus + `fixture_controller_opportunities` (`mod.rs:490`), + `TracingInferencePort` (`controllers.rs:278`), + `InferenceFault::fixture_integrity_violation` / `fixture_recovery_required` + (`controllers.rs:225,234`), and the ID `issue()` impls those fixtures call + (`mod.rs:200-226`, `ScopeDigest::fixture` `mod.rs:448`). Gating all of + them behind `#[cfg(any(test, feature = "test-support"))]` with the feature + enabled only from Dungeon's `[dev-dependencies]` compiles (probe 1d). No + Dungeon test reaches a sealed constructor: the only `::fixture(` calls + outside `world/` are Heimdall's (`runtime.rs:2552,2611,4126`, + `app_session.rs:560`) and `VerifiedPrincipalEvidence::fixture` + (`runtime.rs:4313`). +- **Compile-fail proof**: eight `compile_fail` doc-tests on the library root + (`use ::WorldKernel`, `reduce`, `WorldState`, `CommandEnvelope`, + `AuthenticatedCaller`, `journal::WorldJournal`, `SubjectId::issue()`, + `fixture_controller_opportunities` without the feature) all pass under + `cargo test -p --doc` (probe 4). Doc-tests compile as an external + crate, which is exactly the boundary; no `trybuild`, no scratch crate. +- **Negative-grep collisions checked** in `src/world/`: `axum`, + `tower_http`, `jwt`/`jsonwebtoken`, `heimdall`, `eve::`, `idunn_health`, + `Session Zero` have zero legitimate hits. `Dungeon` collides with the vault + fixture path `Spoilers/Dungeons/Provenance.md` (`vault.rs:489,581`): use + `\bDungeon\b`. `player` collides with `tests::player()` + (`consumer.rs:966`): do not publish it. `app_session` has 5 hits, all the + principal seam, and must reach 0. +- **Line endings**: index and working tree are LF (`git ls-files --eol`); + `core.autocrlf=true` rewrote a fresh worktree checkout to CRLF. Hands + edits in `F:\Projects\Ghostlight` itself, not a new worktree, or verifies + `--eol` after the move. `sdk_inference.rs:1831` normalizes `\r\n`. +- **Build host**: every probe ran on the Windows workstation with + `cargo check`/`cargo test --doc`; it proves the crate graph and visibility + on Windows only. The Linux proof is Idunn's recipe run on Yggdrasil. + +## Cut 0. Captures + +- **Repo/branch:** Ghostlight `codex/ghostlight-dungeon-mvp` at `d83534c`. + No commit; output lives in the session scratchpad. +- **First:** + - Test census: `cargo test -p ghostlight-dungeon --bin ghostlight-dungeon + -- --list > base-tests.txt` (PowerShell `>` writes UTF-8 BOM + CRLF; + compare post-cut with the same redirection and `Compare-Object` on + trimmed lines after stripping the `world::` prefix). Expected 469. + - A `world.cc` written at base. There is no existing store on disk + (`F:\Projects\Ghostlight-smoke\ghostlight` holds no `world.cc`), so + produce one: in a scratch copy at `d83534c`, add an ignored test beside + `mailbox.rs:966 fixture()` that opens `WorldMailbox::open(/world.cc)`, + creates through `create_fixture`, approves, activates, and ticks three + times through `submit_clock`; record `snapshot().revision`, the + `state_digest`, and the last `commit_digest` from `operator_log` to + `capture.txt`. Copy `world.cc` and `capture.txt` to the scratchpad. + After Cut 1 the same test body, in the library's `mailbox` tests, + reopens that file and asserts the three values are equal. The + `controller-work.cc` layout is pinned by the untouched + `CONTROLLER_WORK_SCHEMA`/`CONTROLLER_WORK_ROW` constants + (`controllers.rs:75-76`) and the existing v7/v8 refusal tests + (`controllers.rs:6487,10654`); no separate capture. + - `Get-ChildItem target -Recurse | Measure-Object Length -Sum`: 7.5 GiB at + the start of this pass. + +## Cut 1. Extract the library crate + +- **Repo/branch:** Ghostlight `codex/ghostlight-dungeon-mvp` from + `d83534c`. One commit; the crate graph does not build half-moved. Depends + on Cut 0 and rulings Q1-1..Q1-5. +- **First:** Cut 0 captures in hand; `git status` clean. +- **Deletes first:** + - `crates/ghostlight-dungeon/src/world/mod.rs:28-32` — the nine unused + re-exported names above (leave `CellRun`, `ConnectorBinding`, + `ControllerError`, `ControllerModels`, `ControllerPendingReason`, + `ControllerRunner`, `ControllerWorkCustody`, `NarrativeRun`, + `OperationalRun`, `SubmissionDisposition`, `open_controller_work`, + `open_inference`). + - `crates/ghostlight-dungeon/src/world/mod.rs:61-72` — the `#[cfg(test)]` + re-export block (12 lines); it returns as the gated `pub use` under + Adds. + - `crates/ghostlight-dungeon/src/app_session.rs:54-91` — the + `VerifiedPrincipalEvidence` struct, accessors, and `fixture` (38 lines). + - `crates/ghostlight-dungeon/src/main.rs:7` — `mod world;`. + - `crates/ghostlight-dungeon/Cargo.toml:27` — the direct + `ghostlight-persona-projection` dependency (Dungeon no longer names the + crate; `controllers.rs:35` is the only importer and it moves). +- **Keeps and moves:** + - `git mv crates/ghostlight-dungeon/src/world/{action,clock,consumer, + controllers,cover,elaboration,journal,mailbox,patch,sdk_inference, + tool_schema,vault}.rs crates//src/` and + `git mv .../world/mod.rs crates//src/lib.rs`. Verify with + `git diff --cached -M --stat` that all 13 show as renames. + - Inside the moved files: `crate::world::` → `crate::` (199 sites; + `super::super::` paths need no change, tests are one module below the + root as before); `pub(super)` → `pub(crate)` in `lib.rs` only (94 + sites; the submodules keep theirs); + `crate::app_session::VerifiedPrincipalEvidence` → `crate::VerifiedPrincipalEvidence` + (`mailbox.rs:10`, `controllers.rs:10714,10790,10988,11473`). + - `build.rs`, `tests/build_provenance.rs`, `runtime.rs:375` (web/dist) + stay in Dungeon; their `CARGO_MANIFEST_DIR/../..` still resolves. + `sdk_inference.rs:1900,1983` move with the file and still resolve from + `crates/` (same depth). +- **Adds:** + - `Cargo.toml` (workspace): member `crates/`. + - `crates//Cargo.toml`: package ``; `[features] test-support = []`; + dependencies `async-trait`, `chrono`, `codex-connector`, `cultcache-rs`, + `ghostlight-persona-projection = { path = "../ghostlight-persona-projection" }`, + `rmp-serde`, `serde`, `serde_bytes = "0.11"`, `serde_json`, `sha2`, + `thiserror`, `tokio`, `tracing`, `uuid`, `zeroize` (all `.workspace = + true` except `serde_bytes`, which the workspace does not declare); dev + `tempfile = "3"`. Every one is already in `Cargo.lock`; no new package. + - `crates/ghostlight-dungeon/Cargo.toml`: ` = { path = "../" }` + in dependencies; ` = { path = "../", features = ["test-support"] }` + in dev-dependencies. + - `Cargo.lock`: the new `[[package]] name = ""` entry. Commit it; + Idunn builds `--locked`. + - `crates//src/lib.rs`, root: `pub struct VerifiedPrincipalEvidence + { account_subject_hash: String, valid_until: DateTime }` with `pub fn + new`, `pub fn account_subject_hash`, `pub fn valid_until`, and + `#[cfg(test)] fn fixture` delegating to `new` (per Q1-3 A). Doc comment: + the consumer verifies; the library bounds by `valid_until` at + `mailbox.rs:231`. + - `crates//src/lib.rs`, the public boundary. Production `pub use`: + `clock::{FictionalMinutes, TickMinutes}`; + `consumer::{CONSUMER_BODY_LIMIT, CONSUMER_CREDENTIALS_ENVIRONMENT, + CONSUMER_PATCH_SCHEMA, CONSUMER_RECEIPT_SCHEMA, ConsumerRegistry, + admit_document, encode_receipt}`; + `controllers::{CellRun, ConnectorBinding, ControllerError, + ControllerModels, ControllerPendingReason, ControllerRunner, + ControllerWorkCustody, NarrativeRun, OperationalRun, + SubmissionDisposition, open_controller_work, open_inference}`; + `cover::{AgencyGraph, Cell, Cover, CoverBudget, TickIndex, derive_cover}`; + `elaboration::{SeedOutcome, select_row}`; + `mailbox::{ConsumerPort, ControllerPort, MailboxError, SeedPort, WorldMailbox}`; + `patch::{JurisdictionKey, Statement}`; + `sdk_inference::{DEFAULT_SDK_MODEL_PREFIX, SdkBinding}`; + `vault::VaultEvidenceSource`. Root items made `pub`: `STATE_SCHEMA`, + `state_schema_compatibility_tag`, `AffordanceId`, `CommandId`, + `SubjectId` (the `opaque_uuid!` macro at `mod.rs:98-106` emits + `pub(crate) struct`; make the macro take a visibility or emit `pub` for + these three only), `PrincipalId`, `WorldPhase`, `SubjectKind`, + `ControllerMode`, `CreateWorldIntent`, `CreateJurisdictionIntent`, + `DecisionOpportunity`, `DecisionInvocation`, `PrincipalCommandIntent`, + `CommandBody`, `WorldSnapshot`, `OperatorEvent`, `CommitReceipt`, + `SubmitReceipt`, `KernelError`, `ScaleDeficitRow`, and the snapshot + row types Dungeon reads through `WorldSnapshot` fields + (`SubjectSnapshot`, `PlaceSnapshot`, `AffordanceSnapshot`, and whichever + others the compiler names; probe 3 stopped at the `WorldSnapshot` field + layer). `GroupedRun` (`controllers.rs`) is reached through + `ControllerRunner` return values and needs `pub` with its five fields. + - Gated `#[cfg(any(test, feature = "test-support"))] pub use + controllers::{ControllerWork, ControllerWorkLookup, ControllerWorkStore, + ControllerWorkStoreError, ControllerWorkWrite, InferenceEvent, + InferenceFault, InferenceOutput, InferencePort, InferencePurpose, + InferenceRequest, PreparedInference, TracingInferencePort, + fixture_inference_events, fixture_inference_output, + fixture_prepared_inference}` and the same gate on + `fixture_controller_opportunities` and on the seven `#[cfg(test)]` + sites it and `TracingInferencePort` depend on (`mod.rs:200,207,214,221` + ID issuance impls, `mod.rs:448` `ScopeDigest::fixture`, + `controllers.rs:225,234,277,283,344,377,412,423`). `InferencePort` and + `ControllerWorkStore` are ports; if Q1-4 rules them production API, + move those two traits and their request/output types to the ungated + list and leave only the `fixture_*` functions and `TracingInferencePort` + gated. + - Fields that become `pub` (38, from probe 3): `CommitReceipt.{command_id, + commit_digest, resulting_revision, resulting_state_digest}`, + `Constituent.subject`, `Cover.{cells, oversubscribed, tick}`, + `CoverBudget.{cells, constituent_cap, urgency_slots}`, + `DecisionOpportunity.{controller_mode, world_id}`, `GroupedRun.{cell, + needs, pending, resolution, submissions}`, `OperatorEvent.{revision, + speech}`, `PreparedInference.purpose`, `ScaleDeficitRow.{deficit, + jurisdiction, kind, target}`, `WorldSnapshot.{affordances, + draft_approvals, now, opportunities, owner, phase, places, + required_approvers, revision, scale_deficit, subjects, title, world_id}`, + plus `FictionalMinutes.0` or a `pub const fn new(u64)` for + `runtime.rs:3579`. Fields of the row structs reached through + `WorldSnapshot` follow in the next compiler round. + - Methods that become `pub` (36, from probe 3, by definition site): + `WorldMailbox::{open, create, submit_principal, submit_clock, + agency_graph, operator_log, snapshot}` (`mailbox.rs:93,105,226,355,448, + 461,474`), `ConsumerPort::new` (`:552`), `SeedPort::new` (`:594`), + `ElaborationPort::new` (`:660`), `ControllerPort::{new, snapshot, + submit_controller}` (`:692,696,721`), `ControllerRunner::{open, + elaborator, seeder, custody_probe, run_narrative, run_operational, + run_cell}` (`controllers.rs:2691,2712,2728,2744,2748,2838,3057`), + the receipt/pending accessors at `controllers.rs:2397,2413,2421,2453, + 2457,2461,2465,2469,2515,2519,2523,2543,2591,2595` (`persona_turn`, + `re_lowering`, `into_parts`, `subject`, `bound_scope_digest`, + `fresh_scope_digest`, `gap`, `mode`, `reason`, `persona_prose`), + `TracingInferencePort::new` (`:285`, gated), `sweep` + (`elaboration.rs:607,1746`), `name` (`elaboration.rs:1511`), + `Cover::{singletons, groups, validated}` (`cover.rs:163,170,198`), + `ConsumerRegistry::{empty, from_secret_file}` (`consumer.rs:168,176`), + `TickMinutes::{new, minutes}` (`clock.rs:45,51`), `CommandId::new` + (`mod.rs:122`), `PrincipalId::new` (`mod.rs:239`), `Statement::new` + (`patch.rs:281`). + - `crates//src/lib.rs` crate docs: the eight `compile_fail` + doc-tests from probe 4, each pinned to its error code (`E0603` for the + six private paths, `E0599` for `SubjectId::issue()`, unpinned for the + feature-gated fixture whose code differs by feature). + - `crates/ghostlight-dungeon/src/runtime.rs` (tests): a ~25-line copy of + `soul_no_credential_name_appears_in_the_ports_own_source` scanning + `runtime.rs` only, because the library test cannot read another + crate's source (see per-file `sdk_inference.rs:1824-1828`). + - `crates/ghostlight-dungeon/src/app_session.rs` (tests): one test + asserting `VerifiedPrincipalEvidence::new(` occurs exactly once in the + crate's production source, at `account_for_cookie` (same shape as the + `from_secret_file` count at `sdk_inference.rs:1860`). This is the + single-minter rule the deleted private fields used to enforce. +- **Per-file changes** (against `d83534c`): + - `Cargo.toml:3-5`: add member. + - `crates/ghostlight-dungeon/Cargo.toml:27`: replace with the library + path dep; `:43-45`: add the dev-dep with `test-support`. + - `crates/ghostlight-dungeon/src/main.rs:7`: delete. + - `crates/ghostlight-dungeon/src/runtime.rs:3-22`: split the `use crate::{}` + block; `world::{…}` becomes `use ::{…}` (probe edit); `:4`: import + `VerifiedPrincipalEvidence` from ``, not `app_session`. + `:518,519,535,1084,1088,1092,1201,1325,1621,2492,2493,2581,2582,3081, + 3501,3568,3658,3666,3675,3677,3777,3782,4267`: `crate::world::` → + `::`. `:4313`: `::fixture(` → `::new(`. `:987,1040,1189,1431,1777, + 2110`: type paths follow the import. `:504,2673` doc comments name + `world::consumer`; reword to the crate. + - `crates/ghostlight-dungeon/src/eve.rs:3-6` and `:182,335,338,354,386, + 405,449,494`: same rewrite. + - `crates/ghostlight-dungeon/src/mesh.rs:6-9`: same. + - `crates/ghostlight-dungeon/src/idunn_health.rs:8`: same. + - `crates/ghostlight-dungeon/src/app_session.rs:54-91`: delete; `:6`: add + `use ::VerifiedPrincipalEvidence;`; `:232-235`: struct literal → + `VerifiedPrincipalEvidence::new(session.account_subject_hash.clone(), + session.access_expires_at)`. + - `crates//src/sdk_inference.rs:1824-1828`: the scanned file list + becomes `["sdk_inference.rs", "controllers.rs"]` (paths are now + crate-root relative); `:1860` `sources[1]` still indexes + `controllers.rs`. The `runtime.rs` half moves to Dungeon (Adds). + - `deployment/idunn/recipe.toml:14-17`: `test-world-owner` becomes two + steps — `test-world-library`: `["cargo","test","--locked","-p",""]` + (runs the 420 unit tests and the 8 doc-tests) and `test-dungeon`: + the existing `-p ghostlight-dungeon --bin ghostlight-dungeon` line. + `:51-53`: acceptance argv becomes `-p --lib + controllers::tests::real_codex_connector_cognition_modes_commit_speech` + (the `world::` prefix is gone; `--exact` still matches). + - `package.json:9` (`dungeon:run`): unchanged; `:7-8` use `--workspace` + and pick the crate up. + - `notes/local-live-smoke.md:64`: unchanged; the ignored smoke stays in + `runtime.rs:3697`. + - Docs that name the old path (owner in brackets): `README.md:165-166` + [Hands] add the library line; `docs/public-architecture/index.md:46` + [Hands]; `docs/architecture/ghostlight-session-zero.md:21-23` [Hands] + rewrite "Source does not yet match" as the current rule; + `docs/architecture/ghostlight-world-ontology.md:939-941` [Hands] the + build-budget sentence now names the library crate; + `notes/ghostlight-current-system-map.md:18,28,289` [Mind steward]; + `notes/fresh-workspace-handoff.md:284-286` [steward]; + `state/map.yaml:57,389-390` [steward]. +- **Authority map:** + - Owner: the library crate `` owns the world kernel, its ontology, + reducer, journal, mailbox, controllers, elaboration, cover, clock, + consumer admission, inference ports, and evidence sources. Its public + surface is the list under Adds and nothing else. + - Inputs: `VerifiedPrincipalEvidence` values Dungeon constructs from + `AppSessionOwner::account_for_cookie`; `CreateWorldIntent`, + `PrincipalCommandIntent`, `DecisionInvocation`; consumer patch bytes via + `admit_document`; store paths via `WorldMailbox::open` and + `open_controller_work`; connector and SDK bindings via `open_inference`. + - Outputs: immutable `WorldSnapshot`, `OperatorEvent`, `AgencyGraph`, + typed receipts (`SubmitReceipt`, `CommitReceipt`, `CreationReceipt` + through `MailboxError`/`KernelError`), controller runs, cover + partitions, encoded consumer receipts. + - Derived state: Dungeon's Eve projection, mesh documents, Idunn health + (which reads `STATE_SCHEMA` and `state_schema_compatibility_tag` and + derives, never restates), HTTP responses. + - Forbidden writers: any path in Dungeon naming `WorldKernel`, + `WorldState`, `reduce`, `apply_effect`, `CommandEnvelope`, + `AuthenticatedCaller`, the journal, or an `issue()` — enforced by + visibility, proven by the doc-tests. Any second minter of + `VerifiedPrincipalEvidence` — enforced by the Dungeon count test. + `test-support` in a production build — checked below. + - Shared paths: Dungeon production and Dungeon tests reach the kernel + through the same `pub` items; tests additionally see the gated fixture + set. Library-internal tests keep `pub(crate)`/`super::` access as + today. Consumers other than Dungeon: none yet; Epiphany consumes only + `ghostlight-persona-projection`, which is unchanged. + - Deletion line: `mod world;`, the nine unused re-exports, the + `#[cfg(test)]` re-export block, and Dungeon's private principal struct + are gone before the first `pub` is written. +- **Verification** (Windows workstation; proves Windows only): + - builds: `cargo check -p ` (no new warnings; the 396–646 dead-code + warnings of an under-opened boundary must be 0), + `cargo check -p ghostlight-dungeon --bin ghostlight-dungeon`, + `cargo check -p ghostlight-dungeon --tests`, + `cargo build --locked -p ghostlight-dungeon --bin ghostlight-dungeon` + (proves `Cargo.lock`). Expected target-dir delta: one extra lib crate + in debug; the scratch probe's check-only target was 2.3 GiB from empty, + so expect well under 1 GiB incremental on the repo's 7.5 GiB. + - tests: `cargo test -p ` = 420 unit + 8 doc (the 420 names equal + the base `world::` list with the prefix stripped); + `cargo test -p ghostlight-dungeon --bin ghostlight-dungeon` = 49; + `cargo test -p ghostlight-dungeon --test build_provenance` = 1; + `cargo test -p ghostlight-persona-projection` = 13. Ignored counts + unchanged (1 + 1). The reopen test from Cut 0 pins invariant 2 + (`world.cc` written at base reopens with equal revision and digests). + The doc-tests pin invariant 1. The `VerifiedPrincipalEvidence::new` + count pins the single minter. The split credential-name test pins + "no credential name in the port's source" on both sides. + - negative: + `rg -n 'crate::world::' crates` = 0; + `rg -n '^mod world;' crates` = 0; + `rg -n 'app_session|heimdall|eve::|idunn_health|axum|tower_http|jsonwebtoken|\bDungeon\b|Session Zero' crates//src` = 0 + (collisions checked: `Dungeons` in `vault.rs` is excluded by `\b`; + do not add `player`); + `rg -n 'pub\(super\)' crates//src/lib.rs` = 0; + `rg -n 'VerifiedPrincipalEvidence::new\(' crates/ghostlight-dungeon/src` + = 1 outside `#[cfg(test)]`; + `cargo check -p ghostlight-dungeon --bin ghostlight-dungeon -v 2>&1 | rg 'feature="test-support"'` + = 0 (resolver 2 keeps dev-dep features out of the bin build); + `cargo tree -p ghostlight-dungeon -i axum` shows no path through ``; + `cargo tree -p ` names no `axum`, `tower-http`, `jsonwebtoken`, + `cultnet-rs`, `cultmesh-rs`, `reqwest`, `aes-gcm`, `hmac`, `rand`, + `base64`, `libc`. + `git diff -M --stat d83534c..HEAD -- crates` shows 13 renames. + - operator: Idunn recipe run on Yggdrasil (Linux proof): the + `test-world-library`, `test-dungeon`, `build-daemon` steps and the + acceptance step resolve; `/srv/…/ghostlight-dungeon --state-root` + reopens the live `world.cc`; health reports `consumer-v4`. +- **Operator questions:** + - Q1-1 Library crate name. A: `ghostlight` (`crates/ghostlight`, + `use ghostlight::WorldMailbox`); B: `ghostlight-world` (the probe name); + C: `ghostlight-core`. **Recommended: A.** The target, the map, and the + operator ruling all say "the Ghostlight library"; the crate should be + what the prose calls it. The sibling `ghostlight-persona-projection` is a + membrane published for Epiphany, not the library. The repo, workspace, + and `package.json` names do not collide with a Cargo package name. + Mechanically A and B are identical (probes used B). + - Q1-2 `vault.rs` and `sdk_inference.rs`. A: both in the library; + B: both in Dungeon behind the `EvidenceSource`/`InferencePort` traits; + C: vault in the library, SDK port in Dungeon. **Recommended: A.** + `open_inference` (`controllers.rs:2314`) constructs the SDK route + itself, so B and C change how the port is built — a structure change + L0 forbids. Neither file names a Dungeon concept: the vault takes a + root and a scope, the port takes a sidecar entry. Both are named in the + target's own library list (evidence sources, inference ports). The + sidecar fixture writes (`sdk_inference.rs:1900,1983`) resolve from the + library's manifest dir at the same depth. Revisit when a second + consumer needs a different evidence source or transport. + - Q1-3 Principal evidence seam. A: library-owned + `VerifiedPrincipalEvidence` with a public `new`; Dungeon's + `account_for_cookie` is the single production caller, proven by a count + test. B: library trait `PrincipalEvidence { account_subject_hash, + valid_until }`; Dungeon keeps its private-field struct and implements + it; `SeedPort` (`mailbox.rs:590`) stores `Arc` + and the controllers' seed tests gain a fixture impl. **Recommended: A.** + It is what invariant 3 of the target says ("a library-owned type that + Dungeon constructs"), it is four edit sites, and the probe is green. The + minter seal was module privacy inside one crate; across a crate + boundary the library cannot know who a principal is, so the rule lives + with its owner (Dungeon) as a test, not as a trait the library carries + for one consumer. + - Q1-4 Cross-boundary fixtures. A: `test-support` cargo feature on the + library gating the 22 items and the fixture-only `issue()` impls, + enabled only by Dungeon's dev-dependency. B: move the runtime tests + into the library — not possible, they drive `run_cover_tick` and + `drive_one_tick`, which are Dungeon. C: make the four `fixture_*` + functions ungated `pub` — puts fixture-shaped `DecisionOpportunity` + minting in the production API. **Recommended: A**, with the port traits + (`InferencePort`, `ControllerWorkStore`) and their request/output types + in the ungated production API rather than behind the feature: they are + the "runner and port entry points" invariant 1 names, `open_inference` + already returns `Arc`, and hiding a trait a public + function returns is a `private_interfaces` smell. Gate only + `fixture_inference_*`, `fixture_prepared_inference`, + `fixture_controller_opportunities`, `InferenceFault::fixture_*`, + `TracingInferencePort`, and the `issue()`/`ScopeDigest::fixture` impls. + Invariant 7 admits exactly this feature. + - Q1-5 `ghostlight-persona-projection`. A: stays a separate crate, the + library depends on it by path as Dungeon does today. B: fold into the + library. **Recommended: A.** Epiphany pins it by git rev `22281891` with + `package = "ghostlight-persona-projection"`, which resolves against that + revision regardless of HEAD, so either option keeps the pin today; but + folding would make Epiphany's next bump pull the world library (tokio, + cultcache, codex-connector) for three prompt builders. Wrong dependency + direction; its own recipe test step stays. + - Q1-6 `gamecult-ops` deploy scripts — see Cut 2. + +## Cut 2. Build and deploy references in gamecult-ops + +- **Repo/branch:** gamecult-ops, current default branch; one commit after + Cut 1 is pushed. Depends on Q1-1 (the crate name) and Q1-6. +- **First:** `git -C F:\Projects\gamecult-ops status` clean; + `bash scripts/test-ghostlight-yggdrasil-wiring.sh` passes at base. +- **Deletes first:** per Q1-6 B only: `scripts/deploy-ghostlight-yggdrasil.sh` + (1300+ lines) and `scripts/test-ghostlight-yggdrasil-wiring.sh` + (`:276-278,282,343` are the only Ghostlight cargo assertions). +- **Keeps and moves:** none. +- **Adds:** none. +- **Per-file changes:** + - `runbooks/ghostlight-dungeon-yggdrasil.md:249-251`: insert + `cargo test --locked -p ` before the Dungeon test line; `:257` "one + explicit Rust binary" stays true. + - Per Q1-6 A: `scripts/deploy-ghostlight-yggdrasil.sh:964-970` add the + library test invocation in the same container run; `:1308` acceptance + becomes `-p --lib controllers::tests::real_codex_connector_cognition_modes_commit_speech`; + `scripts/test-ghostlight-yggdrasil-wiring.sh:276-278` add + `require_text "${deploy}" 'cargo test --locked -p '`. + - `idunn/yggdrasil/bindings/ghostlight.toml.in`: unchanged; it names the + recipe path, and the recipe carries the commands. +- **Authority map:** + - Owner: `deployment/idunn/recipe.toml` in Ghostlight owns the build and + test commands (binding `:11 recipe_path`). The runbook describes; the + scripts either actuate (A) or are retired (B). + - Inputs: the admitted ref and the recipe. + - Outputs: the same one binary plus `web/`. + - Derived state: runbook text, wiring-test assertions. + - Forbidden writers: no second list of cargo commands that can drift + from the recipe — under A the wiring test pins the script to the + recipe's strings; under B there is no second list. + - Shared paths: n/a. + - Deletion line: under B, both scripts before the runbook edit. +- **Verification:** + - builds: none (shell and Markdown). + - tests: `bash scripts/test-ghostlight-yggdrasil-wiring.sh` (A) passes + with the new `require_text`; (B) the script is gone and nothing + references it (`rg -n 'test-ghostlight-yggdrasil-wiring|deploy-ghostlight-yggdrasil' F:\Projects\gamecult-ops` = 0 + outside `docs/repo-census-2026-09`, which is an archive). + - negative: `rg -n 'ghostlight-dungeon --bin ghostlight-dungeon' F:\Projects\gamecult-ops --glob '!docs/**'` + lists only lines that also sit beside a `-p ` line. + - operator: the next Idunn deploy of a post-Cut-1 commit runs the + library step; before that, the operator confirms which actuator is live + on Yggdrasil (`systemctl cat ghostlight-dungeon.service` names the + unit's origin; the v2 binding lives under `/var/lib/gamecult/idunn-v2`). +- **Operator questions:** + - Q2-1 (= Q1-6) Edit or retire the scripts. A: edit both (five lines); + B: delete both and point the runbook at the recipe; C: leave them. + **Recommended: A.** The Body is ambiguous: the runbook (`:243-251`) + still says `request-idunn-bounded-redeploy-yggdrasil` runs those exact + cargo lines, the Idunn v2 binding template points at the recipe + instead, and the Ghostlight handoff (`notes/fresh-workspace-handoff.md:133`) + says Yggdrasil still serves legacy release `a4080d4`. No Ghostlight + doc calls the scripts legacy helpers (`rg` finds no mention of either + script name in the Ghostlight repo), so that claim is unverified. + Retiring a deploy actuator is a deployment-authority decision outside + L0; an edited dead script costs five lines, an unedited live script + would test only Dungeon's 49 tests and silently skip the 420 kernel + tests. C is the one option that leaves the release path able to lie. + +## Subtraction ledger (estimate) + +| Cut | Removed | Added | Deps / formats / targets | +|---|---|---|---| +| 0 | — | — | scratch captures only | +| 1 | `mod world;` (1), 9 unused re-exports (~3 lines), `cfg(test)` re-export block (12), Dungeon principal struct (38), Dungeon's direct persona-projection dep (1); ~55 lines | lib manifest (~30), workspace member (1), Dungeon deps (2), lock entry (~20), principal type in lib (~30), `pub use` boundary (~20), doc-tests (~25), runtime credential test (~25), minter count test (~15), recipe step (~6); ~175 lines. Visibility edits: 94 `pub(super)`→`pub(crate)`, ~130 `pub(crate)`→`pub` (items, fields, methods), 199 `crate::world::`→`crate::`, 32 `crate::world::`→`::` | +1 lib crate, +1 feature (`test-support`), 0 binaries, 0 new packages in `Cargo.lock`; 13 files renamed (~52k lines) | +| 2 | (B) ~1600 lines of shell | (A) ~5 lines | 0 | + +Net for L0: about +120 source lines, one crate, one feature. The line +growth is boundary declaration (what was implicit `pub(crate)` becomes an +explicit `pub` list plus its compile-fail proofs); the liability it retires +is the convention "Dungeon does not touch kernel internals", which becomes a +compile error. + +## Findings not assigned to a cut + +- `deployment/idunn/recipe.toml:142-146,169-171` declare the world slot and + provided capability as `ghostlight.world_state.foundation.v0` / + `foundation-v0`, and `:150-154` the controller-work slot as + `ghostlight.controller_work.v3`; source is `consumer.v4` + (`mod.rs:84-85`, tag `consumer-v4`) and `controller_work.v15` + (`controllers.rs:75-76`). Pre-existing drift; L0 changes no schema + string, so it stays. The census flagged the v3/v4 half already. +- `tests/build_provenance.rs` is never run by the recipe or the scripts: + `--bin ghostlight-dungeon` selects the bin's unit tests only. A + `cargo test --locked -p ghostlight-dungeon --test build_provenance` step + would be a recipe change with its own reason. +- `notes/ghostlight-current-system-map.md:18-19` still names the state + owner as `ghostlight.world_state.foundation.v0`; `:28` says "the private + `world::WorldKernel`". Both are stale before this cut; the steward owns + the rewrite. +- `runtime.rs:2492` is a `CommitReceipt` literal in Dungeon tests; after + the cut it needs the four `pub` fields listed, which is why + `CommitReceipt` is in the boundary at all. If Hands prefers not to expose + receipt fields for one test, the alternative is a library fixture behind + `test-support`; either is behavior-neutral. +- `web/` references nothing in `crates/`; `sidecar/claude-sdk/test/*` is + written by library tests under `GHOSTLIGHT_WRITE_SIDECAR_FIXTURES` and + read otherwise — path depth unchanged, nothing to do.