An ignored runtime test drives the production tick driver, cover,
elaboration sweep, and clock against a real CodexConnector on a genesis
world and logs what the operator would see; the fixture now takes an
optional live controller. Runbook in notes/local-live-smoke.md; the
first run (three ticks, Persona spoke each tick, elaboration clean) is
recorded as evidence.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The consumer-api doc is the live contract for the landed ingress; the
ontology's Current mechanism runs through pass 10 and its follow-ups;
the mvp doc names the live authority answer; the plan's step 6 is
complete with the outbound consumer response as the next seam; the map
and handoff carry the series-complete state, the world-v3 generation,
and the decisions the operator owns; one evidence record distils the
ten passes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Bump controller_work to v9 so a checkpoint from before the
session_command_id derivation change refuses to open, and document why
no migration exists.
- Make a malformed GHOSTLIGHT_CONSUMER_CREDENTIALS file fail startup
instead of warning into an empty registry.
- Redact ConsumerPatchDocument's secret from its Debug impl.
- Route the elaboration lane's draft through the same check_patch_caps
the consumer ingress uses, closing the unenforced MAX_PATCH_EVIDENCE gap.
- Give the three item-cap refusals their own ConsumerRefusal variants
instead of flattening them into TooLarge.
- Delete journal_replay_refuses_a_forged_consumer_row, now covered by
soul_a_forged_decision_event_on_a_mirror_is_refused_at_replay.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Soul, pass 10. Nine tests, no production change.
Two claimed proofs were not proofs. `journal_replay_refuses_a_forged_consumer_row`
guards its whole first assertion behind `if let Some(event) = state.events.first()`
and the mirror fixture commits no decision, so that half never ran; it now runs
under a committed decision. The deficit half of the boundary test lands on
`AnswerNotDerived` because its fixture names no scale intent, so the answer rule
refuses before the ground rule is consulted; a scale-intent fixture derives a real
deficit and proves `Unauthorized`.
`session_command_id` moved to `CommandId::derived` and the two spellings hash
different byte streams. The new test pins the fact: a session checkpointed before
this pass resumes under a different command id.
The rest: the byte cap ahead of serde, every ingress refusal shape against
snapshot equality and replay, the mirror re-scoped onto a real committed decision
at `apply_effect`, a mixed owner/elaborator/consumer history replayed to the same
world, the pass-9 state schema string refused by name, derived-key determinism and
part unambiguity, and the registry's error and Debug paths carrying no secret.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twenty-three tests: the three Delvehold negative proofs (an ordinary
strategic wave cannot select an externally controlled mirror through
opportunities, adjacency, the cover, or a hand-built opportunity; a malformed,
oversize, stale, or structurally broken batch commits nothing; a consumer's
transfer into a Ghostlight subject is refused on the unbound endpoint while
the owner's identical transfer commits), the inbound-bytes-to-receipt run with
its journal replay, idempotent resubmission and key conflict, the capability
pairing, the confinement rows, the phase rule in both directions, the shared
decode bound, the transport gates on the new route, and replay equality with a
prior-schema store refused.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut first: the elaboration lane's two draft caps and its own sha256-to-uuid
command-id spelling, `jurisdiction_covers` and `confine_to_jurisdiction` as
jurisdiction-only names and signatures, the flat empty-affordance invariant,
and the two total controller accessors that were keeping a fictional
controller so a reader could stay infallible.
Then widen. `PatchGround` names what a confined author may write: a place
subtree, or every subject bound to one consumer, derived from
`controller_assignments` at decision time and never carried on the wire.
`SystemCapability::Consumer` is admitted for `AdmitPatch` and nothing else, in
the same closed pairing `reduce` decides and `apply_effect` re-decides.
`ControllerAssignment::ExternallyControlled` is a subject the world can see,
relate to, and reason about but never puppet: no controller id, no mode, no
affordance, no opportunity, no adjacency, and a forged history in which it
acted fails replay.
`patch::decode_patch` is the one decode owner for a `WorldPatch` from a byte
source, with one byte cap applied to the frame before `rmp_serde` sees it and
three item caps checked before `resolve_patch`; the elaboration lane reads the
same two item constants it used to own privately.
`world/consumer.rs` owns the ingress and no world truth: one typed document
over one loopback route, canonical MessagePack in and out, a registry that
holds only SHA-256 digests, one derived command key, one port method, one
atomic verdict. The state and commit schemas bump with the caller tag; prior
stores are refused.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Current mechanism describes the budgeted connected cover, the tick
driver as the single clock owner, and the grouped-cell membrane rule;
the scale section carries the ceil(N/R) bound, the two reasons the cap
yields, and the contention finding with its open operator decision; the
two stale claims about AdmitPatch phase and EvidenceRef are corrected;
the consumer-api drift note names the decided transport; controller
model variables are documented. Steward folded passes 1 through 9 and
the pass 10 design state.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Follow-up to the pass-9 follow-ups: deviation 2 flagged that
run_cover_tick's per-cell quarantine check only ran once, before a cell
contended for its permit, so a sibling already parked behind the permit
pool when another cell's fault raised the flag would still run once the
permit freed up. Add a second check right after permits.acquire(): with
the pool sized to one, the second cell cannot be granted the permit
until the first has fully returned, and for a faulting cell that means
the flag is already set by then, so the post-acquire check is
deterministic rather than a race. The top-of-function guard against a
later tick's cells stays as-is.
Renamed and extended
quarantine_raised_mid_tick_stops_every_later_tick_from_reaching_the_port
to quarantine_raised_mid_tick_stops_the_sibling_cell_and_every_later_tick:
same cross-tick assertion as before, plus permit pool 1 and an exact
(not >=1) count of one call in the first tick, proving the sibling cell
never reaches the port once the first cell's fault quarantines the
organ. Ran the new test 8x in isolation with no flakes.
world:: 281 passed, full crate 320 passed (unchanged counts — this
extends an existing test rather than adding one). cargo fmt -p
ghostlight-dungeon -- --check is clean.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pass 9 follow-ups 1-3 from Soul's review:
- Widen ControllerRunner::with_test_ports, InferencePort, and
ControllerWorkStore to pub(crate) (still cfg(test)), plus the small
cfg(test) seam needed to implement them outside world::controllers
(fixture_prepared_inference, fixture_inference_output,
InferenceFault::fixture_integrity_violation, and cfg(test) pub(crate)
re-exports from world::mod). Two new runtime spec tests drive a real
ControllerRunner over a counting/quarantining InferencePort and an
in-memory ControllerWorkStore, proving the controller-permit semaphore
never admits more concurrent run_cell calls than its pool, and that an
integrity-violating fault quarantines the organ so no later tick's
cells ever reach the port again.
- Extract drive_one_tick out of drive_cover_tick's loop body, narrowed
the same way submit_clock_tick narrows the clock port: run_cover and
submit_clock are opaque closures, never run_cover_tick or
WorldMailbox::submit_clock by name. A new test drives it with a fake
cover-runner over a real three-cell Cover (via a new cfg(test)
fixture_controller_opportunities in world::mod) and a fake clock
submitter, proving the clock is always submitted after every cell has
run and that every cell in the tick carries the cover's one tick index.
- Delete cover.rs's merges_are_deterministic_in_subject_order: its name
overclaimed determinism the test never checked. Its coverage-survives-
a-permutation assertion was already proven by
soul_b_the_cover_is_pure_and_the_attention_order_is_a_real_input; its
other assertion (cell count is stable under permutation) was not, so
that one assertion moved into soul_b's test instead of surviving under
a misleading name.
Deviations from the literal brief, both load-bearing:
- Ruling 1 asked for "a cover of several cells" with
GHOSTLIGHT_CONTROLLER_MAX_CONCURRENT = 2. The production world-create
intent caps a genesis world at exactly two controller-bearing subjects
(one narrative persona, one operational agent), so the real cover this
test can derive tops out at two cells. Reusing that production ceiling
meant not widening the kernel's private command types (CreateWorld,
CallerId, CommandEnvelope) past what this pass already opens, which
seemed like the more coherent boundary to hold. The permit pool is set
to one instead of two so the cap is still load-bearing: with two cells
and a pool of two, the assertion would be vacuously true.
- Ruling 1's second scenario wanted "the remaining cells are skipped"
within one tick. Tracing run_cover_tick's per-cell task shows the
quarantine check runs once, before a cell ever contends for its
permit; every cell in one JoinSet is spawned and gets that check
before any of them can reach the port and set the flag, so no cell of
the *same* tick is ever actually skipped by it — only a cell of a
*later* tick is, via run_cover_tick's own top-of-function guard. The
test proves that real mechanism (a second tick makes zero further
calls) rather than an intra-tick race the driver does not resolve,
which would have been flaky either way it landed.
world:: 281 passed (baseline 282, minus the one deleted test).
Full crate 320 passed (baseline 318, minus one deleted plus three new).
cargo fmt -p ghostlight-dungeon -- --check is clean.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tests only. Ten additions across the two organs pass 9 landed.
world/cover.rs, pure over `derive_cover` with no kernel:
- the rotation guarantee at the design profile, not at a toy size: 2,400
subjects, B=240, C=24, U=36 gives R=110 and the window closes in exactly
ceil(2400/110)=22 ticks, and not in 21;
- urgency reads the attention order while the window reads ascending id
order, proved on a fixture whose two rankings are reversed against each
other, so no third ranking can explain the result;
- the cover is pure for identical inputs, and the attention order is a real
input rather than an incidental one;
- every cell in one cover carries that cover's tick index;
- derived cell and constituent keys are collision-free across a full sweep
of ticks;
- the cap yields when component shapes cannot pack inside the budget even
though token capacity would allow it, and the flag tracks the partition
rather than the subject count.
world/controllers.rs:
- an uncoupled grouped cell commits every constituent, which is what makes
the coupled cell's refusal a fact about coupling instead of a per-cell
quota or a moved world revision;
- the refused constituent's scope digest really did change;
- the other grouped checkpoint resumes: a retryable fault parks the row at
AgentInFlight, and the resumed run re-derives the same constituent ids,
infers once, and commits once;
- a grouped tick replays from genesis to an identical state digest and an
identical event sequence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut the accessors, fields, and re-exports the pass added without a live
consumer: `Cell::id`, `Cell::tick`, `Cell::resolution`, the grouped
capture's receipt vector, and the `CoverBudgetError` re-export. The
resolution a row records is derived from the row's own shape, and the
capture's receipts were a second copy of what its outputs already carry.
In exchange the tick driver now reads what it runs. One debug line per
coarse cell names the cell, its resolution, how many turns it offered,
how many committed, and how many gaps it produced. The gap between
offered and committed is the number that says a batch buys one
inference and never one admission rule.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Step 6 pass 9: the budgeted connected cover. Every active subject is in
the cover every tick; the budget decides at what resolution, never
whether. A singleton cell keeps the detail path and its membrane; a
grouped cell is one inference over N labeled, never-unioned constituent
views, each lowered into an ordinary one-opportunity submission verified
by its own opportunity. The kernel never sees a cell.
`world/cover.rs` owns the partition and nothing else: pure over
(world, now, opportunities, agency graph, budget), disposable, dropped at
end of tick. The fairness guarantee is arithmetic over the clock and a
total subject order rather than a second persisted memory. Adjacency is a
kernel-side projection wider than any subject view, reachable from the
tick driver and from no prompt builder.
Cut before adding: the runner's exclusive mutex and its three lock sites,
`controller_slot.take()` as the quarantine mechanism, `select`'s equation
between representation and controller mode, and pass 7's standalone
`advance_world_clock` task, which `drive_cover_tick` absorbs so one owner
holds the tick's cadence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Current mechanism describes the two AdmitPatch authors, jurisdictional
confinement at answer and write, the tool surface as one schema spelling
per type, and the evidence-bounded repair loop; Invariant 11 and
Verification 16 carry the expansion rule; the consumer-api drift note
names pass 10's four debts. Steward folded passes 1 through 8 and the
in-flight pass 9.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- variant_content now spells a payload-less branch, so an effect slot's
bounds schema offers `none` alongside `quantity` and `cost` and a world
author can declare an unbounded slot. The falsifying test is inverted
and renamed.
- composite_example is now a projection of composite_schema, walked
generically (first branch of every sum, minimum of every bounded
integer, a fixed canonical string, an empty list where allowed)
instead of a hand-written third spelling. The round-trip test checks
every schema-derived example against serde directly, so the standalone
tie test pinning the one known disagreement is redundant and deleted.
- The mod.rs test claiming to refuse a forged elaborator row never forged
anything; journal.rs already carries a real version that rewrites the
recorded caller and re-seals the commit digest. Deleted the duplicate
with the lying name.
- Added every_declaration()/every_component_op(), each built from an
exhaustive match over their enum that fails to compile on a new
variant, and a test proving every exemplar is accepted by exactly one
PATCH_TOOLS entry with counts matching the catalog (7, 28, 37) — a new
variant can no longer ship by mapping onto an existing tool.
- Removed the dead-code-only assert_declarations_exhaustive/
assert_operations_exhaustive helpers and their unfulfilled
#[expect(dead_code)]; their logic now lives in the exemplar builders
above and is actually exercised.
world:: 250 passed (was 252; two tests deleted as redundant/duplicate,
one test replaced net-neutral). cargo fmt clean. No new warnings.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Soul's tests only. Fifteen checks against the landed shapes: the
confinement predicate past its declaration lane, the fifth body the
capability rule refuses, disjoint boundaries committing independently
with a spent answer refused, an unsatisfied answer through the
elaborator lane, canonical provenance surviving the ungating of
`EvidenceRef::new`, a forged elaborator commit row re-sealed against its
own chain digest so replay's authority half must answer, the emitted
tool schema against the decoder branch by branch, the round-trip
examples pinned as a second spelling, the unbounded effect slot the
catalog cannot express, the elaboration runner's resume-and-repair round
trip, a clean jurisdiction spending no inference, `controller_work.v7`
refusing `v6`, and the custody discriminator's lane.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pass 8: the in-kernel elaborator loop, the Elaborator caller, and the
derived patch-tool catalog.
`SystemCapability` becomes internally tagged and gains
`Elaborator { jurisdiction }`. `require_patch_author` decides who may
`AdmitPatch` and returns the caller's confinement;
`confine_to_jurisdiction` decides what that caller may write. Both are
called from `reduce`'s arm and re-called from `apply_effect`'s, so replay
re-decides authority for free. `require_owner` leaves the `AdmitPatch`
path and survives for `ApproveDraft` and `ActivateWorld`.
Boundaries are covered transitively through `covers_place`; deficits are
covered by exact key equality, because a subject under nested roots counts
toward both roots' targets and a parent answering a child's row would
reduce two targets with one subject.
`patch.rs` gains `PATCH_TOOLS` and its two generators: the tool surface is
the variants a non-genesis patch may carry, with every always-refused
choice removed rather than exposed. `world/tool_schema.rs` is the one
emitter for every model-facing schema in the tree; the interpreter and
action catalogs move onto it and their `json!` literals are deleted.
`world/elaboration.rs` owns the loop: it selects a derived answer,
retrieves evidence, accumulates a draft across tool calls, submits through
`WorldMailbox::submit_elaboration`, and repairs the same draft from the
complete mismatch set. The draft is re-derived from persisted inference
outputs; only `last_mismatches` is stored, because it came from the kernel
rather than the model. Session identity is derived from the answer's
digest, so a crashed loop resumes the same store row.
Schemas bump to `elaboration.v1` and `controller_work.v7`; a prior store
is refused.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Current mechanism describes the clock, commitments, pressure, causal
boundaries, scale deficit, and the structural answer rule; the Commitment
and Pressure rows and the twenty-seven operation count follow the cut;
the mvp doc no longer names a readiness partition. Steward folded passes
1 through 7 and the in-flight pass 8.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- verify_state_shape now enforces the 1..=MAX_ROUTE_COST span bound on a
routine's period, the same bound TickMinutes::new enforces, closing the
serde-transparent forgery gap. Inverted and renamed the falsification test
to soul_a_zero_period_is_refused_by_the_shape_check.
- Extracted advance_world_clock's per-tick body into submit_clock_tick, a
narrow port over submit_clock, and added a test that captures the
submitted span without driving tokio's timer.
- Removed the dead top-level PreconditionRef/PressureSourceRef imports in
world/mod.rs; clock_tests keeps its own.
- Renamed JurisdictionKey::Unplaced to Uncovered and fixed the scale deficit
to count a positioned subject under no jurisdiction root in that row
instead of dropping it, with a test asserting the row counts sum to the
subject count.
cargo test -p ghostlight-dungeon --bin ghostlight-dungeon world:: -> 217 passed
cargo test -p ghostlight-dungeon --bin ghostlight-dungeon -> 251 passed
cargo fmt -p ghostlight-dungeon -- --check -> clean
A tick leaves an UnelaboratedDestination's digest where it is, and
closing the route it names moves it while the predicate still holds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ten falsification tests over pass 7's claims: the attention order's id
tiebreak under every input permutation, the Active answer ruling
(component-only commits, evidence must answer, the gate precedes the
resolver, a satisfied answer clears its boundary), pressure saturation
and underflow, a clock-only tick moving no scope digest, scale intent as
genesis-only, discharge of a foreign or discharged key, one routine roll
per command, derive_motion purity plus tick replay identity, the replay
gate over a forged motion, and the TickMinutes store gap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twenty-two tests over the pass-7 surface: the tick's three stages and its
re-derivation at apply and at replay, the clock capability's single admitted
body, every commitment shape check as one complete sorted set, the attention
order's totality and its refusal to filter, boundary derivation and the
answer-must-be-satisfied rule, and the scale deficit that only admitted
subjects reduce.
Two defects the tests found and this commit fixes: PressureSource mixed struct
and newtype variants under an internal tag, which cannot serialize a scalar
newtype, so it is adjacently tagged; and Motion carried an is_empty no caller
outside a test used.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ontology widening pass 7. The world acquires a fictional clock advanced by
exactly one command body; promises acquire a due date; an unmet promise becomes
stored pressure; pressure orders whose turn it is without deciding whether a
turn exists; and the places the world has not grown into become derivable
rather than noticed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Current mechanism describes facts, channels, knowledge, speech fan-out
over the declared reach, the two homes of a statement, and the
controller port; the Channel row and Precondition block follow the cut.
Steward folded passes 1 through 6 and the in-flight pass 7.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- audience() no longer folds a channel's controller into its own
audience: a controller may still can_broadcast() from outside its
reach, but it gains no fan-out knowledge and is not a CanReach target
unless it actually stands inside the declared reach.
- Correct the "one home" doc claims: the statement lives in
facts[fact].statement (the readable home) and is duplicated inside
the committed AssertClaim effect as a replay witness only; no
projection reads the event copy.
- ControllerRunner now holds a narrow ControllerPort instead of a whole
WorldMailbox, forwarding exactly the five requests a controller lane
makes. operator_log has no method on that type, so it is a compile-time
boundary rather than a pub(crate) convention.
- Document why resolve_patch's candidate knowledge map safely skips
modeling Communicate's fan-out (AuthoredSource has no Told variant),
and pin that invariant with a test on the one ComponentOpKind lowering
site that could otherwise drift.
- Add a test proving a stale controller_work.v4 row is refused at open.
- Length-prefix derive_id's discriminator so Some("") and None (and any
other handle/discriminator split) cannot collide; derived IDs change
as a result, which the v5 schema bump already requires callers to
accept.
Baseline 178 world / 213 crate; now 181 world / 216 crate, cargo fmt
clean.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Nine test-only additions in world::soul_knowledge_tests. Two pin behaviour
that differs from the specified shape rather than endorsing it:
- a channel's controller is folded into `audience` itself, so it receives
every telling on its own channel even when standing outside the declared
reach;
- the utterance is stored twice, in `facts[fact].statement` and again in the
committed event's own `AssertClaim` effect.
The rest confirm the gates: every speech precondition rejects at `exercise`
with the whole state byte-identical, a forged wider audience or appended
telling dies in `apply_effect`, an unheard utterance moves no scope digest
including the speaker's, the same-patch telling-then-forget asymmetry is
fail-closed only, an orphan claim is a Draft shape unreachable in Active,
replay is exact across all six new arms twice over, and `operator_log` is
unscoped by construction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Speech stops being a broadcast event everyone can read and becomes a typed
claim that some subjects hold. Three partitions carry it: `facts` (one row per
`EntityKind::Fact`, write-once), `channels` (one row per `EntityKind::Channel`),
and `knowledge`, which is the sole owner of who-heard-what.
`state.events` is demoted from perception surface to causal record. The events
log records what was done; the knowledge partition decides what a subject may
act on and what its projection may contain. `WorldSnapshot.events` is deleted
outright, so `controllers.rs` structurally cannot read an unscoped log; the
human operator's story feed moves to `WorldKernel::operator_log`, reached
through its own mailbox request and passed to `eve::authenticated_surface`
beside the snapshot.
Two pure functions state reach and nothing else does: `audience` (who is in
earshot) and `fan_out` (who gains knowledge from one telling). Co-location is
the actor's exact place; a channel's `Reach::Place` is the subtree, through the
same `covers_place` walk a jurisdiction uses. A telling never overwrites a
holder and `Believed` is fixed by the kernel, so no speaker owns another
subject's credence.
The kernel Speak entry gains its real shape: one `CanBroadcast` precondition, so
an unplaced subject fails with `NoAudience` rather than being heard by everyone.
A `carries_speech` entry must name exactly one audience, and `exercise` prepends
two kernel-owned operations no slot or band can influence: `AssertClaim` mints
the fact, `Communicate` fans it out. That makes `action::exercise` the second
`derive_id` call site; the "only `admit_resolved` allocates" rule is rewritten
rather than amended.
The kernel does not compare two statements. There is no dedup, no interning, no
contradiction check, and no similarity. Deception is a `Claimed` fact standing
beside its asserter's own knowledge of a different one, and the meaning of that
gap belongs to a reader.
`STATE_SCHEMA`/`COMMIT_SCHEMA` become `.knowledge.v1` and the controller work
schema moves to v5; a pre-pass store is refused with no migration path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Current mechanism describes authority, selection, and redress with the one
covering predicate, the admission-only nesting rule, and the shared
grant/revoke envelope; the selection row and operation count follow the
landed cut. Steward folded passes 1 through 5 and the in-flight pass 6
into the handoff and map.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds an admission-time check in GrantAuthority resolution that rejects a
Subject grant nested inside a same-kind PlaceSubtree grant the holder
already has, and the reverse, using the live covering predicate. This is
admission-only: targets_overlap and verify_state_shape stay structural and
position-independent, and a later Relocate that creates the same nesting
is never re-derived as a violation.
Extends the action lane's check_delegation to gate RevokeAuthority slots
by the same monotonicity envelope it already applies to GrantAuthority:
an actor may not strip authority it does not itself hold. Adds a fixture
"revoke" affordance to exercise both the covering and non-covering cases.
Adds an end-to-end test for the fixture's "deploy" affordance (Relocate
lowering) plus its precondition's negative case.
Updates the two existing tests whose assumptions depended on the old gap
(a Subject grant silently nesting inside a PlaceSubtree grant) to assert
the new rejection instead.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Seven tests, no production change. Each one asks whether a claim fails at
the gate the spec names rather than somewhere downstream that happens to
catch it.
`DelegationNotMonotone` now has a test that walks `submit`, not the
`draw_once` bench, and asserts the refused act moved no revision, no
grant, and no event. The `Restricted` rule is exercised at
`apply_operations` with a forged `ResolvedOp` that skipped the resolver,
which is the only lane the existing test did not cover. The rebind
requirement is asserted at the layer an operator sees it: a revocation on
the granter refuses the incumbent's already-bound proposal with
`ScopeChanged`, at the current revision, so nothing but the stale scope
digest can be doing the refusing. `reduce` is shown pure across every new
civic arm and equal to what the commit stored.
Two tests pin decisions the spec did not make. Structural overlap cannot
be decayed by a `Relocate`, and the store survives one; but it also does
not see a `Subject` grant sitting inside a `PlaceSubtree` grant of the
same kind, so one subject can hold two sources of one permission and
revoking the wider one leaves the act standing through the narrower. A
destination declared in the same patch as the restricted route into it is
covered by no grant, so a genesis patch cannot declare a room and walk
anyone into it in one batch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A subject stops being able to do anything to anyone it can name, and starts
needing a jurisdiction that covers the target: held directly, or lent to it
by an institution through an office it occupies.
Three partitions on `WorldState`, written only by `apply_operation` and
validated only by `resolve_patch`: `authority` maps a subject to grants of a
world-declared kind over a subject or a place subtree, `selection` maps an
institution to its offices and their incumbents, and `redress` maps a
grievance kind to a forum and its standing. Eight `ComponentOp` variants
reach them through the existing `AdmitPatch` lane, and four
payload-carrying `ComponentOpKind` variants reach them from an affordance's
effect slots.
One covering predicate, `covers`, answers every jurisdictional question:
`Precondition::Authorized`, `Precondition::HasStanding`, the delegation
monotonicity rule, `route_admits`, and the snapshot's redress projection.
`AccessKind::Restricted` now names the authority kind that opens the route,
so a restricted route admits exactly the subject holding that kind over its
destination — checked identically by `resolve_patch`'s `Relocate` arm, by
`apply_operation`'s, and by `Reachable`'s edge admission.
An office's delegated grants are copied into its incumbent's
`ScopeComponents`, so one scope digest covers the grant, the office, and the
institution's jurisdiction: revocation, reconstitution, installation, and
vacancy are all rebinds. What the actor reaches stays out, so a target that
walks out of a jurisdiction fails at `Authorized` instead.
`OverlappingJurisdiction` makes it impossible for one subject to hold two
grants of one kind over overlapping ground, so nothing ever arbitrates
between two sources of one permission; overlap between subjects is layered
government and stays legal. The reserved `actor` role binds the acting
subject, which is what makes a levy's payee unforgeable.
Six world-authored fixture entries — levy, delegate, deploy, sanction,
appoint, petition — are the worked examples. No decree and no contract:
their effect is `Commitment`, and without it either lowers to speech in an
institution's coat.
Schema moves to `ghostlight.world_state.authority.v1`; a previous store is
refused with no migration adapter.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`action::exercise` read `invocation.affordance` and looked the entry up
itself, with the grant check standing beside it in each caller. Pass 5 adds
a third caller, so the check becomes the type: `require_granted` now returns
a `GrantedAffordance` carrying the verified id and its catalog entry, and
`exercise` takes that instead of the raw id. Nothing in the pipeline reads
`invocation.affordance`, and no caller can reach it ungated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Current mechanism now describes passes 1 through 4; plan invariant 10
follows the scope-digest rule; steward folded the pipeline state into
the handoff and map. Emergency compaction scratch deleted.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Four test-only additions, plus one two-slot fixture entry:
- `soul_only_the_selected_bands_effects_are_committed` closes verification
contract 10's discriminating case. The existing tests prove an empty band
commits nothing and a one-slot entry lowers what it declares; neither
separates a slot the proposer offered from a slot the draw admitted. The new
`carry_split` entry declares two slots on disjoint bands, both proposed
within ceiling, and each band is reached by searching for a command id
before the envelope is built rather than by retrying commits.
- `soul_a_forged_band_or_effect_in_the_journal_dies_at_replay` names the layer.
`verify_state_shape` bounds a band index and refuses effects under an empty
band; it does not re-derive the draw. The forgery is re-digested into a
contiguous chain and dies at `apply_committed_command`'s effect equality.
- `soul_a_shared_catalog_id_is_gated_by_the_scope_grant` pins that an
affordance id names a catalog entry every grantee shares, so grant
membership per scope is what gates: the same `convene` id commits for its
grantee and is denied for a controller-driven scope that does not hold it.
- `soul_a_stored_role_spec_over_an_optional_subject_kind_round_trips` proves
the adjacently tagged `RefKind` survives the `to_vec_named` path that a
stored `RoleSpec { kind: Subject(None) }` writes through.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An action stops being an enum the kernel understands and becomes a lookup into
world-authored data: preconditions over live state, magnitudes under declared
ceilings, one deterministic band, lowered into the same ResolvedOps the patch
path already applies.
Cut first, with no compatibility path: DecisionAction and its kind(),
AffordanceKind, the AffordanceGrant struct and the (scope, kind) uniqueness
scan, affordance_slug, validated_invocation and its three call sites,
KernelError::EmptySpeech and ::AffordanceMismatch, the seven-clause equality
chain in apply_effect's DecisionExercised arm, the grant-kind comparison in
replay, speak_invocation's parallel constructor, and all three hand-written
claims about what a subject may do (the available_tools prose, the
operational_tools schemas, and the typed view's speak permission literal).
WorldState gains affordance_catalog, the sole store of what an affordance is,
written only by admit_resolved from a resolved patch and validated only by
resolve_patch. affordance_grants collapses to a set per scope, so a duplicate
grant is unrepresentable rather than checked and derive_opportunities is a map
lookup. Declaration::Affordance authors an entry; a subject grants entries by
reference, so one genesis patch declares a catalog and grants from it
atomically. Speak is the kernel-built entry under an ordinary draft handle:
zero preconditions, zero slots, one empty band, one utterance.
world/action.rs owns the pipeline behind one entry point, exercise(), called
from both reduce and apply_effect so a forged effect is re-derived by the
function that produced the honest one. It binds roles, evaluates Present,
Reachable, and Holds, checks every slot against its ceiling, draws one band
through the existing digest() owner over a named BandPreimage, lowers the
selected band's slots through the bindings, and admits them through the same
resolver. Effects ride on the event and apply through apply_operations, so an
affordance's Transfer meets the same conservation equation as a declaration.
ActionMismatch is a sibling of Mismatch, complete, sorted, and never
serialized; it surfaces as KernelError::ActionRejected.
Binding and admission are two checks, not one. Binding is scope-digest
equality, and the digest now reads the granted entries' definitions. Admission
runs at commit against live state, so a route closing mid-flight is
TargetUnreachable rather than ScopeChanged, and a proposal is not invalidated
by distant traffic.
The OperationalAgent tool catalog, its prose signature line, and the typed
view's permission block are three projections of the granted entries, naming
affordance kinds and role parameters and never an affordance id.
State, commit, and controller-work schemas bump; a pre-pass store is refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The seed/apply/accumulate/check_ledger sequence was inlined in the tail of
admit_resolved, which made declaration admission the only lane able to prove
custody conservation. Lift it verbatim into apply_operations, taking the
operations and the evidence list; admit_resolved calls it after its declaration
loops. No behaviour change.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pass 3 implemented Custody and conservation per the ontology; the Delvehold
consumer profile still assigned both to Delvehold. Transform at one-to-one
with no unit or rate field makes prices and recipes unrepresentable here,
which is the structural form of the boundary the profile wanted.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Six test-only additions, no source change.
- soul_a_mixed_patch_conserves_every_resource_it_touches: the ledger equation as
arithmetic over committed totals across transfer + transform + consume in one
patch, plus the admit-only opening balance. Absence stays absent.
- soul_a_forged_sequence_dies_at_the_per_operation_check_not_the_ledger: answers
the reachability question for the apply-side check_ledger. A forged
PatchAdmitted carrying two transfers whose second exceeds what the first left
dies at apply_operation's own precondition, one operation early. A conserving
forged sequence applies. from == to and from_resource == into_resource are
refused by their arms. A forged Admit still needs its receipt.
- soul_a_holding_cannot_overflow_silently: QuantityOverflow had no landed test.
Two admissions summing past u64::MAX are the complete verdict; nothing mints.
- soul_an_incoming_transfer_changes_only_the_counterpartys_scope: the receiver's
bound proposal is refused with ScopeChanged; an unrelated subject's commits.
- soul_replay_is_exact_after_every_custody_operation: replay covered only
transfer. Transform, consume, and both bind targets now ride the same restart.
- soul_a_dependency_on_an_absent_or_wrong_kind_target_is_corrupt: the dependency
shape clauses refuse a wrong-kind target, an empty target set, and an orphan
holder key.
cargo test -p ghostlight-dungeon --bin ghostlight-dungeon world::
97 passed, 0 failed, 1 ignored.
cargo test -p ghostlight-dungeon: 132 passed + 1, 0 failed, 1 ignored.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The cut first. `controllers.rs::typed_incident_routes` recomputed by hand the
same incident-route set `scope_digest` computed in `mod.rs`, two scans in two
modules held together by a comment. Both are gone. `mod.rs::scope_components`
is now the single owner of "what components does this subject's verification
read"; `scope_digest` and `snapshot` both consume it, and the controller view
reads `SubjectSnapshot.incident_routes` and resolves IDs against the snapshot.
`ScopePreimage`'s `position` and `routes` fields fold into one `components`
field. Three candidate mismatch names are refused before they exist:
`CustodyNotHeld` (absence is zero, so it is the boundary case of
`InsufficientCustody`), `DependencyTargetKind` (a target of the wrong kind is
`WrongKind`), and `DependencyDuplicate` / `DependencyNotBound` (an operation
that changes nothing is `NoOperationEffect`, which pass 2 already owns).
The addition.
- `WorldState` gains `holdings: BTreeMap<SubjectId, BTreeMap<EntityId,
Quantity>>` and `dependencies: BTreeMap<SubjectId, BTreeSet<
DependencyTarget>>`: subject-keyed partitions following `positions`, not
`EdgeRecord` variants. `EdgeRecord`, `EdgeKind`, `ResolvedRoute`, and the
seven accessors are untouched. Absence is zero at both levels, so no stored
`Quantity(0)`, no empty inner map, and a duplicate holding is unrepresentable
because a map key is unique.
- `Quantity(u64)`: unsigned, no unit, no maximum. Subtraction is `checked_sub`
and fails as `InsufficientCustody`; addition is `checked_add` and fails as
`QuantityOverflow`. No saturating arithmetic.
- `ComponentOp` and `ResolvedOp` gain `Transfer`, `Transform`, `Consume`,
`Admit`, `Bind`, `Release`. `Transform` is 1:1 by construction: one `qty`
spent and gained, so a ratio has nowhere to be written. No `ResolvedOp`
carries a resulting balance; every arm re-derives its precondition from the
committed partitions at apply time.
- `Mismatch` gains `InsufficientCustody`, `ZeroQuantity`, `QuantityOverflow`,
`AdmitWithoutEvidence`, `SelfDependency`, and `CustodyNotConserved`, appended
after the pass-2 variants so single-item assertions keep their ordering.
- `patch::check_ledger` is the one statement of conservation:
`after == before + admitted + gained - consumed - spent`, over `u128`
accumulators. `resolve_patch` seeds a fourth candidate map beside containers,
routes, and positions, records each admitted operation's terms, and verdicts
the ledger after the operation loop and before the emptiness gate.
`admit_resolved` re-derives the same equation over the committed partitions,
so a forged `PatchAdmitted` that reaches `apply_effect` without passing the
resolver dies with `Invariant("custody does not conserve")`. `Transfer`
contributes to no term: the equation proves it.
- Evidenced admission. `EvidenceRef` gains a constructor and crosses the
facade. An `Admit` is valid when its ref is canonical text and the identical
ref appears in the same patch's `evidence` list; otherwise
`AdmitWithoutEvidence`. The kernel checks no store and no digest — retrieval
and receipt authenticity belong to the retrieval organ — but the ref rides
inside the digested, hash-chained commit, so it cannot be altered or dropped
afterwards. There is no holdings declaration field: `Admit` with evidence is
the only creation path, in every lane including genesis. Evidence is
Draft-only, so `Admit` is Draft-only in this pass, with no second gate.
- Replay shape. Every `holdings` key names a canonical subject, every inner key
a `Resource` entity at a nonzero quantity, no inner map empty; every
`dependencies` key names a canonical subject with a non-empty set of
canonical, distinct targets.
- Snapshot and typed view. `WorldSnapshot` gains `resources`;
`SubjectSnapshot` gains `holdings`, `dependencies`, and `incident_routes`,
all the acting subject's own. `typed_view` gains a `holdings` block with
labels resolved only for what this subject holds, and a `dependencies` block
carrying target kind and ID with no label and no target state. The
hand-written `available_tools` string is unchanged; the derived tool catalog
is a later pass.
- Schema bump to `ghostlight.world_state.custody.v1` /
`ghostlight.world_commit.custody.v1` and the matching journal rows. A
pre-pass store fails `recover` with an unadmitted row type. No migration
adapter.
Deviations from the Imagination spec, each decided here:
- `resolve_place` generalizes into `resolve_entity(expected_kind)` rather than
gaining a `resolve_resource` sibling. Two eight-line wrappers around one body
bought nothing; the four place call sites pass the kind.
- Spec test 1 asks for a forged `Transfer` whose debit and credit differ.
`ResolvedOp::Transfer` carries one `qty`, so that is unrepresentable — which
is the outcome the shape was chosen for. The test proves the reachable
forgery (a transfer the holder cannot cover leaves `holdings` byte-identical)
and falsifies `check_ledger` directly beside it.
- Spec test 14 asserts against `verify_state_shape` rather than
`verify_history`. Mutating a committed holding also breaks replay equality,
so routing through `verify_history` would pass even with the shape clauses
deleted.
- Spec test 11's visible half lives in `controllers.rs`, where
`SelectedDecision` is, extending the existing typed-view non-leakage test;
its kernel half is the world-level test of the same name.
- `EvidenceRef::new` is `#[cfg(test)]`. No `AdmitPatch` ingress exists yet, so
every caller that builds a patch today is a test; the gate comes off in the
same commit as the first production patch author. The type itself crosses the
facade, as `ComponentOp::Admit` requires.
No external-owner field, no unit, rate, or recipe field anywhere, no new crate
or dependency, no compatibility shim.
cargo test -p ghostlight-dungeon --bin ghostlight-dungeon world::
91 passed, 0 failed, 1 ignored (75 before this commit).
cargo test -p ghostlight-dungeon: 126 passed + 1, 0 failed, 1 ignored.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Four cuts Soul named on pass 2, none of them behaviour changes.
- runtime.rs: delete the `source_version` presence check in
`admit_controller_command`. It called `.ok_or_else(...)?` and discarded the
value, so it validated nothing. The `expected_revision = source_version - 1`
computation on the approve/activate path is untouched; that one still needs a
caller-supplied binding.
- world/patch.rs: drop `Serialize`/`Deserialize` from `RefKind`.
`RefKind::Subject(Option<SubjectKind>)` is an internally tagged newtype over a
non-map, which serde refuses at runtime, so the derives were unexercised
weight. `RefKind` is never persisted: it appears only in `Mismatch` (not
serializable), the resolution index, and the three kind constants. Soul's
`soul_ref_kind_is_not_a_serializable_shape` cannot compile as a negative
without the derives it falsifies, so it is deleted; the doc comment on
`RefKind` now carries the rule it was proving.
- world/mailbox.rs: document that `submit_stamped` stamps `world_id` as well as
`revision`, and why — the mailbox owns exactly one world, so there is no other
world a controller runner could mean. The consequence is that
`WorldKernel::submit`'s `WorldMismatch` check is unreachable on this path;
`soul_a_stamped_submission_still_fails_closed_on_a_foreign_scope` stays as the
proof that scope still fails closed.
- world/mod.rs: `ComponentOp` and `RouteDeclaration` are test-only, so their
import moves behind `#[cfg(test)]`.
ghostlight-persona-projection: `opportunity_digest` no longer claims to bind
"world state as one value"; pass 2 made it bind the scope digest.
cargo test -p ghostlight-dungeon --bin ghostlight-dungeon world::
75 passed (76 minus the deleted RefKind test), 1 ignored.
cargo test -p ghostlight-dungeon: 110 passed + 1, 1 ignored.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Handoff, map, scratch, and the ontology doc's current-mechanism section now
describe e99af63. The elaborator witness is corrected to 26 finished slot
passes. The worktree the steward saw at 8cad6b9 is the pass-3 Hands tree
before its reset to the tip, not an orphan.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ten test-only additions, no production source touched.
- mailbox: the end-to-end proof the kernel-level test does not give — a
controller turn bound at revision N commits through the mailbox after an
unrelated commit at N+1, and a stamped body still fails closed on a
foreign scope.
- world: the scope preimage excludes revision, phase, a place declared
elsewhere, a non-incident route, and another subject's Position, across
real commits; a forged affordance list cannot widen authority.
- patch: RouteSelfLoop, UnplacedSubject, and NoOperationEffect are each
reachable; a forged relocate effect dies at apply time on all three
preconditions; RefKind is not a serializable shape.
- journal: forged store rows carrying a self-loop, an out-of-range cost, a
non-place endpoint, or a stray position are refused by recover, and a
pre-topology row type is refused outright.
- controllers: the typed view carries only the actor's own place and the
routes incident to it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cut: SubjectDeclaration.authority_scope and SubjectState.authority_scope with
their four production readers; DecisionOpportunity.state_digest and every reader
of it, including the kernel's whole-world gate, ControllerRunner::select's
struct-equality filter, PersonaTurnBinding.state_digest, the two runtime.rs
surface-freshness comparisons, and the caller-supplied envelope revision for
opportunity-bearing commands; EdgeKind's Relation, Commitment, and Pressure arms.
Add: a positions partition and Position, EntityRecord.container with containment
acyclicity over the complete candidate graph, Route written into the existing
edges partition with AccessKind, Cost, and an open flag, the four inhabited
ComponentOp variants, and resolve_patch as the one owner of declarations,
references, topology admission, and operation preconditions, closing the complete
mismatch set before the first ID mints. A proposal now binds the digest of the
components its verification reads -- controller assignment, grants, Position, and
incident routes -- so an unchanged scope commits at a later revision and a changed
one is refused with KernelError::ScopeChanged. Schema bumps to topology.v1.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Handoff and map carry the four world-branch tips and fixture commits, the
retired single-loop launcher, the live 32-slot scheduler, ontology tip
5e53beb with its follow-ups landed, and pass 2 in flight.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>