PA.f36: the resolver tracked a freshly declared mirror in `mirrors` but never
tracked a freshly declared human, so a genesis patch that declared and retired
the human in the same patch was admitted; only the apply-time re-check caught
it, after `open_owner` had already committed an empty world.cc. The resolver
now tracks a `humans` candidate set the same way, seeded from the committed
controller assignment and extended for a subject this patch declares as
`NewController::Human`, and refuses its retirement in Draft with
`RetiresAnApprover`.
PA.f37: a retired subject could still act through the clock and through
commitments. `CreateCommitment` now refuses a retired promisor with
`RetiredSubjectActed`, and `derive_motion` now skips a retired subject's
commitments and dependencies entirely, so its routines stop rolling and its
obligations, goals, and dependencies stop pressing. Nothing is discharged or
deleted; a retired subject's commitments stay on record exactly as they were
the moment it retired.
PA.f38: `delegated_authority` read a retired institution's grants, so a live
office-holder kept a dissolved institution's authority. `Retire` does not
vacate offices — the incumbency stays on the ledger — but a retired
institution now lends nothing: `delegated_authority` skips a retired
institution's offices at the one site that turns an office into effective
authority.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PersonaLane (crates/ghostlight/src/controllers.rs) is the unpersisted
Projector-then-Persona turn PA-Q3 A called for: no work-store row, no
persisted state, a restart just repeats the two inferences. Its `turn`
runs Projector then Persona for a NarrativePersona opportunity and
returns a receipt-bound PersonaTurn; `narrate` runs the Projector alone
over a Human opportunity for the player's own view. Both refuse the
wrong mode before touching the mailbox, and a retired subject is
refused by `select` itself (no live opportunity survives retirement).
The Projector-then-Persona prompt builders in `run_narrative` and
`run_projector` move into two private functions, `build_projector_invocation`
and `build_persona_invocation`, so PersonaLane and the narrative lane
build both prompts through the same one seam: a Persona's input is
Projector prose and nothing else (invariant 2).
PA-Q2's recency marker: `SubjectSnapshot.last_acted_at` is derived in
`snapshot` from one pass over `state.events` (the greatest
DecisionEvent.revision scoped to that subject), and `projector_knowledge`
stamps each row `"new": minted_at > last_acted_at`. A subject that never
acted sees every minted row as new; an unminted background fact (no
revision) is never new, having no arrival time to compare.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PA.f36 to PA.f40 are to fix in batch 5. Self's rulings under the standing
go: retired subjects' commitments stop moving, a retired promisor is
refused, and a retired institution lends no authority.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`audience()`'s Reach::Subjects arm returned channel members without
filtering retirement, so a retired subject remained a Communicate audience
and a CanReach target (audience() is the one statement both preconditions
and Communicate read, so one filter, `is_retired`, fixes both). A retired
Communicate speaker was also resolved rather than refused. Audience
derivation stays the one owner of reachability, so a channel may still
declare a retired member; retirement is filtered at that single read site.
AcquireKnowledge, Forget, and InstallIncumbent all accepted a retired
subject as the one learning, forgetting, or taking office. All four ops now
push the new `Mismatch::RetiredSubjectActed`. Transfer is untouched: a
retired holder's holdings remain transferable, so a body can still be
looted.
Adds permanent tests for PA.f33-f35, including named pins for the five
mutations Soul's probes found live: MuA (retire-then-relocate), MuB (an
elaborator revoking outside its jurisdiction), MuC (a mirror flagged
retired), MuD (three revokes in one patch), and MuE (retiring the same
subject twice in one patch).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`verify_state_shape` judged every past DecisionEvent against the subject's
*current* controller and affordance grants, so retiring a subject that had
acted, or revoking a verb it had used, failed `verify_append` with
CorruptJournal. Authority is judged once, at commit, by `reduce`, and after
that by replay (`verify_history` re-runs `reduce` for every commit) and the
digest chain; the shape check now holds only the time-invariant facts (scope
resolves to a live assignment, affordance is a catalog entry). Every
production caller of `verify_state_shape` (`verify_append`, `verify_history`,
both reached from `recover`/`initialize`) already replays, so no load path
loses coverage.
`required_approvers` reads the live controller assignment, and `Retired`
carries no human principal, so an owner could retire the human's subject in
Draft, approve alone, and activate without the human's approval. `Retire` now
refuses a Human-controlled subject in Draft with a new `RetiresAnApprover`
mismatch, re-decided at apply beside the resolver. In Active the human's
subject may die: the journal's approval-shape check now holds only in Draft,
since Active approvals are frozen history judged once by `ActivateWorld`'s
`reduce` arm and after that by replay.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PA.f23: LocalChatResponse.id already tolerated an absent field through
#[serde(default)], but not an explicit "id": null, since #[serde(default)]
only fires when the key is missing. Give it the same null_as_default
treatment tool_calls already has, with a_null_response_id_decodes_as_empty
mirroring the existing tool_calls coverage.
PA.f24: add ipv6_loopback_opens_and_a_non_loopback_ipv6_address_is_refused,
pinning that the loopback check is IP-address loopback and not "any IPv6
address": [::1] opens and a routable IPv6 address such as
[2001:db8::1]:8080 is refused the same way a non-loopback IPv4 address is.
Test-only; the loopback check itself was already correct.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Test-only. No production code changes: nothing here found a real bug.
PA.f27: require_ruler's own coverage gaps.
- apply_effect_re_decides_the_ruler_for_the_owner_too: a forged
PatchAdmitted under the owner (CallerId::Principal), carrying a Ruled
fact, is refused at apply exactly as it is for an elaborator. Kills an
exemption for Principal in apply_effect's re-decision.
- require_ruler_catches_a_mint_past_the_first_operation: a harmless
Relocate at index 0 followed by a Mint at index 1, from a non-Play
author, is refused at index 1. Kills a scan narrowed to .take(1).
- require_ruler_reports_every_ruled_fact: two Ruled facts from a non-Play
author are both reported. Kills a check narrowed to the first fact.
PA.f29: the evidence gate, extended and carried to the document path.
- the_evidence_gate_is_unchanged_for_every_author now also covers the
owner in Draft (the seed's caller), the owner in Active with a real
boundary answer, an answered elaborator, and a consumer on its own
mirror. Each pair proves an uncited Canonical fact or Admit is refused
(FactWithoutEvidence / AdmitWithoutEvidence) and the same content, cited,
is admitted. The owner/elaborator "cited" patches also seat a fresh
occupant at the dead end so the Boundary answer they must supply in
Active is genuinely cleared, rather than masked by AnswerNotSatisfied.
- a_document_carrying_a_mint_is_refused_through_admit_document (in
consumer.rs): a consumer document carrying a Mint is refused through the
real decode path, admit_document, not only the in-process as_consumer
fixture caller other tests use.
PA.f30: confinement and controller edge cases.
- a_consumer_cannot_retire_grant_or_revoke (consumer.rs): Retire,
GrantAffordance and RevokeAffordance from a consumer are refused.
Soul: every subject choice available to a consumer is also caught by an
upstream structural gate (a mirror can never hold a grant or be retired;
no non-mirror subject can ever bind to a consumer's own ground), so this
specific narrowing — confine_to_ground confining only for an elaborator
jurisdiction, not a consumer — is not independently killable from the
consumer side. The refusal itself is real and the finding calls for the
test, so it is kept; the redundancy is recorded in the test's own
comment rather than dropped silently.
- a_revoke_cannot_leave_a_human_subject_mute: the last-verb refusal for
RevokeAffordance holds for a Human-controlled subject exactly as for any
other controller kind.
- retired_expects_no_caller: a direct unit pin that
ControllerAssignment::Retired.expected_caller() is None.
All three production-touching mutations described by the finding (the two
require_ruler scans, and a hypothetical Human exemption on the last-verb
check) were applied by hand, confirmed to fail the new test, and reverted;
git status is clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The journal's shape check judges past events against present assignments,
so a subject that acted cannot be retired and a used verb cannot be
revoked (PA.f31). The owner can retire the human in Draft to skip
approval (PA.f32). Retired subjects still hear and speak through subject
channels (PA.f33) and can gain or forget knowledge or hold office
(PA.f34). Self's design rulings under the standing go are recorded in each
finding.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
prepare_creation and WorldState::genesis both admitted a creation patch
without calling require_ruler, so a genesis patch could carry a Ruled fact
or a Mint outright, and journal replay would happily reproduce it. Call
require_ruler in both places: prepare_creation maps its Vec<Mismatch> to
KernelError::PatchRejected, the same way every other reduce-site refusal
does; WorldState::genesis maps it to KernelError::Invariant, the same way
its own derivation check already does, since genesis has no patch-rejection
channel of its own.
Three new tests. Two exercise the full WorldKernel::create path with a
Ruled-fact and a Mint-carrying genesis patch. A third calls
WorldState::genesis directly with a hand-resolved effect, bypassing
prepare_creation entirely, so it is not satisfied by prepare_creation's own
check alone — this is what actually protects journal replay of a forged or
corrupted genesis commit, not just a freshly admitted one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ComponentOp::GrantAffordance{subject, affordance} and RevokeAffordance
resolve beside GrantAuthority/RevokeAuthority: a retired or externally
controlled subject, or an unknown catalog entry, is refused (the new
Mismatch::GrantsOutsideControl); a grant already held or a revoke not held is
NoOperationEffect; a revoke that would leave a controlled subject with no
affordance at all is refused with the new Mismatch::WouldLeaveSubjectMute,
because derive_opportunities requires at least one. Applying either writes
state.affordance_grants directly, re-deciding the same three refusals against
committed state. confine_to_ground now refuses both, unconditionally, beside
Retire: a grant is authority over the catalog, not structure over ground, so
the owner (including the seed lane, the owner in Draft) and the play
authority are the only callers.
PATCH_TOOLS gains grant_affordance and revoke_affordance (Operate, subject
plus an affordance reference); catalog counts move to (7, 34, 43). Same
consumer.v6 / controller_work.v17 as Cuts 3 and 4.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ControllerAssignment::Retired is a new value of the existing controller
assignment map, not a second partition: id() and mode() return None for it
like a mirror, so derive_opportunities mints nothing and it is never an
audience once its position is gone. expected_caller becomes Option<CallerId>
(None only for Retired; a mirror still returns Some, the one caller that
could ever act for it) and both exact-caller comparison sites move to
Some(&caller).
ComponentOp::Retire{subject} resolves beside Relocate: refuses an already
retired subject with NoOperationEffect and a mirror with the new
Mismatch::RetiresAMirror. Applying it sets the assignment to Retired and
removes the subject's positions and affordance_grants entries, so a witness
at its former place never reaches it while its holdings, commitments, and
knowledge stay untouched. confine_to_ground refuses Retire unconditionally
for every confined author, the same unconditional shape require_ruler already
gave a Ruled fact and a Mint: the owner and the play authority are the two
callers left. SubjectSnapshot gains pub retired: bool, set in snapshot().
PATCH_TOOLS gains retire (Operate, one subject field); catalog counts move to
(7, 32, 41). Schema and controller_work row version are unchanged: this rides
Cut 3's consumer.v6 / controller_work.v17.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No promise was falsified. Genesis bypasses require_ruler (PA.f26), three
mutations survive the author-rule tests (PA.f27), and two promised tests
are missing (PA.f29); all three are to fix. PlayPort's convention sealing
(PA.f28) is defaulted and recorded under the standing go.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PA.f23 (null response id quarantines the lane) and PA.f24 (no IPv6
loopback test) are to fix; PA.f25 is recorded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Soul's Cut 2 verdicts left five findings after PA.f16/f17: two decode gaps,
two missing open-time refusals, one duplication/placement cleanup, and four
surviving mutations with no test naming them. All fix here.
PA.f18: LocalChatResponse.id and LocalChatMessage.tool_calls both integrity-
violated on shapes a nonstandard local server can plausibly send. An id is
used only for receipt bookkeeping, so #[serde(default)] tolerates an absent
one. tool_calls already tolerated an absent field through #[serde(default)]
but not an explicit "tool_calls": null, since default only fires when the
key is missing; deserialize_with = "null_as_default" covers both. Tool-call
ids still integrity-violate on absence, since the tool loop must correlate
them.
PA.f19: open_inference now refuses an empty local or SDK model prefix
(ControllerOpenError::EmptyModelPrefix) instead of letting it match every
model and silently take a transport's lanes. assemble_output now refuses a
reply carrying the same tool-call id twice, as integrity_violation, since the
port owns reply integrity and duplicate ids would otherwise reach the
evaluator unchecked.
PA.f20: call_id_is_valid and tool_name_is_valid are hoisted to one
pub(super) copy in controllers.rs; local_inference.rs and sdk_inference.rs
both import it instead of keeping their own. The loopback check moves out of
open_inference and into LocalInferencePort::new itself, so it holds by
construction for every caller, not only the one open_inference exercises;
open_local_port and open_inference propagate the constructor's Result. A
request offering no tools now omits `tools` and `parallel_tool_calls` from
the lowered body instead of sending an empty array. Test helper
two_tool_call_reply is folded into tool_call_reply, which now takes a slice
of calls.
PA.f22: one test per mutation that survived Soul's Cut 2 pass, each run by
hand against this commit and reverted after killing its test:
offered_tools_are_declared_in_the_request_body (S2, empty tools array),
the_model_prefix_is_stripped_in_the_request_body (S3, unstripped model),
infer_refuses_a_foreign_caller and infer_refuses_an_expired_invocation (S4,
both infer gates), the_receipt_digest_changes_with_the_response_id (S6,
response_id dropped from the receipt).
Local lib census: 9 (after the prior commit) -> 21 in local_inference::;
488 -> 502 in the full `cargo test -p ghostlight --lib` run (501 passed, 1
ignored).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Soul falsified P2.2 against 182f803: reqwest::Client::builder() kept the
environment proxy, so an HTTP_PROXY variable sent the whole loopback request
to the proxy and a proxy URL carrying userinfo added a proxy-authorization
header, a credential, from the environment, sent by Ghostlight (PA.f16). A
307 reply was followed and re-POSTed the body to a second port (PA.f17).
Fix both by construction in LocalInferencePort:🆕 .no_proxy() so the
client never resolves a proxy from the environment, and
redirect::Policy::none() so a 3xx reply falls through the existing non-2xx
status handling in send() as a recovery-required fault, the same disposition
every other unsuccessful status outside 429/503 already gets, rather than
being followed.
Tests: the_client_never_honors_an_environment_proxy sets HTTP_PROXY to a
second scripted responder and asserts the real endpoint was hit directly and
the proxy endpoint received nothing. HTTP_PROXY is process-global and
reqwest resolves it only at Client::build time, so the test and every other
local-port client construction in this module (through the port() helper)
share one client_build_lock for the whole window the variable is set,
serializing against the one place this module builds a reqwest client,
instead of adding a dev-dependency. a_redirect_is_a_fault_and_the_redirect_target_is_never_reached
points a 307 at a second responder and asserts that responder never receives
a request. Both mutations (removing .no_proxy(), removing
redirect::Policy::none()) were run by hand against this change and killed
each test, then reverted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SystemCapability::Play is a new system capability, minted only by the new
PlayPort (mailbox.rs): unconfined, declares in Active with no answer, may
advance time, and is the sole author of FactStanding::Ruled and
ComponentOp::Mint (quantity with no evidence receipt). One new admission rule,
require_ruler, refuses a Ruled fact or a Mint from any other caller in both
reduce and apply_effect; the evidence gate on Canonical facts and Admit is
untouched for every author, Play included. controller_work rows bump to v17
and the consumer schema bumps to consumer.v6 in the same commit, since the
shape change and the version are one bump.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The port's HTTP client honours proxy environment variables and follows
redirects. Loopback-only requests left the machine, and a proxy URL's
userinfo was sent as proxy-authorization. PA.f16 to PA.f22 are triaged
to fix in one batch after Cut 3 lands; PA.f21 is recorded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Road runs 8 to 12 went through the sidecar. The smoke that drove them
was deleted in the play agent's Cut 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 1 (d69e9d4) deleted the tick driver and the Active elaboration sweep, and
the ignored live smoke that exercised them. These passages still described
that machine as live:
- notes/local-live-smoke.md was a runbook for the deleted smoke and its
command now runs zero tests. Trimmed to what is still true: connector and
Claude SDK sidecar bring-up, which the seed lane still depends on. Fixed
the references to its deleted sections in notes/fresh-workspace-handoff.md.
- docs/architecture/ghostlight-dungeon-mvp.md's Verification contract named
the live smoke as the road's one proof point; it now says none exists until
the play agent's own gate.
- notes/ghostlight-implementation-plan.md's Interruption section named
ControllerHttpResult::Interrupted and run_cover_tick as live; both are
deleted, and reporting an interruption to the play agent is Cut 8's.
- docs/architecture/ghostlight-stock-lenses.md gets one added sentence: the
concurrent-elaboration claim holds as a library capability, and Dungeon
runs none of it since Cut 1. The verdict is unchanged.
- notes/fresh-workspace-handoff.md's concurrent-elaboration and
tick-driver lines now say what Dungeon actually runs today.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PA.f11: Cut 1 deleted the tick driver and the Active elaborator, but nothing
pinned their absence. Soul re-added a submit_clock loop and a spawned sweep
and both passed 46/46. Add
soul_no_forbidden_writer_name_appears_in_dungeons_own_source, on the
soul_no_credential_name_appears_in_the_runtimes_own_source precedent, banning
submit_clock(, .elaborator(, run_narrative(, run_operational(, run_cell( and
ElaborationRunner from every non-test .rs file under
crates/ghostlight-dungeon/src, not just runtime.rs.
PA.f12: the dropped no_proposal_projection_carries_the_canonical_world_commit
was the only test of submit_receipt's applied/already_applied kinds and
revision. Add
a_principal_command_resubmitted_under_its_own_key_returns_applied_then_already_applied,
which submits one world.advance_time command twice under the same
idempotency key through the real HTTP path and asserts the kinds and the
matching revision.
Both mutations were run and restored by hand against the committed code: a
submit_clock( comment and a .elaborator() comment each failed the new source
scan, and swapping submit_receipt's two "kind" strings failed the new
idempotency test. Dungeon bin census: 46 -> 48.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A third `InferencePort` beside the connector and the SDK sidecar: one
loopback-only OpenAI-compatible `POST /v1/chat/completions` call per
prepared request, tool calls returned inert for Rust's own evaluator to
drive the next round, exactly as the connector lane already does. No
credential is read or sent; `open_inference` refuses a non-loopback
`LocalBinding` and a model prefix two transports both claim.
`RoutedInferencePort` gains the local slot and routes by longest claimed
prefix, refusing rather than falling back to the connector when a claimed
prefix has no configured port behind it (the same rule the SDK prefix
already had, now made explicit for both). `ControllerModels::each` is
`pub` and `open_inference` takes `models: &[&str]` instead of the whole
struct, so a caller can extend the list with a model the struct does not
carry.
Dungeon's runtime reads `GHOSTLIGHT_LOCAL_ENDPOINT` and
`GHOSTLIGHT_LOCAL_MODEL_PREFIX` and wires the binding through; no other
Dungeon behavior changes in this cut.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All four promises hold. P1.1 and P1.2 are unguarded by any test (PA.f11),
and the only test of the receipt kinds was dropped (PA.f12). Both are to
fix, along with the stale docs (PA.f15). PA.f13 and PA.f14 go to Cut 8.
The negative grep now allows the one hit at M1.1's test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing in Dungeon now runs a Persona, an operational agent or an
elaborator on its own. Deleted from runtime.rs: the cover tick driver
(drive_cover_tick, drive_one_tick, run_cover_tick, submit_clock_tick,
CoverSummary, InterruptedCell, cover_panel, the clock-tick constants,
configured_cover_budget, configured_tick_interval), the Active
elaboration sweep (elaborate_world, run_elaboration_sweep,
ELABORATION_SWEEP_INTERVAL, configured_elaboration_ceiling,
AppState.elaboration_permits), and the owner's "Let X act" route
(ControllerActPayload, dispatch_controller, admit_controller_command,
ControllerHttpResult and its builders, controller_error_disposition).
Deleted from eve.rs: CoverPanel, CoverPanelTick, cover_card, the owner's
controller buttons and descriptor, and world.controller.act from
operation_schema.
AppState.controller_quarantined is deleted as well: its only writers were
dispatch_controller and run_cover_tick, so it could never be set again.
controller_permits and configured_controller_concurrency stay for Cut 8;
until then only runtime_readiness reads the pool. ControllerRunner stays
for seeding and custody_probe; world.speak, the story card and
world.advance_time stay until Cut 9.
Tests dropped with the behaviour they pinned: the nine the cut map lists,
plus no_proposal_projection_carries_the_canonical_world_commit, which
tested the deleted controller_submission. Their fixtures go with them;
the world fixture collapses to the Draft-only path its remaining callers
use. removed_legacy_operation_is_denied_before_dispatch now also names
world.controller.act.
Docs: the MVP doc, the ontology and the handoff drop the cover,
tick-interval and elaboration-ceiling variables and the sentences that
described the deleted drivers.
Library untouched. Built and tested on Windows only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cut 3 now adds a unit FactStanding::Ruled and a new ComponentOp::Mint
rather than reshaping Admit. It adds one post-resolution author check,
require_ruler, which refuses either one unless the caller is
System(Play). The evidence gate is unchanged for every author, Play
included. The work-row bump to v17 moves into Cut 3.
PA-Q10 and Q12 are folded in as ruled designs. PA-Q7's list becomes 25
tools, with mint in and admit out. table_view prints ids, holdings, and
each fact with its standing and who knows it.
New findings: PA.f9 (the shared catalog shows mint and ruled to the seed
and elaborator lanes; watched at the gate), and PA.f10 (minted quantity's
only provenance is the commit caller; a question for the outbound pass).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator accepted the map's recommendation on PA-Q1, Q3, Q7, Q10, Q12
and Q13 on 2026-09-22. The operator also ruled PA-Q15: the play authority's
facts stand as Ruled, and it mints quantity without evidence ("if the agent
can invent people, it can invent loot"). Only Play gains either;
elaborators, consumers and the seed keep the evidence gate.
The target now lists the library's fourth piece (the table vocabulary and
inference seams) and the affordance grants. Invariant 5 gains Ruled facts
and minting, and invariant 8 shows the kernel's refusal of the player's own
act. Cut 3 still specifies the superseded PlayCarriesEvidence refusal and
is being respecified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Imagination's cut map for the play agent target. Probes compiled a scratch
consumer against the library, and the agent's tool catalog was measured.
The map answers target questions Q1-Q14. Q2, Q5, Q6, Q8 and Q9 are
defaulted, Q4 and Q14 are findings, and Q11 is ruled by the operator.
Q1, Q3, Q7, Q10, Q12 and Q13 await operator rulings.
Ten cuts follow, subtraction in Dungeon first, then the local port, the
kernel pieces (play authority, retirement, affordance grants), the Persona
lane, the table vocabulary, the play table, and the Eve surface. Findings
PA.f1-PA.f8; PA.f1 was already resolved at 5ff1d54.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The map, handoff, plan and Session Zero doc now name plan step 16, the play
agent, as the playtest gate. L2-L4, D1-D3 and the L1 follow-ups are
recorded as deferred by ruling, not dropped.
A source check corrected the map. Inference routes per lane by model name
to the SDK sidecar or CodexConnector, and no local transport exists. The
sidecar is a private msgpack pipe behind InferencePort, not the CultNet
connector API the map claimed every backend shares.
The handoff's session_zero.begin item is retired: the name is refused, and
only tests reference it. One evidence record covers the pivot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator ruled on 2026-09-22 that the player's perception goes through
the same Projector a Persona gets, which makes the Projector the player's
narration. The play agent writes none. The turn description and invariant 8
now say so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator redirected the playtest gate on 2026-09-22. Ghostlight's
controller machinery suits offline simulation, not interactive role-play.
One operational agent now runs the table; the Projector and Persona passes
stay exactly as they are.
The target records the rulings (the play authority, actor acts through the
actor's affordances, deferral of L2-L4, D1-D3 and the L1 follow-ups), eight
invariants, the redefined playtest gate, and eleven open questions for the
cut map.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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>
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>
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>
"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>
`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>
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>
`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>
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>
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>
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>
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>