Commit graph

1,128 commits

Author SHA1 Message Date
Meta Vens
bd25d0fffe Sync the last surfaces after L1
The closing memory pass found four stale spots:
- The ontology still said the schemas are consumer.v3, contradicting
  consumer.v5 in the same paragraph. The sentence is gone.
- Evidence record 21 said each world carries a typed detail rule, which
  is L3 and not built. It now says a world will carry one.
- Evidence record 20 pointed at the design section. It now points at the
  landed L1 target.
- The system map's "Current acceptance body" describes the legacy release
  Yggdrasil still serves. It is now headed as pre-rebuild, so its titled
  worker text no longer reads as live.

Evidence is rewritten through compact-evidence, which archives the old
text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 20:10:02 +02:00
Meta Vens
576c8ac667 Close L1: postmortem, ledger, target and docs reconciled
The stock lenses pass is closed.
- The ghostlight library ships eight lenses.
- Worlds carry weights as world data, and the owner can replace them.
- Each elaborator session draws its lens replayably and records the lens and
  its instruction text.
- Sessions run concurrently under their own permit ceiling.
- In-flight work is found again by reading the store.
All eight target invariants hold.

The cut map records the second Cut 4 fix batch, Soul's final pass, Cut 5's
verdicts and the ledger's actuals. The actuals are +4,907 / −266 in crates
against about +1,300 estimated, the overrun overwhelmingly tests that caught
real defects.

Recorded as follow-ups: an Uncovered case the tests do not pin (L1.f15), and
whether consumers may supply their own lens sets (L1.f16, the operator's).

The target is rewritten as reconciled. The ontology loop no longer claims
seed requests or Vault evidence for the Active sweep, the handoff says
lens weights, and map.yaml names L2 as next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 20:08:25 +02:00
Meta Vens
b15ade4d08 Name world_create.v4 beside the seed producer in the MVP doc (L1 Cut 5)
The verification contract named creation only as step 8's `world.create` v2.
It now states the current payload, `world_create.v4`, with its required
fields including lens weights, and that Dungeon's create surface offers
uniform weights as its editable default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:56:30 +02:00
Meta Vens
04fd00416c Mark L1 implemented in the plan, handoff and map (L1 Cut 5)
Plan step 15 records L1 as landed at 835ea4d..872ba35 with its cut map, and
its heading names L2 next. Step 6 calls the elaborator's flavor a lens.

The handoff and state/map.yaml name the live schemas, consumer.v5 and
controller_work.v16, and world_create.v4 in the handoff; both say L1 is
implemented and what it gave the machine, and the handoff names L2 next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:56:20 +02:00
Meta Vens
39b2af4237 Describe the landed lenses and concurrent sweep in the ontology (L1 Cut 5)
"Elaborators" states the per-sweep claim set, the caller-owned ceiling and
Dungeon's two pools, and its loop takes a demand entry, resumes an in-flight
session answering it, and otherwise draws and records a lens.

"Elaboration lenses and detail rules" says which bullets L1 implemented and
which L2 to L4 still owe. The Lenses bullet describes the live mechanism:
weights required at creation, replaced by the owner through SetLensWeights in
any phase, a draw from world id and command id, lens and instruction text on
the checkpoint, LensWeightsNeverDraw and NoCanonicalChange refusals. The stock
set is closed. The cut list drops the sequential per-jurisdiction sweep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:54:47 +02:00
Meta Vens
872ba3525c Drop the answer digest from same_answer (L1 Cut 4, c4.s2.f3)
`step_session` compares a row found by command id against the fresh session
with `same_answer`. The command id is `session_command_id(world_id,
jurisdiction, answer_digest)`, so a row under the fresh session's id carries
the fresh session's digest unless its bytes were forged or the hash collided;
the digest clause never decided and survived as an equivalent mutant in two
Soul passes. It is deleted; world, jurisdiction and answer remain.

The id derivation is unchanged, and
`soul_the_session_command_id_derivation_moved_under_the_refactor` still pins
that a moved digest moves the id. Suite counts are unchanged and the distinct
warning messages equal those at 01312dd.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:52:35 +02:00
Meta Vens
2b4b833d1d Pin that a retryable fault does not stop the sweep (L1 Cut 4, c4.s2.f1)
The session permit's rule that a retryable error does not raise the sweep's
stop was unpinned: the existing retryable test uses a pool of two, so both
sessions spawn before either fails, and `held.stop = outcome.is_err()`
survived. Under a pool of one the second entry is spawned only after the
first session's permit is released; the first call faults as retryable and
the second session must still run and commit in the same sweep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:45:24 +02:00
Meta Vens
635123fbf4 Sweep Uncovered beside a root in deficit in a test (L1 Cut 4, c4.s2.f2)
`Uncovered` was pinned only for a world with no deficit row, so pushing it
only when no jurisdiction was named survived. The new test sweeps a world
whose commons root is in deficit and whose unpositioned debtor holds a
MissingStructure boundary no root covers; one sweep must run the deficit
session and a session under `Uncovered` for that boundary.

The unpositioned-debtor world moves into `unpositioned_debtor_world`, taking
the scale intent, and the existing no-root test uses it unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:43:51 +02:00
Meta Vens
01312dd45d Record L1-Q10, the panic ruling, and the Cut 4 fix batch
The operator ruled L1-Q10 option A: the in-flight listing excludes rows
ready to submit, and the crash window goes under L1.f14. The operator
also agreed to the panic handling. A session-task panic becomes an error
returned after its siblings finish, and the driver keeps running.
Quarantine stays with L1.f1.

The fix batch landed at 6f35229..0764842. Soul's second pass confirmed the
new permit guard releases exactly once on every exit path, including
cancellation, and that a panicked session is adopted cleanly. It found the
adoption rule reachable through the sweep only by ready-to-submit boundary
rows.

Findings folded into the Cut 5 pass: pin that a retryable error does not
stop the sweep, pin that Uncovered work is swept alongside a deficit, and
delete a proven-dead digest comparison. Recorded for L1.f1: a panic during
a checkpoint write would wedge the lane with only debug-level logging.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:41:04 +02:00
Meta Vens
0764842f6b Return a session panic from the sweep instead of unwinding it (L1 Cut 4, c4.s1.f4)
A panic inside one elaboration session used to resume-unwind out of
sweep, abort its siblings mid-inference, and end the runtime's
elaboration driver task, whose handle is dropped: elaboration stopped
silently until restart.

Now each session holds its permit inside a SessionPermit guard whose
Drop raises the sweep's stop before the permit is released, on every
exit but a clean return. A quarantine-class error disarms nothing, and a
panicking task never reaches the line that disarms it, so no session
starts after a panic, by the same path that stops sessions after a
quarantine-class error. The join loop turns a task that did not return
into ControllerError::Serialization("an elaboration session did not
return: ...") and returns it after every started sibling has finished.

The class is quarantine-class on purpose: sweep returns only
quarantine-class errors and debug-logs the rest, so this keeps that one
rule, and the driver's handling of any returned error stays exactly as
it is (debug log). Whether that error quarantines cognition is L1.f1's,
and is not decided here. Serialization is the variant the elaboration
lane already uses for its local invariants.

elaborate_world takes its interval, as drive_cover_tick does, so a test
can drive it at one millisecond; production passes
ELABORATION_SWEEP_INTERVAL unchanged.

Tests: a_panicking_session_lets_its_sibling_finish (the held sibling
commits in the panicking sweep, the error is returned, permits are
free, and a second sweep resumes and commits the panicked session);
no_session_starts_after_a_panic; and in Dungeon
the_elaboration_driver_survives_a_panicking_sweep, which waits for a
second sweep's listing and inference against the driver task ending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:19:34 +02:00
Meta Vens
4b5013050f Prove pool independence for player controller turns (L1 Cut 4, c4.s1.f2)
a_speak_turn_and_a_sweep_draw_only_from_their_own_pools reads both pools
from inside the provider boundary, with the call held there by a gated
port. A world.controller.act turn through the HTTP route holds one
simulation permit and no elaboration permit, and still reaches the
provider with every elaboration permit held. A sweep holds no simulation
permit and at least one elaboration permit, and still reaches the
provider with every simulation permit held. The readings, not timing,
carry the proof; a timeout only turns a lane blocked on a pool into a
failure instead of a hang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:12:59 +02:00
Meta Vens
a3d109bd5e Sweep work under the Uncovered jurisdiction in a test (L1 Cut 4, c4.s1.f7)
the_sweep_claims_work_no_root_covers builds a world with no declared
root, so no deficit row names any jurisdiction, and one open boundary
only Uncovered can claim: a positionless debtor's obligation to a
creditor who can neither command nor sue it (MissingStructure). One
sweep runs one session for it, recorded under Uncovered with that
boundary as its answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:10:49 +02:00
Meta Vens
c90b5a046c Pin the sweep's Active-only gate (L1 Cut 4, c4.s1.f3)
a_draft_world_is_not_swept sweeps a Draft world holding an open dead end
and a deficit: no listing read (so no draw), no inference, no row, the
pool whole, and the world's revision unchanged. WorldPhase has only
Draft and Active, so Draft is the whole non-Active case.

dead_end_world now delegates to dead_end_world_in(phase, ...), which
skips approval and activation for Draft; every existing caller still
gets the Active world it had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:08:35 +02:00
Meta Vens
6f35229f24 Pin Cut 3's adoption rule on the NoPatch path (L1 Cut 4, c4.s1.f1)
The sweep hands step_session an adopted row's own session, so the
adoption rule is reachable with a differing fresh session only through
rows the in-flight listing excludes: ReadyToSubmit and NoPatch. Until
now only the ReadyToSubmit test killed whole-session equality (M3.1).

a_finished_boundary_session_stays_finished_after_the_world_moves ends a
boundary session NoPatch, narrows the weights to a lens it did not draw
(moving the ancestry), and steps the entry again: the row is found by
id, recognised as this answer's row, ends NoPatch with no inference, and
its bytes are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:06:52 +02:00
Meta Vens
21edecaff9 Record L1 Cut 4's landing, verdicts and findings
Cut 4 landed at 8538fab..3e35b3d. Soul's first pass confirmed:
- distinct claims and the permit ceiling;
- deficit adoption, with no adoption across jurisdiction, world or a
  NoPatch row;
- a listing read once per sweep;
- claims taken before any await, and sweeps that cannot overlap.

Going to a fix batch:
- Cut 3's adoption rule now rests on one test;
- speak-turn pool independence is untested;
- the sweep's Active-only gate is unpinned;
- the Uncovered jurisdiction is never swept in a test;
- a panic in one session task silently kills the elaboration driver
  until restart. That fix makes a task panic an error returned after its
  siblings finish, and leaves quarantine handling to L1.f1.

L1-Q10 asks the operator whether the in-flight listing excludes rows
ready to submit. Soul measured both sides: including them stalls every
committed deficit forever; excluding them costs one repeated round and one
orphan in a crash window. L1.f14 records that stored rows are never
retired.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:58:26 +02:00
Meta Vens
3e35b3d006 Pin the id-only resume path and single-session permits (L1 Cut 4)
Test-only; no production change.

- a_boundary_row_ready_to_submit_is_submitted_after_a_tick: the listing
  returns only ElaboratorInFlight rows, so a boundary session that stopped
  between ReadyToSubmit and its submit is found by its id alone, and Cut 3's
  adoption rule decides whether it resumes. The test advances a refused
  session to that row through the real store and ticks the clock. The next
  sweep must submit the row under its id with no new inference.
  The test targets the mutation that restores whole-session equality in
  step_session. At bc31d11 that mutation survived every test, because
  adopting from the listing hands step_session the row's own session.
- one_answer_is_one_session_per_sweep now also records how many permits
  are free while its one session infers, and expects one. The target is a
  claim set that holds one entry twice. At bc31d11 that mutant passed this
  test, because the duplicate session resumed the first session's committed
  row.

This commit builds. Library 458 + 1 ignored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:02:14 +02:00
Meta Vens
bc31d1196f Put the elaboration call site and the quarantine stop under test (L1 Cut 4)
- Dungeon: the body of elaborate_world's loop becomes
  run_elaboration_sweep(&state). It checks quarantine, runs the sweep on
  state.elaboration_permits, and logs any error at debug, exactly as before.
  The interval loop is unchanged.
- saturating_one_pool_never_delays_the_other now runs its second half
  through run_elaboration_sweep. The production call site is therefore the
  one tested: passing controller_permits there fails the test, which was
  not true when the test called sweep directly.
- Library: no_session_starts_after_a_quarantine_error pins the sweep's stop
  rule. The pool holds one permit and the first session faults with an
  integrity violation, so the second entry never reaches the provider.

This commit builds. Library 457 + 1 ignored, Dungeon 53 + 1 ignored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 17:51:31 +02:00
Meta Vens
b3963f911d Run elaboration sessions concurrently over a per-sweep claim set (L1 Cut 4)
Rulings L1-Q4 (revised) and L1-Q9 (A).

Library (elaboration.rs):
- The sequential per-jurisdiction loop and step(jurisdiction) are deleted.
  ElaborationOutcome loses Clean and Inactive, which nothing produces now.
- sweep(permits) takes one snapshot and returns Ok unless the world is
  Active. It reads the store's in-flight listing once, builds the claim set,
  and spawns one step_session per entry into a JoinSet. Each session holds a
  permit from the caller's pool for its whole step.
- Every spawned session finishes before sweep returns. A quarantine-class
  error is returned after that, and no session starts after one has returned
  such an error. The sequential loop's early return also started no further
  session. Any other error is logged at debug.
- demand_entries is the claim set. For each swept jurisdiction it takes
  every covered boundary in derivation order, then the deficit if nonzero.
  Each entry is matched against the listing by world, jurisdiction and
  answer before anything is drawn. A match adopts the row's session and
  command id; if several match, the oldest is adopted and the rest are
  logged as orphans. An unmatched entry derives its id and draws its lens
  (fresh_session). An answer is claimed once, and so is a command id.
- step_session(command_id, session, snapshot) is the old step body. Its
  lookup and Cut 3's adoption rule are unchanged.
- step_in(jurisdiction), test-only, steps the first entry of one
  jurisdiction's claim set, so the Cut 3 tests keep reading one session's
  outcome.

Store: the listing now returns only ElaboratorInFlight rows. A committed
session ends in ReadyToSubmit, so listing that stage adopted a committed
deficit row, and every later sweep resubmitted it (AlreadyApplied) instead
of opening the next deficit session. That departs from the map's listing
contract.

Dungeon: elaborate_world and the ignored road test pass
state.elaboration_permits to sweep. The driver doc states that sweeps never
overlap.

Tests: 12 library tests.
- Concurrency: distinct_entries_run_at_once_under_the_ceiling,
  the_sweep_never_exceeds_its_ceiling, a_sweep_holds_a_permit_while_it_infers,
  one_answer_is_one_session_per_sweep.
- Claim set: a_boundary_under_nested_roots_is_claimed_once,
  a_deficit_entry_is_one_per_jurisdiction_per_ancestry.
- Errors: a_retryable_fault_ends_only_its_own_session,
  a_quarantine_error_is_returned_after_every_session_finishes.
- Rediscovery: a_stranded_deficit_session_is_adopted_with_its_refusal_intact,
  a_boundary_session_is_repaired_after_a_commit_and_a_tick,
  the_store_gains_no_orphan_per_commit, a_row_for_another_answer_is_not_adopted.

Dungeon adds saturating_one_pool_never_delays_the_other. The dead-end
fixture can now declare its commons (or commons and a ward) as a
jurisdiction root, with a subject that holds a goal, because a sweep visits
only jurisdictions that have a deficit row. The Cut 3 tests call step_in.

Docs: ghostlight-world-ontology.md states the concurrent sweep as the
shipped rule.

Counts: library 456 + 1 ignored, Dungeon 53 + 1 ignored. This commit
builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 17:44:04 +02:00
Meta Vens
6150bd7667 Split Dungeon's controller pool into simulation and elaboration (L1 Cut 4)
Ruling L1-Q4: neither lane may starve the other, and the elaboration pool is
a ceiling rather than a budget.

- AppState gains elaboration_permits beside controller_permits.
  controller_permits stays the simulation budget, which speak turns and
  cover cells draw from.
- configured_elaboration_ceiling() reads GHOSTLIGHT_ELABORATION_MAX_CONCURRENT
  at open, in the same shape as the simulation pool: default 2, minimum 1.
- The speak handler's comment now names both pools.
- The test fixture opens the second pool with TEST_ELABORATION_CEILING = 2.
- docs/architecture/ghostlight-dungeon-mvp.md names both variables and what
  each pool means.

This commit builds with one warning more than its parent:
elaboration_permits is never read. The next commit passes the pool to the
elaboration sweep, and the warning goes away there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 17:09:43 +02:00
Meta Vens
8538fab9a7 List in-flight elaboration sessions from the work store (L1 Cut 4)
ControllerWorkStore gains elaboration_in_flight(), a required method with
no default body. It returns every elaboration row in ElaboratorInFlight or
ReadyToSubmit, never NoPatch, with its command id, least recently stored
first.

- The CultCache store answers from its in-memory work map in one pass,
  after the same custody check lookup makes, and orders the rows by their
  stored_at.
- RecordingWorkStore answers over its map, in map order, because it keeps
  no write time.
- Dungeon's AlwaysFreshWorkStore returns an empty list.
- ElaboratorSession is now re-exported, because Dungeon implements the
  trait and must name the type. ElaborationCheckpoint::session is
  pub(super).

Nothing calls the method yet; the elaboration sweep reads it in a later
commit. This commit builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 17:09:00 +02:00
Meta Vens
d9b71c49fd Record the Cut 3 test-only fix batch
The batch landed at f63dfd1, b3b5053 and 2c3b513 with no production
change.

- The flaky illegal-transition test failed 3 of 40 runs before and passed
  120 of 120 after.
- The call-site draw is now pinned to the session's own command id across
  eight multi-lens worlds.
- A resumed session is proven to keep its stored instruction text, which
  was the operator's purpose for Q7.

Both mutations that previously survived, S3 and S5, now fail. Cut 4's Soul
pass reruns them in place of a separate pass on a test-only batch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 16:59:42 +02:00
Meta Vens
2c3b513b17 Pin that a resumed session keeps its stored text (L1 c3.s1.f3)
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>
2026-09-16 16:52:26 +02:00
Meta Vens
b3b5053dab Pin that a fresh session draws its lens from its own command id (L1 c3.s1.f2)
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>
2026-09-16 16:51:13 +02:00
Meta Vens
f63dfd12fc Rewrite the lens to one the fixture did not draw (L1 c3.s1.f1)
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>
2026-09-16 16:47:00 +02:00
Meta Vens
ef6400884a Record L1 Cut 3's landing, verdicts and findings
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>
2026-09-16 16:42:12 +02:00
Meta Vens
2bf81645ed Pin that adoption refuses a row under another world or jurisdiction (L1 Cut 3)
`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>
2026-09-16 16:13:37 +02:00
Meta Vens
4457fde138 Remove the dead-code expectations from the lens draw and text (L1 c1.s1.f3)
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>
2026-09-16 16:07:47 +02:00
Meta Vens
18b1098c3f Record the lens and its text on the session; adopt rows by answer (L1 Cut 3)
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>
2026-09-16 16:07:25 +02:00
Meta Vens
4334dd7394 Compare lens sets by effective weight in the no-op check (L1 c2.s2.f1)
`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>
2026-09-16 15:55:23 +02:00
Meta Vens
dfb3c52b01 Guard the lens draw's divisor directly (L1 c1.s1.f2)
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>
2026-09-16 15:53:04 +02:00
Meta Vens
1a402e1d45 Record the Cut 2 fix batch, Soul pass 2 and two operator rulings
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>
2026-09-16 15:45:58 +02:00
Meta Vens
acb9b3379d Re-seal the forged lens weights rows so only the rule refuses (L1 c2.s1.f4)
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>
2026-09-16 15:31:09 +02:00
Meta Vens
afca403fca Refuse identical lens weights as no change (L1 c2.s1.f1)
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>
2026-09-16 15:31:09 +02:00
Meta Vens
c8dfdd1a35 Record L1 Cut 2's landing, verdicts and findings
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>
2026-09-16 15:26:07 +02:00
Meta Vens
10be98cce7 Name consumer.v5 and world_create.v4 in the world docs (L1 Cut 2)
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>
2026-09-16 15:00:45 +02:00
Meta Vens
d06fd7ec2e Put lens weights in the world (plan step 15, L1 Cut 2)
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>
2026-09-16 15:00:45 +02:00
Meta Vens
3cb6a74bb0 Record L1 Cut 0 captures and Cut 1's landing and verdicts
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>
2026-09-16 14:40:45 +02:00
Meta Vens
835ea4d019 Add the stock lens module (plan step 15, L1 Cut 1)
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>
2026-09-16 14:26:21 +02:00
Meta Vens
ff766e8901 Rule L1-Q9: resume reads in-flight elaboration state
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>
2026-09-16 14:11:42 +02:00
Meta Vens
d3b2c0e464 Keep the lens out of command identity; find the stranded repair loop
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>
2026-09-16 14:04:56 +02:00
Meta Vens
b846c68a28 Map the stock lenses cuts (plan step 15, L1)
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>
2026-09-16 13:24:25 +02:00
Meta Vens
817c9f45ea Write the stock lenses target (plan step 15, L1)
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>
2026-09-16 12:45:03 +02:00
Meta Vens
ef04aa5e03 Stop listing the stock lens set twice; name it plainly in the handoff
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>
2026-09-16 12:41:46 +02:00
Meta Vens
d89f2602c2 Sync the last surfaces that described the kernel before L0
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>
2026-09-16 12:26:38 +02:00
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
Meta Vens
ab95d7897c Name the library's test subject human, not player
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>
2026-09-16 12:19:04 +02:00
Meta Vens
b14507c894 Pin the scope digest comparison to exact case and label
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>
2026-09-16 12:14:52 +02:00
Meta Vens
af9c95a5bf Name the consumer, not Dungeon's session type, in library docs
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>
2026-09-16 11:56:55 +02:00
Meta Vens
9e514ebf15 Close InferenceRequest::provider_model to the crate
Its only caller is the SDK inference router inside the library, and no
consumer names it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 11:56:55 +02:00
Meta Vens
bde05de4e3 Refuse a digest shaped exactly like the real one
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>
2026-09-16 11:56:55 +02:00