Ghostlight/docs/architecture/ghostlight-library-extraction-cut.md
Meta Vens d86ed9f4a2 Close L0: postmortem, ledger, target and state reconciled
The kernel extraction is finished. The world kernel is the ghostlight
library crate; Dungeon consumes it across a boundary sealed by admission,
proven from outside the crate.

The cut map records the last two fix batches and Q1-10, defaulted: the
minter scanner stays a tripwire, with Clippy recorded as the fix. The
subtraction ledger missed its estimate, +210 net source lines against about
+65. The reasons: the scanner grew chasing evasions, the boundary needed
about 90 more public items than planned, and the rejection tests buy a
ruling made after the estimate.

The postmortem lists what Soul caught, the operator corrections, the
incidents, and the open follow-ups. The target states which invariants
hold and how. The plan, map.yaml and the handoff point at L1. The L0
evidence record is rewritten as its distilled lessons; the previous text is
archived.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 12:24:49 +02:00

886 lines
56 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`; first version committed at `02bbf62`;
refreshed 2026-09-16 with the operator's rulings.
Cut 1 landed at Ghostlight `108b691..f859d2c`. `108b691` is 13 renames with
zero content change; `f859d2c` builds. Verified by Soul's own runs, not the
Hands report: library 419 passed + 1 ignored with test names identical to the
base `world::` set; 10 compile_fail doc-tests pass, also under
`--all-features`; Dungeon 50 + 1 ignored, all 49 base names present and the
+2 exactly the two new soul tests; no `[features]`, `cfg(feature`, or
`cfg(any(test` in the library; no schema string, `_NAMESPACE`, `derive_id`,
serde attribute, or `Preimage` changed; the rewritten tests fail under
mutation of what they pin. Windows only; the Linux release remains Idunn's.
Soul found six defects. Triage:
- **F1 seal is name-based (high, open):** every `pub` ID derives
`Deserialize` (`crates/ghostlight/src/lib.rs:186-200`), as do `ScopeDigest`
(`:532`) and `DecisionOpportunity` (`:559`), so an external crate mints IDs,
reproduces `ScopeDigest::fixture`, and forges a `DecisionOpportunity` into
`derive_cover` from JSON. The reducer still rejects forged opportunities
(`lib.rs:4192-4201`), so kernel integrity holds and the invariant's wording
does not. Dungeon's own Eve payloads deserialize these types, so removing
the derives is not available. Operator question Q1-9 below.
- **F2 single-minter test is bypassable (medium, fix):** the count at
`crates/ghostlight-dungeon/src/app_session.rs:530-555` greps a literal and
misses `<VerifiedPrincipalEvidence>::new(`, an alias, a renamed test module,
or a subdirectory.
- **F3 surface wider than bought (low, fix):** 71 `pub` identifiers are
unreferenced by Dungeon; `ScopeDigest`, `WorkLane`, and `ScopeComponents`
compile as `pub(crate)` and were opened only to quiet `private_interfaces`.
- **F4 "Eve" named in library error strings (low, fix):**
`crates/ghostlight/src/controllers.rs:1494,2576,2582,2584`. Pre-existing;
invariant 3 is still unmet.
- **F5 two Hands claims false as stated (cosmetic, recorded):** `Cargo.lock`
did gain a `ghostlight` package entry (no new external package), and
`fixture_[a-z]` has 8 benign hits in Dungeon.
- **F6 recipe schema drift (pre-existing, recorded):**
`deployment/idunn/recipe.toml` declares `world_state.foundation.v0` and
`controller_work.v3` against code at `consumer.v4` and
`controller_work.v15`. Outside L0; it changes no behavior here.
Cut 2 landed at gamecult-ops `6aba281` (text only; nothing live was run).
The library test step is in the runbook, the deploy script, and the wiring
test, ordered as `recipe.toml` orders it. Two spec defects Hands reported
rather than redesigned: the assertion this map specified was vacuous
(`require_text` is `grep -Fq`, and the string given is a substring of the
persona-projection line, so it passed with the library step deleted), so
Hands used the file's existing exact-line helper and proved it fails under
deletion; and the acceptance test path lives in `acceptance_test=` at
`scripts/deploy-ghostlight-yggdrasil.sh:34`, a fourth site this map did not
list, pinned by the wiring test and repeated in the runbook prose. Operator
consequence: changing `acceptance_test` changes the acceptance binding, so
sealed witnesses under `/srv/ghostlight/acceptance/<binding-sha>/` from
before this commit will miss and regenerate.
Soul passed on Cut 2 and found the assertions pin text, not invocations:
- **C2-F1 (high, fixing):** deleting the two lines above the pinned cargo
line in `scripts/deploy-ghostlight-yggdrasil.sh` leaves it byte-identical
as trailing argv on the *persona-projection* container, so the kernel
tests never run and the wiring test still exits 0. Prefixing the
`docker run` line with `: ` does the same.
- **C2-F2 (medium, fixing):** nothing pins the order of the four cargo
steps; the library block moved after the release build and the wiring
test passed, which would seal an artifact before the kernel tests ran.
- **C2-F3 (medium, fixing):** the wiring test never reads the runbook, so
only three of the four acceptance sites are enforced to agree.
- **C2-F4 (medium, pre-existing, recorded):** `deploy-…sh:937` takes a
reuse branch when the release directory for a commit already exists, so a
redeploy of a sealed commit runs no cargo steps at all. This bounds "the
deploy runs the kernel tests" to a commit's first build. Operator owns
whether that is right.
- **C2-F5 (low, recorded):** `docs/repo-census-2026-09/*` still shows
`src/world/...`. Dated census; no deploy effect.
Held: the step sits in the same branch, image and mounts as its
neighbours, and `set -euo pipefail` with `trap` means a library-test
failure does abort the deploy. The acceptance test exists at
`crates/ghostlight/src/controllers.rs:8980` under `-p ghostlight --lib`.
C2-F1, C2-F2 and C2-F3 are fixed at gamecult-ops `803f2c7` (wiring test
only). A new `require_invocation` helper pins each of the four cargo steps
as a whole contiguous docker invocation and returns its start line; three
numeric comparisons assert the recipe order; the runbook became a checked
file, tying all four acceptance sites together. All four of Soul's
mutations now exit 1 where they previously exited 0, and every
pre-existing assertion still passes. `require_order` was unusable here
because its `last_line` resolves a substring and would have bound the
library step to the acceptance invocation.
Soul passed on the fix batch and confirmed it does what it claimed:
whole-invocation pinning survives image swaps, line swaps, re-indentation,
continuation splits and appended flags; the order checks catch a move into
the reuse branch; the runbook is tied in both directions; `fail` inside a
command substitution does abort, because `set -e` fires on the
assignment's status; nothing was weakened. It also found the structural
limit, recorded and not fixed:
- **C2-F6 (high, recorded):** an exact-line pin proves the lines exist,
not that they run. Wrapping the block in `if false`, prefixing the
opener with `true || \`, or swallowing it in a heredoc keeps every
pinned line byte-identical and the checker exits 0.
- **C2-F7 (medium, recorded):** the four steps carry no explicit `||
exit`; abort depends entirely on `set -euo pipefail` at
`deploy-…sh:2`, which nothing pins. Removing the `e` leaves the checker
green, so a failing kernel suite would be followed by a sealed release.
- **C2-F8 (low, recorded):** a duplicated block passes, since the helper
takes the first match and never asserts a count.
These stay recorded rather than fixed: that script is a legacy actuator
under Ghostlight's do-not-invoke warning and is retired at the deployment
gate, and the authority that actually runs the tests is Ghostlight's own
`deployment/idunn/recipe.toml`. Closing them means control-flow analysis
in a bash checker for a script we intend to delete. Revisit only if the
deployment gate keeps the script.
Follow-ups outside this migration, from the fix batch:
- `scripts/deploy-ghostlight-yggdrasil.sh:974`, the web container, is
pinned by substring and survives a `: ` prefix that neuters it.
- The Codex Connector's four cargo steps are bare substring pins with no
invocation or order assertion: the connector-side C2-F1 and C2-F2.
- `require_command_options` searches a fixed 18-line window, so a block
that grows past it stops being checked silently.
Cut 1 fix batch landed at Ghostlight `7c1d1e9..2865ea7`: `a8d3377` external
rejection tests (Q1-9), `fac90e6` single-minter check by name (F2),
`7cbb9e3` Eve removed from library text (F4), `2865ea7` public surface
262 → 258 (F3). Hands' correction to F3 is confirmed by Soul: Dungeon calls
`bound_scope_digest` and `fresh_scope_digest`, and `ScopeDigest`, `WorkLane`
and `ScopeComponents` are held public by `KernelError::ScopeChanged`,
`ControllerWorkCustody::Uncertain::lane` and `NarrativeCheckpoint` variant
fields; closing them means reshaping public enums, which F3 does not
license. `fac90e6`'s commit message describes a replaced brace-counting
scanner; the landed code and its doc comment are correct.
Soul held: the rejection tests are a true external crate with no `#[path]`
or `include!`, driving `ControllerPort::submit_controller` through the
mailbox into `reduce` with no pre-validation; every commit moves `revision`
and `state_digest`, so the unchanged-state assertions are sound; the
positive test goes red when issued opportunities are refused; counts,
the 14 distinct warnings, and both manifests are unchanged.
Soul found:
- **S1 (medium, fixing):** both forged digests differ in length from a real
`sha256:` + 64 hex, so comparing only lengths at
`crates/ghostlight/src/lib.rs:4203` leaves all seven tests green. The
realistic forgery is a stale same-length digest from an earlier revision.
- **S4 (medium, open, Q1-10):** the minter scanner at
`crates/ghostlight-dungeon/src/app_session.rs:523-668` misses a `pub type`
alias, a multi-line alias, a `} // tests` closing brace, non-rustfmt
indentation, and `::new` taken as a function value.
- **S7 (low, fixing):** `controllers.rs:122 provider_model` closes to
`pub(crate)` with no error and no warning change.
- **S8 (cosmetic, fixing):** `mailbox.rs:580` names `AppSessionOwner` and
`controllers.rs:2606` names AppSession.
- **S2 (low, recorded):** neutering the world_id check at `lib.rs:4193`
leaves the suite green because `ScopePreimage` includes `world_id`, so the
scope lookup refuses first. Redundant by construction; not isolable from
outside the crate.
- **S3 (low, recorded):** the decline path (`lib.rs:1603`) is `pub(crate)`
and unreachable from an integration test.
- **S5 (low, recorded, pre-existing):** `runtime.rs:3584`'s credential-name
test splits on one literal `#[cfg(test)]` marker and is evadable the same
way the minter test was.
- **S6 (medium, recorded):** stable rustc does not enforce `compile_fail`
error codes; `E0603` changed to `E0999` still passes. All ten doc-tests
prove "does not compile", not privacy, and the three `#[cfg(test)]`
items (`SubjectId::issue`, `ScopeDigest::fixture`, the `fixture_*` fns)
prove non-existence. Enforcing codes needs a snapshot tool such as
trybuild; recorded, not added.
**Q1-10 how the single minter is enforced (defaulted, 2026-09-16).** Asked
how much it mattered, the operator said not at all. The scanner stays as a
tripwire with stated limits: it guards only against the project's own
Dungeon code, and what evades it is deliberate. S4 is a recorded follow-up,
with Clippy's `disallowed-methods` for
`ghostlight::VerifiedPrincipalEvidence::new` noted as the fix if a second
minter ever becomes plausible.
S1, S7 and S8 landed at Ghostlight `bde05de..af9c95a`. S1 submits a
genuinely issued digest made stale by another controller's speech, plus the
current digest with its last digit altered. Soul confirmed the premise
check fails when staleness does not happen and that no earlier check
intercepts the forgery, then found two surviving comparison mutants
(case-insensitive, hex-only after the label). Those, and "player"
vocabulary in library tests, landed at `b14507c` (uppercased-letter,
bare-hex and relabelled forgeries, all six comparison mutants killed) and
`ab95d78` (test-only rename to "human", one test name changed). Soul's
final pass found nothing to fix.
**L0 closed, 2026-09-16.** Postmortem:
`ghostlight-library-extraction-postmortem.md`. Follow-ups are listed there.
**Q1-9 what sealing means (ruled, operator, 2026-09-16): option A.** The
invariant is unforgeable admission, not unconstructible types. External
code may hold a syntactically valid ID, digest, or opportunity; the kernel
must reject any it did not issue, and that is proven by rejection tests
written from outside the crate. The compile_fail set stays for the
genuinely private items. The question as first posed: A: restate invariant 1 as unforgeable
*admission* — external code may hold a syntactically valid ID or opportunity,
and the kernel must reject any it did not issue — and prove it with rejection
tests from an external crate plus the existing compile_fail set. B: make the
types unconstructible from outside, which means Dungeon stops deserializing
`DecisionOpportunity` in its Eve payloads and receives an opaque token
instead, a design change L0 excludes. **Recommended: A**, because the kernel
already enforces it and B moves work into L0 that the target puts out of
scope. Whichever is chosen, F2 and F3 are fixed in the same Hands batch.
Rulings (operator, 2026-09-15):
- **Q1-1 crate name:** "crate name is `ghostlight`."
- **Q1-2 adapters:** "`vault.rs` and `sdk_inference.rs` both go in the
library."
- **Q1-3 principal seam:** "option A, library-owned
`VerifiedPrincipalEvidence` with a public `new`, plus the Dungeon count
test pinning the single minter."
- **Q1-4 fixtures:** "NOT the `test-support` feature. The operator takes the
alternative: the sealed ID `issue()` constructors and `ScopeDigest::fixture`
stay unreachable from outside the library under every configuration,
including feature-enabled builds, and the Dungeon tests that currently
need them move into the library instead. Rationale: a sealed constructor
exposed behind a feature is still reachable by any crate that turns the
feature on, which contradicts invariant 1." (Supersedes the `test-support`
design in the `02bbf62` map; that text is history and is not repeated
here. The classification below found that none of the affected tests is
a kernel test, which raises Q1-7 and Q1-8.)
- **Q1-5 persona projection:** "`ghostlight-persona-projection` stays a
separate crate."
- **Q1-6 gamecult-ops:** "edit the five gamecult-ops cargo lines in Cut 2;
do not retire the scripts (retirement belongs to the deployment gate)."
Correction to the `02bbf62` map: Ghostlight's handoff does call those
helpers unsafe — `notes/fresh-workspace-handoff.md:389-391`: "Do not
invoke the current bounded Connector or Ghostlight redeploy helpers; they
fail at the root/Idunn Git boundary and still embody duplicate target
deployment authority." The earlier claim that no such doc existed was
wrong; the ruling to edit rather than retire stands on the deployment
gate owning retirement, not on the scripts being live.
Rulings (2026-09-16), taken by Self under the operator's standing "Go"
after Q1-4's classification found no kernel test to move. Either may be
reversed by the operator; both are recorded here as the live design:
- **Q1-7 port value constructors:** option A. `PreparedInference::prepare`
(renamed from `prepare_invocation`), `InferenceOutput::new`, and
`InferenceFault::{retryable, recovery_required, integrity_violation}`
become the port's public contract; the five `fixture_*` items are
deleted and `TracingInferencePort` becomes an ungated decorator. A
public trait whose return types only the library can construct is not a
port, none of these touches a sealed item, and the change is a net
deletion.
- **Q1-8 cover source:** option A. The tick-ordering test derives its
cover from a real world through `derive_cover` and asserts 2 cells, with
its ordering and shared-tick assertions unchanged;
`fixture_controller_opportunities`, the only outside caller of a sealed
`issue()`/`ScopeDigest::fixture`, is deleted.
Together these satisfy Q1-4's intent: the sealed ID issuers and
`ScopeDigest::fixture` stay private `#[cfg(test)]` items with no feature
gate anywhere, unreachable from outside the library under every
configuration.
Open: nothing blocking Cut 1. Operator checks remain on the Linux release
(Idunn's recipe run), which no Windows probe can prove.
Follow-ups outside this migration: see "Findings not assigned to a cut".
Probe names versus real names: probes 1–4 built the crate as
`ghostlight-world` (`use ghostlight_world::…`); probe 5 rebuilt it as
`ghostlight` (`use ghostlight::…`). The difference is the package name, the
directory `crates/ghostlight`, and the path prefix; every mechanism finding
held in both. This map uses the real name throughout.
## What the probes established
All probes ran in scratch worktrees at `d83534c` (both removed, repo clean)
on the Windows workstation with `cargo check` and `cargo test --doc`; they
prove the crate graph and visibility on Windows only. The Linux proof is
Idunn's recipe run on Yggdrasil. Logs are in the session scratchpad
(`probe1*-check.log`, `probe3*-visibility.log`, `probe5.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-32` re-exports nine names nobody in the crate
uses (`ControllerOpenError`, `NarrativeCapture`, `NarrativeDecision`,
`NarrativePending`, `OperationalCapture`, `OperationalDecision`,
`OperationalPending`, `SourceRange`, `TranslationGapSummary`). Their
deletion is inside Cut 1.
- **The move compiles** (probe 1d). Moving `src/world/*` to the new crate
(`mod.rs` → `lib.rs`), rewriting `crate::world::` → `crate::` inside it and
→ `ghostlight::` in Dungeon, and moving `VerifiedPrincipalEvidence` into
the library root: `cargo check` on the library, its tests, the Dungeon
bin, and the Dungeon tests all pass. Two mechanical faults: 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 from every
item `pub(crate)`): 54 production items, 22 test-only items, 38 fields on
10 structs, 36 methods, the opaque IDs `AffordanceId`/`CommandId`/
`SubjectId`, and `FictionalMinutes(u64)` (one Dungeon test). Nothing
sealed appears: `WorldKernel`, `WorldState`, `reduce`, `apply_effect`,
`CommandEnvelope`, `AuthenticatedCaller`, the journal, `*::issue()` and
`ScopeDigest::fixture` are never named by Dungeon. Under the narrowed
library `cargo check -p ghostlight` emits 646 dead-code warnings — a
second enumeration of the same boundary (an item used only by Dungeon is
dead inside the library until it is `pub`).
- **The sealed set under the Q1-4 ruling** (probe 5, crate `ghostlight`, no
cargo features, fixtures left `#[cfg(test)]`): ten `compile_fail`
doc-tests on the library root pass under `cargo test -p ghostlight --doc`
and again under `cargo test -p ghostlight --all-features --doc` (the crate
declares no features, so the second run is the same build; it is the
command Soul re-runs if a feature is ever added). `cargo check -p
ghostlight-dungeon --bin ghostlight-dungeon` passes: production needs no
fixture. `cargo check -p ghostlight-dungeon --tests` fails on exactly four
errors — `runtime.rs:3081` (`ControllerWork…` family, `Inference…`
types, three `fixture_inference_*`), `:3570`
(`fixture_controller_opportunities`), `:2582` (`TracingInferencePort`),
`:2581` (`InferencePort`, because the base re-export at `mod.rs:66` is
`cfg(test)`). Those four sites are the whole cross-boundary test surface;
they are classified under Cut 1.
- **Compile-fail mechanism**: doc-tests compile as an external crate, which
is exactly the boundary; no `trybuild`, no scratch crate. The ten
(`use ghostlight::WorldKernel`, `reduce`, `WorldState`,
`CommandEnvelope`, `AuthenticatedCaller`, `journal::WorldJournal`,
`SubjectId::issue()`, `ScopeDigest::fixture("x")`,
`fixture_controller_opportunities(&[])`, `fixture_inference_output(..)`)
are pinned to `E0603`, `E0599`, `E0599`, `E0425`, `E0425`.
- **Negative-grep collisions** checked in `src/world/`: `axum`, `tower_http`,
`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 a bare `player` pattern. `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`
reports `i/lf w/lf`); `core.autocrlf=true` rewrote every fresh scratch
worktree to CRLF. Hands edits in `F:\Projects\Ghostlight` itself, never in
a fresh worktree; if a worktree is unavoidable, run `git ls-files --eol
crates` after the move and refuse any `w/crlf`. `sdk_inference.rs:1831`
normalizes `\r\n` before scanning source.
## Cut 0. Captures
- **Repo/branch:** Ghostlight `codex/ghostlight-dungeon-mvp` at `02bbf62`
(source identical to `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. No store exists 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(<capture>/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
`02bbf62`. One commit; the crate graph does not build half-moved. Depends
on Cut 0 and rulings Q1-7, Q1-8.
- **First:** Cut 0 captures in hand; `git status` clean; working in the
main checkout (LF).
- **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). The port traits and their value types in it
return as ungated production `pub use` under Adds; the three
`fixture_inference_*` names do not return.
- `crates/ghostlight-dungeon/src/world/mod.rs:483-508` —
`fixture_controller_opportunities` (26 lines), per Q1-8 A; and
`controllers.rs:372-382,409-432` — `fixture_prepared_inference`,
`fixture_inference_output`, `fixture_inference_events` (about 40
lines) and `controllers.rs:221-237` — `InferenceFault::fixture_*`
(17 lines), per Q1-7 A. Under Q1-7 B / Q1-8 B these stay `#[cfg(test)]`
inside the library and the Dungeon tests that used them are deleted
instead (see the classification).
- `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 (`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/ghostlight/src/` and
`git mv .../world/mod.rs crates/ghostlight/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); `pub(super)` → `pub(crate)` in
`lib.rs` only (94 sites; submodules keep theirs);
`crate::app_session::VerifiedPrincipalEvidence` → `crate::VerifiedPrincipalEvidence`
(`mailbox.rs:10`, `controllers.rs:10714,10790,10988,11473`).
- `vault.rs` and `sdk_inference.rs` move with the rest (Q1-2). Their
fixture writes (`sdk_inference.rs:1900,1983`) resolve from
`crates/ghostlight` at the same depth.
- `build.rs`, `tests/build_provenance.rs`, `runtime.rs:375` (web/dist)
stay in Dungeon; `CARGO_MANIFEST_DIR/../..` still resolves.
- No test moves between crates. Every test under `world::` was already a
library test and moves with its file (420); every Dungeon test stays in
Dungeon (49). See the classification.
- **Adds:**
- `Cargo.toml` (workspace): member `crates/ghostlight`.
- `crates/ghostlight/Cargo.toml`: package `ghostlight`; no `[features]`;
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`: `ghostlight = { path =
"../ghostlight" }` in `[dependencies]`; nothing in dev-dependencies.
- `Cargo.lock`: the new `[[package]] name = "ghostlight"` entry. Commit
it; Idunn builds `--locked`.
- `crates/ghostlight/src/lib.rs`, root: `pub struct VerifiedPrincipalEvidence
{ account_subject_hash: String, valid_until: DateTime<Utc> }` with `pub fn
new`, `pub fn account_subject_hash`, `pub fn valid_until`, and
`#[cfg(test)] fn fixture` delegating to `new` (Q1-3). Doc comment: the
consumer verifies; the library bounds by `valid_until` at
`mailbox.rs:231`.
- `crates/ghostlight/src/lib.rs`, the public boundary, one ungated list.
`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,
ControllerWork, ControllerWorkCustody, ControllerWorkLookup,
ControllerWorkStore, ControllerWorkStoreError, ControllerWorkWrite,
InferenceEvent, InferenceFault, InferenceOutput, InferencePort,
InferencePurpose, InferenceRequest, NarrativeRun, OperationalRun,
PreparedInference, SubmissionDisposition, ToolResultOracle,
open_controller_work, open_inference}` (plus `TracingInferencePort`
under Q1-7 A);
`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`; give it a visibility parameter and emit `pub` for
these three only — the `issue()` impls stay private and `#[cfg(test)]`),
`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.
- How the ports stay public without the fixture constructors becoming
reachable: `InferencePort` (`controllers.rs:259`) and
`ControllerWorkStore` (`:1541`) are traits whose method signatures name
`InferenceRequest`, `PreparedInference`, `InferenceOutput`,
`InferenceFault`, `ToolResultOracle`, `ControllerWork`,
`ControllerWorkLookup`, `ControllerWorkWrite`, `ControllerWorkCustody`,
`ControllerWorkStoreError`. Those types become `pub`; their fields stay
private except where listed below. Nothing about them reaches an ID
`issue()` or `ScopeDigest::fixture`, which are private free functions
and `#[cfg(test)]` impls (`mod.rs:200-226,448`) with no `pub` path of any
kind and no cargo feature — reachable only from inside the library's
own test build. The `fixture_*` functions are deleted (Q1-7 A / Q1-8 A)
or stay `#[cfg(test)]` (B), and the ten doc-tests pin both outcomes.
- 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}`.
`FictionalMinutes.0` stays `pub(crate)`: under Q1-8 A the one Dungeon
use (`runtime.rs:3579`) is replaced by `snapshot.now`. 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`),
`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`). Under Q1-7 A add `PreparedInference::prepare`
(renamed from `prepare_invocation`, `controllers.rs:388`),
`InferenceOutput::new`, `InferenceFault::{retryable, recovery_required,
integrity_violation}`, `InferenceRequest::provider_model` (`:120`), and
`TracingInferencePort::new` (`:285`).
- `crates/ghostlight/src/lib.rs` crate docs: the ten `compile_fail`
doc-tests from probe 5, each pinned to its error code.
- `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 (Q1-3).
- **Cross-boundary test classification (Q1-4).** Every Dungeon test that
reaches an item which is `#[cfg(test)]` in the library today. Method:
probe 5's four compile errors plus a read of each test body.
| # | File:line | Test | Exercises | Reaches | Class |
|---|---|---|---|---|---|
| 1 | `runtime.rs:3270` | `the_tick_driver_never_exceeds_its_controller_permit_pool` | `run_cover_tick` and `state.controller_permits`: two singleton cells, pool of one, no overlapping `infer` | `fixture_prepared_inference`, `fixture_inference_output` (via `CountingInferencePort`, `:3241`); `AlwaysFreshWorkStore` (`:3194`) uses only `pub` enum variants | Dungeon runtime |
| 2 | `runtime.rs:3337` | `quarantine_raised_mid_tick_stops_the_sibling_cell_and_every_later_tick` | the driver's `controller_quarantined` flag across sibling cell and next tick | `fixture_prepared_inference`, `InferenceFault::fixture_integrity_violation` (`QuarantiningInferencePort`, `:3308`) | Dungeon runtime |
| 3 | `runtime.rs:3500` | `a_second_mid_turn_change_reaches_the_drivers_interrupted_arm` | the driver's `Interrupted` arm reached through `run_cover_tick`, commits through `ControllerPort::submit_controller` (production) | `fixture_prepared_inference`, `fixture_inference_output`, `fixture_inference_events`, `InferenceFault::fixture_recovery_required`, `InferenceEvent::ToolCall` (pub variant), `PreparedInference.purpose` (`InterruptingCoverPort`, `:3432`) | Dungeon runtime |
| 4 | `runtime.rs:3567` | `drive_one_tick_runs_every_cell_before_the_clock_and_all_share_one_tick` | `drive_one_tick` ordering: every cell before the clock, all cells carry the cover's tick | `fixture_controller_opportunities` (the only caller of the sealed `WorldId/SubjectId/ControllerId::issue()` and `ScopeDigest::fixture` from outside `world/`), `FictionalMinutes(u64)`, `AgencyGraph::default`, `Cell`, `TickIndex`, `CommitReceipt` literal (`controller_commit`, `:2492`) | Dungeon runtime |
| 5 | `runtime.rs:3697` (ignored) | `live_smoke_seeds_then_ticks_a_world_against_the_connector` | the road test: seed from a real Vault, activate, tick against a real connector; `GHOSTLIGHT_SMOKE_TRACE` writes the whole membrane (`notes/local-live-smoke.md:214-246`) | `TracingInferencePort` (`fixture_with`, `:2581-2582`) | Dungeon runtime |
Counts: 5 tests reach test-only library items; 0 are kernel tests; 5 are
Dungeon runtime tests. Two other Dungeon tests touch the boundary but
need nothing test-only: `runtime.rs:2502`
`no_proposal_projection_carries_the_canonical_world_commit` builds a
`CommitReceipt` literal (four `pub` fields, in the boundary list) and
`runtime.rs:4293` `soul_verified_evidence_outlives_the_session_that_minted_it`
uses `VerifiedPrincipalEvidence::fixture` → `::new` (Q1-3). Every other
Dungeon test (42) uses production ingress only.
Consequence: the ruling's "move the tests into the library" has no test
to move. Tests 1–4 exercise `run_cover_tick`, `drive_one_tick`, the permit
pool, and quarantine — Dungeon's tick driver — and test 5 is Dungeon's
road test; moving any of them would move the driver. What they need
divides into two kinds, and each is an operator question below rather
than a mechanism this map invents:
- tests 1, 2, 3, 5 need to construct **port values** (`PreparedInference`,
`InferenceOutput`, `InferenceFault`) and a port decorator; none of
these is a sealed item under invariant 1 (mutable state, ID allocator,
reducer, journal, authenticated-caller constructor) — Q1-7;
- test 4 needs a **`Cover`** to walk, and today gets it from fabricated
opportunities minted with sealed IDs — Q1-8.
Expected post-move counts, under Q1-7 A and Q1-8 A: library 420 unit
(the base `world::` list, prefix stripped) + 10 doc; Dungeon bin 49
(the same 49 names; tests 1–4 rewritten in place, test 5 unchanged);
`build_provenance` 1; persona-projection 13; ignored 1 + 1. Under Q1-7 B
tests 1–3 and the `trace` half of test 5 are deleted (Dungeon 46, or 45
if test 4 goes under Q1-8 B); the library count is unchanged either way.
- **Per-file changes** (against `d83534c` = `02bbf62` source):
- `Cargo.toml:3-5`: add member.
- `crates/ghostlight-dungeon/Cargo.toml:27`: replace with the library
path dep.
- `crates/ghostlight-dungeon/src/main.rs:7`: delete.
- `crates/ghostlight-dungeon/src/runtime.rs:3-22`: split the `use crate::{}`
block; `world::{…}` becomes `use ghostlight::{…}` (probe edit); `:4`:
import `VerifiedPrincipalEvidence` from `ghostlight`, 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::` →
`ghostlight::`. `:4313`: `::fixture(` → `::new(`. `:3081-3086`: drop
the three `fixture_inference_*` names. `:3243,3310,3434`:
`PreparedInference::prepare("ghostlight-controller-test",
4_102_444_800_000, request)` (Q1-7 A; the two literals are the ones
`fixture_prepared_inference` hard-codes at `controllers.rs:381`).
`:3255,3442,3447`: `InferenceOutput::new(vec![InferenceEvent::Text(..)],
"sha256:<receipt>")`; `:3457`: `InferenceOutput::new(events, ..)`;
`:3318`: `InferenceFault::integrity_violation(..)`; `:3479`:
`InferenceFault::recovery_required(..)`. `:3567-3644` (Q1-8 A): replace
`fixture_controller_opportunities` + `FictionalMinutes(..)` with
`fixture().await`, `active_two_cell_world`, `snapshot()`, and
`agency_graph()`, then `derive_cover(snapshot.world_id, snapshot.now,
CLOCK_TICK_MINUTES, &snapshot.opportunities, &graph, budget)`; the
cell count assertion becomes 2 (a genesis world has two
controller-bearing subjects — `:3089-3095` already says this is the
most `world.create` can produce); the ordering and shared-tick
assertions are unchanged. `: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 ghostlight::VerifiedPrincipalEvidence;`; `:232-235`: struct literal
→ `VerifiedPrincipalEvidence::new(session.account_subject_hash.clone(),
session.access_expires_at)`.
- `crates/ghostlight/src/controllers.rs:221-237,372-382,409-432` (Q1-7 A):
delete the fixtures; `:388` `prepare_invocation` → `pub fn
PreparedInference::prepare` (keep the body; the two in-library callers
at the connector and SDK ports follow); `:150` add `pub fn new(events,
receipt_digest)`; `:192,206` `new` → `pub fn retryable`,
`integrity_violation` → `pub`, add `pub fn recovery_required(detail)`
(the `RecoveryRequired` disposition has a private `new` today; make it
the named constructor); `:277-370` drop `#[cfg(test)]` from
`TracingInferencePort` and make it and `new` `pub`.
- `crates/ghostlight/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","ghostlight"]`
(runs the 420 unit tests and the 10 doc-tests) and `test-dungeon`:
the existing `-p ghostlight-dungeon --bin ghostlight-dungeon` line.
`:51-53`: acceptance argv becomes `-p ghostlight --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 `crates/ghostlight`;
`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 `ghostlight` owns the world kernel, its
ontology, reducer, journal, mailbox, controllers, elaboration, cover,
clock, consumer admission, inference ports (connector, SDK sidecar,
and the tracing decorator), and the markdown evidence source. 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`;
a consumer-implemented `InferencePort`/`ControllerWorkStore` when the
consumer supplies its own (Dungeon's tests do; production uses the
library's).
- 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, an `issue()`, or
`ScopeDigest::fixture` — enforced by visibility with no feature that
can lift it, proven by the doc-tests under `--all-features`. Any second
minter of `VerifiedPrincipalEvidence` — enforced by the Dungeon count
test. Any fixture-shaped `DecisionOpportunity` — there is no
constructor for one outside the reducer.
- Shared paths: Dungeon production and Dungeon tests reach the kernel
through the same `pub` items; a Dungeon test port constructs port
values through the same constructors the library's own ports use
(Q1-7 A). 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 (Q1-5).
- Deletion line: `mod world;`, the nine unused re-exports, the
`#[cfg(test)]` re-export block, the `fixture_*` functions (A) and
Dungeon's private principal struct are gone before the first `pub` is
written.
- **Verification** (Windows workstation; proves Windows only):
- builds: `cargo check -p ghostlight` (no new warnings; the 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 ghostlight` = 420 unit + 10 doc (the 420 names
equal the base `world::` list with the prefix stripped);
`cargo test -p ghostlight-dungeon --bin ghostlight-dungeon` = 49
(same names as base);
`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. Tests 1–4
keep their names and assertions (test 4's cell count is 2).
- negative:
`cargo test -p ghostlight --all-features --doc` = 10 passed (same as
without; the command is the standing check that no later feature
reopens the seal);
`rg -n '\[features\]' crates/ghostlight/Cargo.toml` = 0;
`rg -n 'cfg\(any\(test' crates/ghostlight/src` = 0 (no feature-gated
test path exists);
`rg -n 'fixture_' crates/ghostlight-dungeon/src` = 0 outside the
Heimdall `::fixture(` sites (`app_session.rs:560`, `runtime.rs:2552,
2611,4126` — use `rg -n 'fixture_[a-z]' …` which they do not match);
`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/ghostlight/src` = 0
(collisions checked: `Dungeons` in `vault.rs` is excluded by `\b`;
do not add `player`);
`rg -n 'pub\(super\)' crates/ghostlight/src/lib.rs` = 0;
`rg -n 'VerifiedPrincipalEvidence::new\(' crates/ghostlight-dungeon/src`
= 1 outside `#[cfg(test)]`;
`cargo tree -p ghostlight` names no `axum`, `tower-http`,
`jsonwebtoken`, `cultnet-rs`, `cultmesh-rs`, `reqwest`, `aes-gcm`,
`hmac`, `rand`, `base64`, `libc`;
`git diff -M --stat 02bbf62..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`. One
`GHOSTLIGHT_SMOKE_TRACE` road run after the cut shows the membrane
trace still writes (test 5).
- **Operator questions:**
- Q1-7 Port value constructors for consumer-implemented ports. Tests 1,
2, 3 and 5 implement `InferencePort` outside the library and must
return `PreparedInference`, `InferenceOutput`, `InferenceFault`; today
they do it through `#[cfg(test)]` `fixture_*` wrappers that exist only
because those types have private constructors. A: make the
constructors the port contract — `PreparedInference::prepare(caller_
runtime_id, expires_at_unix_ms, request)` (rename of the existing
`prepare_invocation`, "the one place a `PreparedInference` is built",
which every in-library port already calls), `InferenceOutput::new(events,
receipt_digest)`, `InferenceFault::{retryable, recovery_required,
integrity_violation}`, `InferenceRequest::provider_model`; delete the
five `fixture_*` items; make `TracingInferencePort` an ungated `pub`
decorator (it wraps any port and constructs nothing sealed; "production
never constructs it" becomes a Dungeon rule, which is where it was
enforced anyway — only the ignored smoke names it). B: keep every
fixture `#[cfg(test)]` in the library and delete tests 1–3 and the
`trace` field of `LiveController` (`runtime.rs:2538,3726`) from
Dungeon; the permit-pool, quarantine, and interrupted-arm invariants of
the tick driver lose their proofs and the road test loses its membrane
trace. **Recommended: A.** A public trait whose methods must return a
type nobody outside the crate can construct is not a port; it is a
trait only the library can implement. Invariant 1 seals the kernel,
not the inference transport, and none of these constructors mints an
ID, mutates state, reduces, or writes the journal. A is a net
deletion (about 85 fixture lines out, about 25 constructor lines in,
of which `prepare` is a rename).
- Q1-8 The cover for `drive_one_tick_runs_every_cell_before_the_clock_and_all_share_one_tick`.
A: derive it from a real world through production ingress — the
module's own `fixture()`/`active_two_cell_world`, then `snapshot()` and
`agency_graph()` into `derive_cover`; delete
`fixture_controller_opportunities` and the `FictionalMinutes(u64)`
construction; the test asserts two cells instead of three, and the
ordering and shared-tick assertions are unchanged. B: delete the test.
C: keep `fixture_controller_opportunities` `#[cfg(test)]` in the
library and move the test there — not available, `drive_one_tick` is
Dungeon. **Recommended: A.** It removes the last caller of the sealed
constructors from outside `world/`, uses only what tests 1–3 already
use, and the test's own doc (`:3558-3565`) is about ordering, not
about three.
## Cut 2. Build and deploy references in gamecult-ops
- **Repo/branch:** gamecult-ops, current default branch; one commit after
Cut 1 is pushed.
- **First:** `git -C F:\Projects\gamecult-ops status` clean;
`bash scripts/test-ghostlight-yggdrasil-wiring.sh` passes at base.
- **Deletes first:** none (Q1-6: retirement belongs to the deployment
gate).
- **Keeps and moves:** none.
- **Adds:** none.
- **Per-file changes:**
- `runbooks/ghostlight-dungeon-yggdrasil.md:249-251`: insert
`cargo test --locked -p ghostlight` before the Dungeon test line;
`:257` "one explicit Rust binary" stays true.
- `scripts/deploy-ghostlight-yggdrasil.sh:964-970`: add the library test
invocation in the same container run; `:1308`: acceptance becomes
`-p ghostlight --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 ghostlight'`.
- `idunn/yggdrasil/bindings/ghostlight.toml.in`: unchanged; it names the
recipe path, and the recipe carries the commands.
- The scripts remain, per the Ghostlight handoff (`:389-391`), helpers
not to invoke: they fail at the root/Idunn Git boundary and hold
duplicate deployment authority. Editing them keeps their command list
from lying about the crate graph until the deployment gate retires
them; it is not an endorsement to run them.
- **Authority map:**
- Owner: `deployment/idunn/recipe.toml` in Ghostlight owns the build and
test commands (binding `:11 recipe_path`). The runbook describes; the
scripts are a second, dormant list pinned to the recipe's strings by
the wiring test until the deployment gate removes them.
- 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 drifts from
the recipe; the wiring test's `require_text` lines are that check.
- Shared paths: n/a.
- Deletion line: none in this cut.
- **Verification:**
- builds: none (shell and Markdown).
- tests: `bash scripts/test-ghostlight-yggdrasil-wiring.sh` passes with
the new `require_text`.
- negative: `rg -n 'ghostlight-dungeon --bin ghostlight-dungeon' F:\Projects\gamecult-ops --glob '!docs/**'`
lists only lines that also sit beside a `-p ghostlight` line.
- operator: the next Idunn deploy of a post-Cut-1 commit runs the
library step.
- **Operator questions:** none; Q2-1 was Q1-6 and is ruled.
## 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), `fixture_controller_opportunities` (26, Q1-8 A), inference fixtures (~60, Q1-7 A); ~140 lines | lib manifest (~28), workspace member (1), Dungeon dep (1), lock entry (~20), principal type in lib (~30), `pub use` boundary (~25), doc-tests (~30), runtime credential test (~25), minter count test (~15), port constructors (~25, Q1-7 A), recipe step (~6); ~205 lines. Visibility edits: 94 `pub(super)`→`pub(crate)`, ~150 `pub(crate)`→`pub` (items, fields, methods), 199 `crate::world::`→`crate::`, 32 `crate::world::`→`ghostlight::`; test 4 rewritten in place (~20 lines) | +1 lib crate, 0 features, 0 binaries, 0 new packages in `Cargo.lock`; 13 files renamed (~52k lines) |
| 2 | — | ~5 lines | 0 |
Net for L0: about +65 source lines, one crate, no feature. The growth is
boundary declaration (an 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 with no feature that can lift it, and five fixture wrappers
that existed only to work around private port constructors.
Actual, reconciled 2026-09-16 against `6ddabca` (Ghostlight) and `647e57e`
(gamecult-ops):
| Area | Estimate | Actual |
|---|---|---|
| Crate sources incl. in-module tests | about +65 net | +1202 / −992 (+210 net) |
| Integration tests | not planned | +479 |
| Manifests and lock | about +50 | +50 / −2 |
| Ghostlight recipe | about +6 | +10 / −5 |
| gamecult-ops | about +5 | +61 / −9 |
The miss is explained, not hidden. The single-minter scanner grew about 110
lines chasing Soul's evasions and remains evadable (S4). The boundary needed
roughly 90 more public items than this map enumerated, each forced by a
compile error or a `private_interfaces` warning. The integration tests buy
ruling Q1-9, which postdates the estimate. Features and binaries added:
none, as estimated.
## 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.
- `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; it is
why `CommitReceipt`'s four fields are in the boundary at all. Under
Q1-8 A the same literal serves `drive_one_tick`'s fake clock submitter.
- `PreparedInference.invocation` (`controllers.rs:131`,
`CodexTransportInvocation` from `codex-connector`) stays `pub(crate)`. A
consumer port that talks to a real transport would need it; no such
consumer exists in L0, and opening it is a port-contract decision for
the pass that brings one.
- `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.