Ruling L1-Q7 (a) persisted the instruction text so that editing a lens's
text in code neither breaks nor alters an in-flight session, but no test
proved a resume reads the stored text. The new test takes a real refused
row, rewrites it as a later build would find it (stored text differs from
the recorded lens's current text, invocation prepared under the stored
text), pushes it through the real store file, reopens, and resumes with a
fresh runner. Every inference after the resume carries the stored text,
the session commits under the same command id, and the row still records
the stored text and the drawn lens.
It pins resume only; nothing ties stored text to the recorded lens, which
remains a recorded cost.
Test-only; no production code changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every lens-observing controller test used a single-lens world, so the
call-site draw was forced and a draw from an unrelated command id survived.
The new test runs one refused round in each of eight fresh worlds under the
stock weights, where every lens can be drawn, and asserts the recorded lens
equals lens::draw over that world and the id the row is keyed by, with that
lens's text. It asserts its premise: the weights give every lens weight, and
in each world some other id draws another lens. An unrelated random id
agrees with the session's in all eight worlds about once in 8^8 runs.
Test-only; no production code changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The illegal-transition pin rewrote the recorded lens to Numen, but its
fixture draws under uniform weights with a random world id, so about one
run in eight the lens was already Numen and assert_ne! failed. The rewrite
now picks Patina when the recorded lens is Numen and Numen otherwise, so
the lens rewrite always differs from the row.
Measured: at ef64008, 3 of 40 runs failed at controllers.rs:10230 with the
recorded lens Numen; after the change, 120 of 120 runs pass.
Test-only; no production code changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 3 landed at dfb3c52..2bf8164. Soul's first pass confirms that
boundary sessions now resume after unrelated commits, that a resumed
session keeps its recorded lens across a weight change, that admission is
the same under every lens, and that the catalog and session identity are
unchanged. It also observed all three illegal transitions (lens, text,
ancestry), and a stale adopted draft is still refused by the kernel.
Going to a test-only fix batch:
- the illegal-transition test is flaky, failing roughly one run in eight;
- nothing pins that the call-site draw reads the command id;
- nothing pins that a session resumes with its stored instruction text,
which was the whole point of the Q7 ruling.
Recorded: stored text is not tied to the recorded lens (inside Q7's
accepted cost); a route cost change re-rolls a boundary session's lens and
orphans its row; the seed lane orphans rows the same way deficits do
(L1.f13). The ledger text no longer doubles "recorded".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`a_row_for_the_same_answer_under_another_world_or_jurisdiction_is_not_adopted`:
a real refused session's row, with its session's world moved to another real
world, or its jurisdiction moved to another real place of the same world, and
then stored under this answer's derived id. The step is `Superseded`, no
inference runs, and the row is unchanged. The row as it was written is then
adopted and commits, so the test is not an inert harness.
The command id already implies world, jurisdiction and digest, so only a row
whose key and session disagree reaches this rule. The test builds exactly
that row.
Builds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 1 marked `Lens::instructions` and `lens::draw` with
`cfg_attr(not(test), expect(dead_code))` while only their tests read them.
`select_answer` reads both now, so each marker produced an unfulfilled lint
expectation warning. `reaches_for`, `leads_with` and the moved
`ELABORATION_INSTRUCTIONS` carried no markers. No `expect(dead_code)`
remains in `lens.rs`.
Builds. The library's warning count returns to 9.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A new elaborator session draws its lens and authors its instruction text
once, and the checkpoint row carries both from then on. A resume reads the
row's session instead of demanding the fresh derivation equal it.
Session:
- `ElaboratorSession` gains `lens: Lens` and `instructions: String`
(`deny_unknown_fields` kept).
- `select_answer` derives the command id from world, jurisdiction and
answer digest, draws `lens::draw(snapshot.lens_weights, world, id)`, and
authors `lens.instructions()`. It returns the id with the session, and
`step` uses that id instead of deriving it a second time.
- `session_command_id` takes the three identity fields instead of a session,
because the lens is drawn from the id and so the id exists before the
session does. The derivation bytes are unchanged: the Cut 0 id
`56dbcdb2-9337-ac07-f322-37376b58cbcb` is pinned in
`a_session_identity_is_derived_from_its_answer`.
- `ELABORATION_INSTRUCTIONS` moves into `lens.rs` as a private constant.
`elaboration_request` takes the text, and `step`, `persist_repair` and
`integrity_is_valid` pass `session.instructions`. Integrity binds the
stored invocation to the stored text, not to code (L1-Q7 (a)).
Adoption rule (L1-Q9, boundary half): the whole-session supersession
equality in `step` is deleted. A found row is this answer's row when world,
jurisdiction, answer and answer digest match (`same_answer`). Its session,
including ancestry, lens and text, replaces the fresh one. A boundary session
is no longer `Superseded` forever after an unrelated commit.
`valid_elaboration_progression` is unchanged and still compares whole
sessions row to row; the doc comments on both rules say why they differ.
Store: `controller_work.v15` -> `v16`; v15 rows are refused at open, not
migrated. The world ontology doc names v16.
Tests (controllers): `a_boundary_session_resumes_after_an_unrelated_commit`
(at 1a402e1 it reported Superseded),
`a_resumed_session_keeps_its_recorded_lens_when_the_weights_change`,
`a_checkpoint_that_changes_the_lens_or_its_text_is_an_illegal_transition`,
`a_row_whose_text_and_invocation_disagree_is_refused_at_open`,
`a_controller_work_row_from_v15_is_refused_at_open`,
`soul_the_same_patch_admits_the_same_under_every_lens` (pinned to the
Cut 0 mismatch JSON), and `the_catalog_takes_no_lens`. The single-dead-end
fixture now delegates to `dead_end_mailbox(weights, dead_ends)`, which builds
the first dead end with the same handles and labels as before.
Builds, but with two "this lint expectation is unfulfilled" warnings from
the `expect(dead_code)` markers on `Lens::instructions` and `lens::draw`. The
next commit removes those markers (c1.s1.f3).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`SetLensWeights` refused an identical set by comparing raw maps, but a
lens a map does not name weighs zero. `{charter:3}` to
`{patina:0, charter:3}` therefore committed, and changing back committed
again, though both sets draw identically.
Equality is now `LensWeights::weighs_the_same_as`, owned in `lens.rs`: every
stock lens's effective weight is equal. The reducer's arm calls it; the
stored spelling is whatever the last committed set said.
Test, through the public mailbox in both directions:
`a_lens_set_spelled_with_or_without_its_zero_weights_commits_nothing`. A
sparse world set refuses a one-zero padded twin and an all-eight padded
twin; a padded world set refuses two sparse twins; each refusal is
`NoCanonicalChange` with revision, digests, operator log and stored
spelling unchanged; real changes commit.
Builds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The draw checked `draws()` over the map's values and then divided by a
sum over `Lens::ALL`. Those were two computations that happened to agree:
weakening the guard alone reached a modulo by zero. The draw now sums the
total first and refuses a zero total, as `select_band` does; `draws()`
stays the admission rule and no longer guards the draw.
Builds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fix batch landed at afca403 and acb9b33: identical lens weights are
refused as no canonical change, and the forged all-zero row is now caught
only by the draw rule. Soul's second pass found that the no-op check
compares map spelling, so a set padded with a zero entry still commits.
That fix is folded into Cut 3, next to the divisor guard.
The Cut 2 spec's claim that identical weights commit is now marked as
history. The implementation plan names a NoEffect variant that the kernel
never had; it now describes the NoCanonicalChange refusal that exists.
Operator rulings: the browser create form (L1.f11) is fixed in Dungeon
pass D2. Draft approvals (L1.f12) are deferred until multi-player
membership returns, and lens weights are excluded from any future binding
because they do not affect what an approver sees.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a_forged_lens_weights_row_fails_replay re-sealed only the commit digest, so
its all-zero forgery was refused by the resulting-state-digest mismatch and
still passed with draws() weakened. Each forgery now re-seals the whole store:
the head takes the effect's weights, its state digest is recomputed, the
commit's resulting state digest and digest follow, and the head's last commit
digest names the re-sealed commit; every seal is asserted consistent.
A drawing control re-sealed the same way replays, so no digest is left to
answer. The caller forgeries assert an ownership refusal and the all-zero
forgery asserts a draw-rule refusal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An owner's SetLensWeights equal to the world's current weights committed:
the revision advanced and the digests moved though nothing canonical changed.
`reduce` now refuses it with PatchRejected([NoCanonicalChange]) after
admission, so the live submit and replay share the rule and nothing commits.
The order is capability, owner, draw rule, then equality: a non-owner's
identical set stays Unauthorized, and an identical set that never draws stays
LensWeightsNeverDraw. The SetLensWeights doc stated the old rule and now
states this one.
Tests: the in-crate soul test's pin of the old commit becomes the refusal,
with the order pinned (non-owner identical is Unauthorized; an identical
all-zero set against a hand-built state is LensWeightsNeverDraw) and the
store reopened to the same head. external_admission gains
identical_lens_weights_from_the_owner_commit_nothing through the public
mailbox: revision, state digest, last commit digest and operator log length
unchanged, non-owner identical Unauthorized, a different set commits.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 2 landed at d06fd7e and 10be98c. Soul's first pass finds every promise
holding except the browser half of P2.6, which is unproven. The owner
check has a single source, and no ingress lets a caller supply its own
authority.
To fix before Cut 3: an identical SetLensWeights commits a no-op revision,
against the kernel's rule that a no-effect command commits nothing. The
forged all-zero journal-row test is caught by the digest, not the draw
rule.
Recorded for the operator: the browser create form never lifts its
bindings into the payload, for any field (L1.f11, relevant to the playtest
gate). Draft approvals are bound to nothing, so an approval survives later
weight and structure changes (L1.f12).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ontology names world_create.v4 with its required lens weights and the
consumer.v5 state and commit schemas; the consumer API names consumer.v5.
Docs only; builds as the previous commit does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lens weights are world data (L1-Q2), replaced only by an owner command
(L1-Q3), and required at creation with no library default (L1-Q6).
Library:
- WorldState.lens_weights, written by genesis through
ResolvedPatch.lens_weights (Some on the genesis lane only) and carried
on CreateWorld and CreateWorldIntent (required).
- CommandBody::SetLensWeights and WorldEffect::LensWeightsReplaced: owner
only, any phase; reduce decides and apply_effect re-decides owner and
draw rule. A set that never draws is Mismatch::LensWeightsNeverDraw at
genesis and in the command; verify_state_shape refuses a stored one.
- WorldSnapshot.lens_weights.
- State and commit schemas go to consumer.v5; v4 stores are refused.
- resolve_patch takes the weights; the action lane passes None.
Dungeon:
- CreatePayload.lens_weights required; world_create.v4 in the descriptor
and operation_schema; the eighth create binding and a lens weights
control defaulted to uniform_lens_weights(), which is Dungeon policy.
- The smoke harness states uniform weights. The stale-payload test now
denies v3, a v4 payload without weights (as a payload error) and one
without a brief; new tests deny an unknown lens and an all-zero set.
Tests: genesis writes and replays weights; an all-zero genesis is refused
and writes no file; only the owner replaces weights (non-owner, stranger,
elaborator, clock, consumer, stale revision, all-zero, hand-built effects);
weights enter state_digest and no scope digest; a history with a weight
change replays to the same digest; forged weight rows fail replay; a v4
store and an all-zero state row are refused; external non-owner refusal
and unknown-lens decode.
This commit builds (Windows only).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 1 landed at 835ea4d, and Soul's first pass records all five promises
as holding. External code can build an all-zero LensWeights through
new, Default or Deserialize but cannot reach the draw, because the lens
module and draws are private. So there is no crash path, which matches
L0's unforgeable-admission ruling.
Soul findings:
- duplicate keys are last-wins, like every BTreeMap field in the crate
(recorded);
- the all-zero guard and the divisor are different computations, a
hardening fix that goes to Cut 3;
- a leftover expect(dead_code) produces a warning, not a compile error,
which corrects Hands' claim.
Cut 0's captures and the census baseline are now recorded, and so are
Cut 1's ledger actuals. The ledger no longer says the draw lives inside
the session identity, which the Q4 revision removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
crates/ghostlight/src/lens.rs owns the eight stock lenses, each lens's
"reaches for" text and leads_with list of existing PATCH_TOOLS names, the
per-lens instruction text, LensWeights (a transparent BTreeMap<Lens, u32>
with an empty Default and no uniform constructor), and draw: a digest over
LensPreimage { world_id, command_id }, first sixteen hex as u64, modulo the
weight total, cumulative walk over Lens::ALL, Invariant error when no
weight is nonzero. Same recipe as select_band; no rand.
lib.rs declares the module, exports Lens and LensWeights, and gives the
library test module stock_weights(). ELABORATION_INSTRUCTIONS stays in
elaboration.rs (Cut 3 moves it) and becomes pub(super) so the lens text can
read it. instructions() and draw() carry cfg_attr(not(test),
expect(dead_code)) until Cut 3 reads them, following controllers.rs:759.
Nothing outside its tests reads the module yet. PATCH_TOOLS, the catalog
functions and the sidecar fixture are untouched. Builds on Windows; the
Linux build is Idunn's.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator ruled that an in-flight elaborator session is rediscovered by
reading state, not by re-deriving its key. Cut 4 adds one store method
listing in-flight elaboration rows, scanned once per sweep. The sweep
matches rows by world, jurisdiction and answer, ignoring ancestry, lens
and instruction text, and adopts the row and its id before any draw. The
command id stays the kernel's idempotency key and stops being the only way
to rediscover work.
New promises prove the fix, and each fails when adoption is removed: a
stranded deficit session is adopted with its refusal and without a
repeated round; a boundary session is repaired after an unrelated commit
and a clock tick; no orphan row accumulates over five ticks; real-shaped
non-matches are never adopted.
L1.f10 is resolved by Cut 3 for boundaries and Cut 4 for deficits.
Every question L1-Q1 through L1-Q9 now carries a ruling. Estimated total
+1,300 lines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator revised L1-Q4: the lens is recorded on the session and stays
out of the command id, because the id is the idempotency key and must be
a fixed property of the demand entry it answers. That dissolves L1-Q8 and
its eight-candidate-id probing. L1-Q7 is ruled: per-lens instructions,
with their text persisted in the checkpoint.
The operator then asked whether resume relied on re-deriving an id
instead of reading state. It does, and a probe found that the elaborator's
repair loop has never worked across sweeps (L1.f10, pre-existing, high).
A deficit session's key moves on any commit, so the next sweep repeats
the inference without its refusal and orphans the old row. A boundary
session is found, but its stored ancestry makes it Superseded forever
after any commit, including a clock tick.
Cut 3 carries the equality fix that boundaries need. L1-Q9 asks whether
resume should read in-flight rows by what they answer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six cuts against 817c9f4: captures, the lens module, lens weights as
world data with an owner command (consumer.v5), the lens on the session
and in the prompt (controller_work.v16), concurrent elaboration under split
permit pools, and doc reconciliation.
Status is derived rather than maintained. A question is open until it
carries a ruling with its authority, and a cut is closed when every
promise has a verdict.
The operator ruled on the seed, weights in the world, the owner weights
command, split pools, emphasis as prompt text, and explicit weights at
creation with world_create.v4. Q7 (where lens text lives) is open with a
probed recommendation. Q8 was defaulted and is under review.
Probes, all on Windows: two sessions on one answer collide today; a
lensed session survives reopen and resume under v16; storing instructions
in the checkpoint passes integrity through resume and repair; the Dungeon
test count differs by four Linux-only tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L1 gives elaboration in the ghostlight library a creator-controlled flavor.
The library ships eight stock lenses, a world carries lens weights as world
data, and each elaborator session draws one lens deterministically. Several
sessions may elaborate at once.
The target names eight invariants: lenses never decide admission, every
lens sees the whole catalog, the draw is replayable with no RNG, weights
are admitted honestly, concurrent sessions stay distinct, the drawn lens
is recorded and never redrawn on resume, stores refuse rather than
migrate, and no Dungeon concept enters the library.
It also records the constraints the substrate map found, and the open
questions Imagination must bring back. Status is derived from the cut map,
not maintained by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
map.yaml's next action listed the stock lens set as L1 and again inside
"the remaining library capabilities". The handoff still said the titled
elaborators "return as weighted lenses", which predates the ruling that
they ship as the library's stock lens set.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The closing Mind pass found four places still describing the tree as it
was before the extraction:
- the handoff's controllers path;
- plan step 15's heading, which said not started;
- plan D1, which still said "detail profile";
- the system map's source owner and schema name, and its private
world::WorldKernel.
Each now names the ghostlight crate and its sealed-by-admission boundary.
The boundary evidence record's first decision said the kernel's only
Dungeon import was a type that has since moved; it now points at the L0
record. The previous text is archived by compaction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Players are a Dungeon concept. The library's own term for a
human-controlled subject is ControllerMode::Human, so its test code now
says so: the principal fixture player() becomes human_principal() (a
local `human` subject binding in action.rs would shadow a bare human()),
and the genesis subject handle, labels, principal address, variables and
one test name follow:
- "player" handle -> "human"; "The Player" -> "The Human";
"Another Player" -> "Another Human";
"player@example.test" -> "human@example.test"
- player_opportunity, after_player, genesis_player ->
human_opportunity, after_human, genesis_human
- draft_activation_player_and_autonomous_actions_share_one_reducer ->
draft_activation_human_and_autonomous_actions_share_one_reducer
Test code only: no production code, schema string, serde name, pinned
digest or fixture file carried any of these values. The library test
count stays 419 passed and 1 ignored.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The stale-digest admission test now submits, besides the one-digit
forgery, the current issued digest with one hex letter uppercased, its
bare 64-hex value, and the same hex under a blake3: label. Each forgery
asserts its own premise before submission, and each must be refused as
ScopeChanged with revision, state digest, last commit digest and
operator-log length unchanged.
This kills the case-insensitive, hex-only (rsplit) and sha256:-stripping
mutants of the comparison in the kernel, which the previous eight tests
admitted, while the length, 7-character prefix and all-but-last mutants
stay killed. The cases extend the existing stale-digest test; the
external admission test count stays eight.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SeedPort's evidence is minted by the consumer authenticating a live
principal, and a quarantined controller leaves WorldMailbox and its
consumer authoritative. Neither comment needs to know the consumer is
Dungeon's AppSession.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both existing scope-digest forgeries differ in length from a real
`sha256:` + 64-hex digest, so a length-only comparison in
`exact_opportunity` passed every external admission test.
The new test takes two genuinely issued controller opportunities, lets
one subject speak (genesis places every subject in the commons, so the
telling lands as knowledge on the other and moves its scope digest), then
submits the listener's now-stale opportunity. It asserts the stale and
current digests differ and share a length, and that the submission is
refused with ScopeChanged and commits nothing.
A stale SHA-256 digest differs from the current one almost everywhere, so
it cannot catch a comparison that skips the last character. The test also
submits the current digest with only its final hex digit changed.
Mutants on the comparison, each killed: length-only, first seven
characters only, all but the last character.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fix batch landed at 7c1d1e9..2865ea7: external rejection tests,
the minter check by name, Eve removed from library text, and a narrower
public surface. Soul confirmed the rejection tests drive the real kernel
path from outside the crate, that the unchanged-state assertions are
sound, and that Hands was right about what holds three types public.
Soul also found the digest check is pinned only by forgeries of the wrong
length, so a stale same-length digest from an earlier revision is going to
a fix. The minter scanner is still evadable five ways; since every text
scanner in this migration has lost to a short evasion, Q1-10 asks whether
Clippy's disallowed-methods should replace it.
Recorded, not fixed: rustc on stable does not enforce compile_fail error
codes, the world_id check is redundant by construction, and the decline
path is not reachable from an external test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An item stays pub only when the compiler requires it for an API Dungeon
calls. WorldSnapshot's resources and routes fields were pub, which forced
ResourceSnapshot and RouteSnapshot pub; Dungeon reads neither field and only
the library's own controllers do. The fields and both types are now
pub(crate). The workspace builds with the same unique warning set.
Every other bare-pub identifier Dungeon never names was closed one at a time
under cargo check --workspace --all-targets. Each either fails to compile
because Dungeon or the admission tests use it through a public path, or adds
a private_interfaces warning from a public enum variant field or method that
Dungeon does use (KernelError, CommandBody, CausalBoundary, RefKind,
ControllerWorkCustody, NarrativeCheckpoint, WorldMailbox), or strands library
code as dead. Those stay pub.
ScopeDigest, WorkLane and ScopeComponents are in the second group: the
warnings come from KernelError::ScopeChanged, ControllerWorkCustody::Uncertain
and the NarrativeCheckpoint variants. NarrativeInterruption's
bound_scope_digest and fresh_scope_digest return &str, so they never forced
ScopeDigest open, and Dungeon calls both at runtime.rs:1286-1287 and
2022-2023, so they stay pub too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four error strings and four comments named Eve, which is a consumer's
interface and not a concept the kernel has. The errors now say what they
refuse — a command ID, an opportunity, persisted controller work — and the
comments name an operator interface or a consumer's payload.
Nothing pins these strings: the four `#[error(...)]` literals appear only at
their definitions, and no test reads a `Display` of them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The old count read one non-recursive directory, split each file at one exact
`#[cfg(test)]\nmod tests {` marker, and counted one literal. Soul walked past
it with an alias; a qualified `<T>::new(`, a file in a subdirectory, and a
production item placed after the test module walked past it too.
The check now walks `src` recursively and removes what each `#[cfg(test)]`
attribute guards by its own extent — balanced braces for an item with a body,
the first `;` or `,` outside parentheses for one without — so a renamed test
module is stripped and nothing after it is hidden. Construction is recognised
by scanning what follows the type's name, which admits `<T>::new(` and any
whitespace, and the one site found must be this owner's file.
Following the type by name means production may not give it a second name: an
`as` import or a type alias is refused outright rather than counted. What the
check still cannot see is a call that never spells the type, such as a macro
expansion or a generic instantiated elsewhere.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Q1-9 rules the seal as unforgeable admission rather than unconstructible
values, so the proof belongs where a consumer stands. An integration test
compiles as its own crate against the public API alone; these seven mint
their forgeries through serde, which visibility does not gate, and assert on
the typed error rather than on any message.
Six rejections — a forged scope digest, the digest string the deleted
ScopeDigest::fixture used to produce, a forged controller ID, a minted
SubjectId, a minted AffordanceId, and a wholly deserialized opportunity —
each assert that revision, state digest, last commit digest and the operator
log's length are unchanged. One control commits through the same port with an
issued opportunity, so the rejections cannot be an inert harness.
No manifest change: serde_json, uuid, chrono and tokio are already library
dependencies, and Cargo gives test targets a package's dependencies as well
as its dev-dependencies. No new production dependency and no feature.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
External code may hold a syntactically valid ID, digest or opportunity,
because Dungeon's Eve payloads deserialize them. What must hold is that
the kernel rejects every one it did not issue, proven by rejection tests
written from outside the crate. The compile_fail set stays for the
genuinely private items, with its limit stated: it pins a path name and
cannot see constructibility.
The target's first invariant now says that, so the claim matches what the
boundary does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The batch holds: 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; and a fail inside a command substitution does abort.
Soul then found the limit of the approach. An exact-line pin proves the
lines exist, not that they run: wrapping the block in `if false`, chaining
it behind `true ||`, or swallowing it in a heredoc keeps every pinned line
intact and the checker green. Abort on failure rests on an unpinned
`set -euo pipefail`, and a duplicated block passes unnoticed.
All three are recorded, not fixed. The script is a legacy actuator under a
do-not-invoke warning, retired at the deployment gate, and the authority
that really runs the tests is Ghostlight's Idunn recipe. Revisit only if
the gate keeps the script.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gamecult-ops 803f2c7 pins each of the four cargo steps as a whole
docker invocation, asserts the recipe order, and brings the runbook into
the wiring test. The four mutations that previously passed silently now
fail, and no pre-existing assertion was weakened.
The same defect class survives in three places outside this migration:
the web build container, the Codex Connector's cargo steps, and a fixed
18-line search window. All three are recorded as follow-ups.
Soul has not yet passed on the fix batch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The assertions Cut 2 added pin text, not invocations. Deleting the two
lines above the pinned cargo line turns it into trailing arguments on the
previous container: the kernel tests never run and the wiring test still
passes. Nothing pins the order of the four cargo steps, and the wiring
test never reads the runbook.
Those three are going to a fix batch. Two more are recorded, not fixed: a
redeploy of an already-sealed commit runs no cargo steps at all, which is
pre-existing deploy behaviour the operator owns, and the dated census docs
still show the old single-crate paths.
What held: the library step runs in the right branch with the same image
and mounts, its failure aborts the deploy, and the acceptance test path
resolves under the library crate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 2 landed at gamecult-ops 6aba281: the library test step now runs in
the deploy script, is documented in the runbook, and is asserted by the
wiring test. Without it a deploy would run 50 tests and skip the 420
kernel tests that moved to the library crate.
Two defects in this map's own spec, reported by Hands rather than worked
around:
- the assertion it specified was vacuous, because require_text is a
substring match and the given string is a substring of the
persona-projection line;
- the acceptance test path lives in a variable the spec did not list,
pinned by the wiring test and repeated in the runbook prose.
Changing the acceptance binding means sealed acceptance witnesses from
before this commit will miss and regenerate.
Soul has not passed on Cut 2 yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 1 landed at 108b691..f859d2c. Soul reran the builds, tests, census and
mutations itself and confirmed the behavior-unchanged claim; it found six
defects.
F1 is the one that matters: the seal is name-based. Public ID types,
ScopeDigest and DecisionOpportunity all derive Deserialize, so an external
crate can mint them from JSON and rebuild the fixture path ruling Q1-4
said must be unreachable. The reducer still rejects forged opportunities,
so kernel integrity holds while the invariant's wording does not. Q1-9 asks
the operator whether sealing means unforgeable admission or unconstructible
types.
F2 (bypassable minter count) and F3 (71 unused public items) are queued for
the fix batch. F4 names Eve in library error strings. F5 and F6 are
recorded, not fixed.
The map, map.yaml and the handoff now say the kernel is the ghostlight
crate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Completes the move begun in the previous commit, which does not build on its
own. `ghostlight` is now a library crate with an explicit public surface;
`ghostlight-dungeon` is a consumer that reaches the kernel through it and
cannot reach past it.
Deleted first: `mod world;`, the nine unused re-exports, the `#[cfg(test)]`
re-export block, `fixture_controller_opportunities`, the five inference
`fixture_*` items, Dungeon's own `VerifiedPrincipalEvidence`, and Dungeon's
direct `ghostlight-persona-projection` dependency.
The seal. `WorldKernel`, `WorldState`, `reduce`, `apply_effect`,
`CommandEnvelope`, `AuthenticatedCaller`, the journal writer, every ID
`issue()`, and `ScopeDigest::fixture` carry no `pub` of any kind and no
feature gate; the crate declares no `[features]`, so no build configuration
can lift them. Ten `compile_fail` doc-tests stand outside the crate and pin
each one to its error code.
The principal seam. The library owns `VerifiedPrincipalEvidence` with a public
`new`; `mailbox` still bounds it by `valid_until` at ingress. A Dungeon test
counts the minters in production source and fails at anything but one.
The port contract (Q1-7). `PreparedInference::prepare` (renamed from the free
`prepare_invocation`), `InferenceOutput::new`, and
`InferenceFault::{retryable, recovery_required, integrity_violation}` are now
the port's public constructors, so a consumer-implemented `InferencePort` can
answer without test-only wrappers. `TracingInferencePort` is an ungated
decorator.
The tick-ordering test (Q1-8) derives its cover from a real active world
through `derive_cover` and asserts two cells; its ordering and shared-tick
assertions are unchanged.
Behaviour is unchanged. The library's 420 unit tests are the base `world::`
set with the prefix stripped — the names diff empty. Dungeon keeps all 49 of
its names and gains two Soul tests the boundary requires. No schema string,
digest preimage, or store layout is touched.
Windows verification only; the Linux release is Idunn's build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pure `git mv` of the thirteen files under
`crates/ghostlight-dungeon/src/world/` into `crates/ghostlight/src/`, with
`mod.rs` becoming `lib.rs`. No byte of any file changes; all thirteen are
detected as renames so history follows.
This commit does not build. The crate graph has no `ghostlight` package yet
and `ghostlight-dungeon` still declares `mod world;`. The next commit adds
the manifest, the public boundary, and the consumer rewrite; the two are
pushed together.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rulings Q1-1 through Q1-6 are in the map's status header. The crate is
`ghostlight`. The Vault reader and the SDK port go in the library. The
library owns the principal evidence type. The persona projection crate
stays separate. The gamecult-ops cargo lines are edited, not retired,
and editing them is not an endorsement to run those helpers.
Ruling Q1-4 asked for the Dungeon tests that need sealed fixtures to move
into the library. Classification found none to move: all five are Dungeon
runtime tests. Two further rulings close it instead:
- Q1-7: the inference port's value constructors become the port's public
contract and the five fixtures are deleted.
- Q1-8: the cover ordering test derives its cover from a real world, and
the last fixture reaching a sealed ID constructor is deleted.
The sealed ID issuers and ScopeDigest::fixture stay private under every
configuration, with no feature gate anywhere.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Plan step 15 pass L0 extracts the world kernel from crates/ghostlight-dungeon
into its own library crate. The new target document lists the invariants
that must survive:
- sealed kernel visibility at the crate boundary;
- no change in behavior, schemas, or stores;
- no Dungeon concepts or dependencies in the library;
- a working release path;
- unaffected pins;
- bounded build fan-out.
It also lists the questions Imagination must bring back.
Two evidence records from 2026-09-15 disagreed about focus and whether the
extraction and title decisions were settled. They are now one boundary
record. The title correction now points at the ontology doc's lens section.
The compaction archived the previous records verbatim.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A detail rule is a function from place to detail level. Each consumer
chooses its own level-of-detail rule. Dungeon uses ByDistance anchored on
important subjects, the player first. Delvehold starts from Uniform.
Rules are typed world data that the library evaluates, not consumer code.
The deficit a rule produces is an admission input, and journal replay
re-runs reduce, so a changed consumer function would break committed
history. WorldScaleIntent per-kind targets remain the ceiling a rule
distributes.
The eight titles move into the library doc as the stock lens set. The
kernel extraction is ordered as plan step 15 pass L0, ahead of every
library pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ghostlight Dungeon is a consumer of the Ghostlight library. Session Zero,
Vault choice, players, sliders and starting locations are Dungeon authority
and do not shape the library.
The ontology doc gains consumer-neutral library targets: elaboration
lenses with weights, focus-weighted demand with a consumer-supplied detail
profile, one evidence source bound per world, and elaborator sessions in
Draft. The Session Zero doc now carries only Dungeon policy that consumes
those capabilities.
The map records the boundary as a hard rule. The plan splits into library
passes L1-L4 and Dungeon passes D1-D3. Two decisions stay open: extracting
the kernel from crates/ghostlight-dungeon, and whether the eight titles ship
as a library stock lens set.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The memory steward found four leftover lines. The plan and the ontology
doc now describe elaborators as lensed sessions, not eight fixed
instances. The handoff no longer says there is no Session Zero. The
ontology doc notes that Session Zero stops declaring the human at the
genesis commons.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The eight titles (Patina, Charter, Ledger, Hearth, Tangle, Veil, Ember,
Numen) were deleted with their titled verifier and quotas, and no operator
decision covered them. The ontology doc and evidence record 6 now say the
tribunal was the verifier and the quotas, not the titles. A new correction
record carries the history and the 2026-09-15 direction.
Session Zero is the player's path through a world's Draft phase. The player
chooses a Vault, describes the experience, and sets title weights. The seed
lane sketches the world and the player chooses a start. A swarm then draws a
title by weight for each session and elaborates by travel distance from the
player, before and after activation. It is plan step 15, and its fifth pass
closes the playtest gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The playtest gate goes ahead of the outbound consumer batch. The handoff
defines it and lists the walls found in source. The map no longer claims
co-op for up to eight players on the rebuilt kernel. It points the economy
spec at GameCult/Njordr and says CultCache comes from CultLib, not a
vendored copy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>