diff --git a/crates/ghostlight-dungeon/src/bin/ghostlight-strategic-smoke.rs b/crates/ghostlight-dungeon/src/bin/ghostlight-strategic-smoke.rs index 35556ef..62d26fa 100644 --- a/crates/ghostlight-dungeon/src/bin/ghostlight-strategic-smoke.rs +++ b/crates/ghostlight-dungeon/src/bin/ghostlight-strategic-smoke.rs @@ -9,6 +9,73 @@ fn final_wave_field( .ok_or_else(|| anyhow::anyhow!("final strategic wave is missing {field}")) } +fn strategic_smoke_digest(value: &T) -> anyhow::Result { + use sha2::{Digest, Sha256}; + + Ok(format!( + "sha256:{:x}", + Sha256::digest(serde_json::to_vec(value)?) + )) +} + +fn strategic_smoke_bytes_digest(bytes: &[u8]) -> String { + use sha2::{Digest, Sha256}; + + format!("sha256:{:x}", Sha256::digest(bytes)) +} + +fn recomposed_model_receipt_set_digest(value: &serde_json::Value) -> anyhow::Result { + let receipts: Vec = + serde_json::from_value(value.clone()).map_err(|error| { + anyhow::anyhow!("newspaper recomposition has invalid model receipts: {error}") + })?; + strategic_smoke_digest(&receipts) +} + +fn completed_wave_issue_campaign( + wave_reports: &[serde_json::Value], + report_index: usize, +) -> anyhow::Result { + let report = wave_reports + .get(report_index) + .ok_or_else(|| anyhow::anyhow!("completed wave report is absent"))?; + let wave_index = report["wave_index"] + .as_u64() + .ok_or_else(|| anyhow::anyhow!("completed wave report has no wave index"))?; + if wave_index != report_index as u64 + 1 { + anyhow::bail!("completed wave reports are not a contiguous prefix") + } + let mut campaign: ghostlight_dungeon::domain::Campaign = + serde_json::from_value(report["commit"]["campaign"].clone()).map_err(|error| { + anyhow::anyhow!("completed wave has no committed campaign: {error}") + })?; + if campaign.revision != report["world_revision_after"].as_u64().unwrap_or_default() { + anyhow::bail!("completed wave campaign disagrees with its committed revision") + } + let previous_campaign: ghostlight_dungeon::domain::Campaign = if report_index == 0 { + anyhow::bail!( + "a missing first-wave newspaper cannot recover its exact pre-wave news boundary" + ) + } else { + serde_json::from_value(wave_reports[report_index - 1]["commit"]["campaign"].clone()) + .map_err(|error| { + anyhow::anyhow!("prior completed wave has no committed campaign: {error}") + })? + }; + let previous_news_count = previous_campaign.news.len(); + if previous_news_count > campaign.news.len() { + anyhow::bail!("completed wave news ledger is shorter than its prior committed prefix") + } + if campaign.news[..previous_news_count] != previous_campaign.news { + anyhow::bail!("completed wave news ledger does not preserve its exact prior prefix") + } + campaign.news = campaign.news[previous_news_count..].to_vec(); + if campaign.news.is_empty() { + anyhow::bail!("completed wave has no recoverable gated news") + } + Ok(campaign) +} + #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct CompilerCheckpoint { @@ -1692,6 +1759,154 @@ async fn main() -> anyhow::Result<()> { }))?, )?; } + if resume { + for report_index in 0..wave_reports.len() { + if !wave_reports[report_index]["issue"].is_null() { + continue; + } + let wave_index = wave_reports[report_index]["wave_index"] + .as_u64() + .ok_or_else(|| anyhow::anyhow!("completed wave report has no wave index"))?; + let issue_campaign = completed_wave_issue_campaign(&wave_reports, report_index)?; + let issue_title = format!("{newspaper_title} — Issue {wave_index}"); + let source_campaign_digest = strategic_smoke_digest(&issue_campaign)?; + let editorial_contract_digest = strategic_smoke_digest(&serde_json::json!({ + "title":&issue_title, + "editorial_voice":&newspaper_voice, + "max_articles":5, + }))?; + let recomposition_path = + root.join(format!("newspaper-wave-{wave_index:02}-recomposition.json")); + let recomposition = if recomposition_path.is_file() { + read_checkpoint::(&recomposition_path)? + } else { + let composition = match compose_persisted_newspaper( + model.as_ref(), + &issue_campaign, + &issue_title, + &newspaper_voice, + 5, + &store, + ) + .await + { + Ok(composition) => composition, + Err(error) => { + let model_receipts = error + .downcast_ref::< + ghostlight_dungeon::newspaper::WorldNewspaperCompositionFailure, + >() + .map(|failure| failure.model_receipts.clone()) + .unwrap_or_default(); + publish_immutable_checkpoint( + &root.join(format!( + "newspaper-wave-{wave_index:02}-recomposition-terminal-failure.json" + )), + &serde_json::json!({ + "schema":"ghostlight.newspaper_wave_recomposition_failure.v1", + "wave_index":wave_index, + "world_revision":issue_campaign.revision, + "error":error.to_string(), + "model_receipts":model_receipts, + }), + )?; + return Err(error); + } + }; + let issue_path = root.join(format!("newspaper-wave-{wave_index:02}.md")); + let issue_audit_path = + root.join(format!("newspaper-wave-{wave_index:02}.audit.md")); + let reader_copy = ghostlight_dungeon::newspaper::render_world_newspaper_markdown( + &composition.issue, + ); + let audit_copy = + ghostlight_dungeon::newspaper::render_world_newspaper_audit_markdown( + &composition.issue, + ); + std::fs::write(&issue_path, &reader_copy)?; + std::fs::write(&issue_audit_path, &audit_copy)?; + let checkpoint = serde_json::json!({ + "schema":"ghostlight.newspaper_wave_recomposition.v1", + "wave_index":wave_index, + "world_revision":issue_campaign.revision, + "source_campaign_digest":source_campaign_digest, + "editorial_contract_digest":editorial_contract_digest, + "issue_digest":strategic_smoke_digest(&composition.issue)?, + "model_receipt_set_digest":strategic_smoke_digest(&composition.model_receipts)?, + "reader_copy_digest":strategic_smoke_bytes_digest(reader_copy.as_bytes()), + "audit_copy_digest":strategic_smoke_bytes_digest(audit_copy.as_bytes()), + "issue":composition.issue, + "newspaper_grounding":composition.grounding, + "newspaper_model_receipts":composition.model_receipts, + "issue_file":format!("newspaper-wave-{wave_index:02}.md"), + "issue_audit_file":format!("newspaper-wave-{wave_index:02}.audit.md"), + }); + publish_immutable_checkpoint(&recomposition_path, &checkpoint)?; + checkpoint + }; + if recomposition["schema"] != "ghostlight.newspaper_wave_recomposition.v1" + || recomposition["wave_index"].as_u64() != Some(wave_index) + || recomposition["world_revision"] + != wave_reports[report_index]["world_revision_after"] + || recomposition["source_campaign_digest"] != source_campaign_digest + || recomposition["editorial_contract_digest"] != editorial_contract_digest + || recomposition["issue"].is_null() + || recomposition["newspaper_grounding"]["accepted"] != true + || recomposition["issue_file"] != format!("newspaper-wave-{wave_index:02}.md") + || recomposition["issue_audit_file"] + != format!("newspaper-wave-{wave_index:02}.audit.md") + { + anyhow::bail!( + "completed wave newspaper recomposition does not bind its exact accepted issue" + ) + } + let issue_path = root.join(format!("newspaper-wave-{wave_index:02}.md")); + let issue_audit_path = root.join(format!("newspaper-wave-{wave_index:02}.audit.md")); + if !issue_path.is_file() || !issue_audit_path.is_file() { + anyhow::bail!("completed wave newspaper recomposition lost its rendered artifact") + } + let recomposed_issue: ghostlight_dungeon::newspaper::WorldNewspaperIssue = + serde_json::from_value(recomposition["issue"].clone()).map_err(|error| { + anyhow::anyhow!( + "completed wave newspaper recomposition has an invalid issue: {error}" + ) + })?; + let reader_copy = std::fs::read_to_string(&issue_path)?; + let audit_copy = std::fs::read_to_string(&issue_audit_path)?; + if reader_copy + != ghostlight_dungeon::newspaper::render_world_newspaper_markdown(&recomposed_issue) + || audit_copy + != ghostlight_dungeon::newspaper::render_world_newspaper_audit_markdown( + &recomposed_issue, + ) + || recomposition["issue_digest"] != strategic_smoke_digest(&recomposed_issue)? + || recomposition["model_receipt_set_digest"] + != recomposed_model_receipt_set_digest( + &recomposition["newspaper_model_receipts"], + )? + || recomposition["reader_copy_digest"] + != strategic_smoke_bytes_digest(reader_copy.as_bytes()) + || recomposition["audit_copy_digest"] + != strategic_smoke_bytes_digest(audit_copy.as_bytes()) + { + anyhow::bail!( + "completed wave newspaper recomposition artifact differs from its receipt" + ) + } + let report = wave_reports[report_index] + .as_object_mut() + .ok_or_else(|| anyhow::anyhow!("completed wave report is not an object"))?; + for field in ["issue", "newspaper_grounding", "newspaper_model_receipts"] { + report.insert(field.into(), recomposition[field].clone()); + } + report.insert("issue_path".into(), serde_json::to_value(issue_path)?); + report.insert( + "issue_audit_path".into(), + serde_json::to_value(issue_audit_path)?, + ); + report.insert("newspaper_error".into(), serde_json::Value::Null); + } + } let final_plan = final_wave_field(&wave_reports, "plan")?; let final_commit = final_wave_field(&wave_reports, "commit")?; let final_model_receipt_hash = final_wave_field(&wave_reports, "model_receipt_hash")?; @@ -2278,9 +2493,10 @@ fn strategic_campaign() -> ghostlight_dungeon::domain::Campaign { mod tests { use super::{ admitted_public_channel, civic_manifest_is_committed_candidate, - committed_elaboration_mutation_proof, final_wave_field, latest_partial_wave_checkpoint, - publish_immutable_checkpoint, recover_committed_clock_binding, strategic_campaign, - strategic_locality_request, strategic_titled_locality_request, + committed_elaboration_mutation_proof, completed_wave_issue_campaign, final_wave_field, + latest_partial_wave_checkpoint, publish_immutable_checkpoint, + recomposed_model_receipt_set_digest, recover_committed_clock_binding, strategic_campaign, + strategic_locality_request, strategic_smoke_digest, strategic_titled_locality_request, titled_failure_checkpoint_paths, }; @@ -2316,6 +2532,105 @@ mod tests { assert_eq!(commit["campaign"]["revision"], 2); } + #[test] + fn recomposition_receipt_rehashes_typed_model_receipts() { + let receipts = vec![ghostlight_dungeon::model::ModelStageReceipt { + schema: "ghostlight.model_stage_receipt.v1".into(), + receipt_hash: "sha256:receipt".into(), + provider: "fixture".into(), + model: "fixture-model".into(), + stage: "newspaper_editor".into(), + snapshot_binding: "sha256:snapshot".into(), + request_hash: "sha256:request".into(), + output_hash: "sha256:output".into(), + source_receipt_ids: vec!["sha256:source".into()], + latency_ms: 1, + validation_result: "accepted".into(), + local_validation_error: None, + input_chars: 10, + output_chars: 20, + provider_attempts: Vec::new(), + }]; + let value = serde_json::to_value(&receipts).unwrap(); + + assert_eq!( + recomposed_model_receipt_set_digest(&value).unwrap(), + strategic_smoke_digest(&receipts).unwrap() + ); + assert!(recomposed_model_receipt_set_digest(&serde_json::json!({})).is_err()); + } + + #[test] + fn completed_wave_newspaper_resume_uses_only_that_waves_committed_news() { + let mut first = strategic_campaign(); + first.revision = 1; + let first_event = ghostlight_dungeon::domain::Event { + id: "event:first".into(), + at: first.world_time, + kind: "fixture".into(), + summary: "The first event entered the public ledger.".into(), + actor_ids: Vec::new(), + institution_ids: vec!["board".into()], + gestalt_ids: Vec::new(), + location_ids: vec!["yard".into()], + public_channels: vec!["root-wire broadsheet".into()], + }; + ghostlight_dungeon::domain::append_event_with_publications(&mut first, first_event); + let mut second = first.clone(); + second.revision = 2; + let second_event = ghostlight_dungeon::domain::Event { + id: "event:second".into(), + at: second.world_time, + kind: "fixture".into(), + summary: "The second event entered the public ledger.".into(), + actor_ids: Vec::new(), + institution_ids: vec!["synod".into()], + gestalt_ids: Vec::new(), + location_ids: vec!["depot".into()], + public_channels: vec!["root-wire broadsheet".into()], + }; + ghostlight_dungeon::domain::append_event_with_publications(&mut second, second_event); + let reports = vec![ + serde_json::json!({ + "wave_index":1, + "world_revision_after":1, + "commit":{"campaign":first}, + }), + serde_json::json!({ + "wave_index":2, + "world_revision_after":2, + "commit":{"campaign":second}, + }), + ]; + + let issue_campaign = completed_wave_issue_campaign(&reports, 1).unwrap(); + + assert_eq!(issue_campaign.revision, 2); + assert_eq!(issue_campaign.events.len(), 2); + assert_eq!(issue_campaign.news.len(), 1); + assert!( + issue_campaign.news[0] + .event_ids + .contains(&"event:second".into()) + ); + + let mut tampered_prefix = reports.clone(); + tampered_prefix[0]["commit"]["campaign"]["news"][0]["headline"] = + "A different earlier publication".into(); + assert!( + completed_wave_issue_campaign(&tampered_prefix, 1) + .unwrap_err() + .to_string() + .contains("does not preserve its exact prior prefix") + ); + assert!( + completed_wave_issue_campaign(&reports, 0) + .unwrap_err() + .to_string() + .contains("cannot recover its exact pre-wave news boundary") + ); + } + #[test] fn public_channel_requires_one_concrete_information_route() { assert_eq!( diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 76f46ac..f6c4642 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -1,6 +1,6 @@ # Ghostlight Fresh Workspace Handoff -Updated: 2026-08-28 +Updated: 2026-08-29 This is the compact re-entry packet. It records current authority, deployed truth, proof, and the next decision. Git history owns chronology; the system map @@ -1009,34 +1009,49 @@ wave 15. That materialized-versus-dormant routing fix was pushed in exact source Run 41 cloned run 40 at committed revision 23, removed only copied wave-15 failure artifacts, and did not replay prior waves. It committed waves 15 through -18 through revision 27 with 363 events and 853 news rows. The unit then failed -only during final newspaper composition: one-pass grounding reconciliation -rejected distinct findings that shared one editable target. The remote root is +18 through revision 27 with 363 events and 853 news rows. Issue 14 is accepted; +Issues 13 and 15 through 18 are absent because their old grounding repair failed +after the corresponding immutable world-wave commit. Final newspaper composition +also failed. The remote root is `/var/lib/gamecult/ghostlight-dungeon/acceptance/elven-realms-autonomous-46810c3-41`. `BUILD-WITNESS` binds the run-40 resume root and -`live_service_mutated=false`. Simulation mechanics are complete; no accepted -final digest exists. Runs 38, 39, and 40 remain preserved, and acceptance is -incomplete. +`live_service_mutated=false`. Simulation mechanics are complete; the derived +publication sequence and final digest are incomplete. Runs 38, 39, and 40 +remain preserved, and acceptance is incomplete. -The current uncommitted `newspaper.rs` diff groups replacements by editable -target, validates every exact phrase once against the original copy, rejects -overlapping ranges, and atomically applies distinct non-overlapping replacements -in reverse source order before requiring the complete finding set and fresh -whole-page verification. Its defining regression and all newspaper tests pass; -the full 468-test library gate and `git diff --check` also pass. The fix is not -yet committed, pushed, provider-proven, or editorially accepted. +Pushed `19a66a4` groups newspaper grounding replacements by editable target, +validates every exact phrase once against the original copy, rejects overlapping +ranges, and atomically applies distinct non-overlapping replacements in reverse +source order before requiring the complete finding set and fresh whole-page +verification. Its defining regression, all newspaper tests, the full 468-test +library gate, and `git diff --check` pass. Provider and editorial acceptance +remain open. + +The current uncommitted strategic-smoke runner diff adds a presentation-only +recovery seam for missing per-wave editions. It derives each absent issue from +that wave's immutable committed campaign snapshot and exact prior committed news +boundary, publishes an immutable recomposition receipt, and skips accepted +issues such as Issue 14. It fails closed when the exact prior boundary is +unavailable, including a missing first-wave issue. Every recomposition receipt +binds source, contract, issue, typed model receipts, rendered content, and file +digests; typed receipt JSON roundtrip is covered. The complete local gate passes +12/12 strategic-smoke binary tests, 468/468 library tests, and +`git diff --check`. It cannot replay or mutate world mechanics and is not yet +committed, pushed, or provider-proven. Pushed `af749de` routes a provider timeout through the shared model transport's existing one same-request retry and records attempt telemetry. Run 36 crossed that boundary; timeout retry is not its active blocker. -1. Commit and push the locally verified atomic same-target grounding edit fix. -2. Resume only final newspaper composition from run 41's committed revision 27. - Do not replay waves 15-18 or any earlier wave. Require the resulting unedited - final digest to pass fresh independent editorial and provenance review before - claiming acceptance. Preserve runs 38/39/40, the run-41 root and committed - world lineage, stopped run 23, exact acceptance conditions, and unchanged - live daemons. +1. Commit and push the locally verified missing-edition recomposition boundary. +2. Launch exact-source run 42 from run 41's committed revision 27 to compose only missing Issues 13 + and 15 through 18 plus the final digest. Preserve accepted Issue 14, every + completed wave checkpoint, and canonical world state; do not replay any + mechanics. Require the resulting unedited publications and final digest to + pass fresh independent editorial and provenance review before claiming + acceptance. Preserve runs 38/39/40, the run-41 root and committed world + lineage, stopped run 23, exact acceptance conditions, and unchanged live + daemons. 3. Have the game-side adapter lower its authored hierarchy into the published generic schemas; keep projection and economy translation consumer-owned. 4. Prove one consumer-owned effect crosses that public API, changes two foreign diff --git a/notes/ghostlight-current-system-map.md b/notes/ghostlight-current-system-map.md index d1226f1..1939b78 100644 --- a/notes/ghostlight-current-system-map.md +++ b/notes/ghostlight-current-system-map.md @@ -1340,6 +1340,57 @@ text before Markdown emission; the provenance renderer applies the same plain-text boundary to its audit data. Neither editor, copy desk, renderer, nor receipt persistence can write world state. +#### Acceptance-driver per-wave newspaper recovery + +- **Owner:** the strategic acceptance driver owns recovery of a missing + consumer artifact after its world wave has already committed. It owns neither + the wave nor the newspaper's factual or editorial judgment. +- **Inputs:** the missing wave's immutable checkpoint and committed `Campaign` + snapshot, the preceding wave checkpoint's committed news-ledger boundary, + the configured title and voice, and the same campaign receipt store used by + ordinary newspaper composition. +- **Output:** one separately named immutable recomposition checkpoint containing + the accepted issue, grounding verdict, and newspaper model receipts, plus the + rendered reader and audit files. Its receipt binds the narrowed source + campaign, title/voice/article-budget contract, issue, model-receipt collection, + expected filenames, and both rendered byte streams by digest. The driver fills + only the missing newspaper fields in its in-memory result projection. Existing + successful issues and original wave checkpoints remain unchanged. +- **Derived state:** the newsroom receives a clone of the completed wave's + campaign whose news ledger is narrowed to rows appended since the preceding + committed boundary. All events and canonical names remain available only so + those exact news rows can resolve their sources; the clone is a consumer view, + not a campaign mutation. +- **Forbidden writers:** recovery cannot rerun a strategic tick, Nemesis, + simulation cells, outcome resolution, clocks, or `WorldKernel`, and it cannot + alter campaign, event, or news state. A failed recomposition stops consumer + recovery and cannot repair mechanics. +- **Shared paths:** live per-wave composition, recovery composition, and final + combined composition all call `compose_persisted_newspaper`, including the + same grounding/copy-desk path and idempotent model-receipt persistence. A + later resume consumes the successful recomposition checkpoint instead of + paying for the newspaper again, but only after recomputing its source and + contract bindings, deserializing the issue, regenerating reader and audit copy, + and comparing the exact stored files and digests. +- **Verification layer:** the focused boundary regression proves that a second + wave is recomposed from only its newly appended news row while retaining the + committed event ledger needed to resolve that row. It also rejects prior-news + prefix drift and confirms that first-wave recovery fails closed without an + exact pre-run boundary. Immutable checkpoint publication separately rejects + overwrite and publishes by synchronized temporary-file rename. A typed + receipt-digest regression proves that checkpoint JSON is deserialized back to + `Vec` before hashing and rejects an invalid receipt shape, + so creation and verification share one representation. The current binary and + library test suites pass. + +The first-wave limit is now an explicit closed boundary rather than inferred +recovery: without a preserved pre-run news prefix the driver refuses to compose. +Run 41 already has its first issue, so the bounded recovery set is later missing +issues only. The remaining limitation is verification breadth, not split +authority: no focused test yet executes the complete resume orchestration from +fresh recomposition through later reuse while asserting successful-issue +preservation, exact rendered-file validation, and absence of mechanics calls. + Session Zero compilation, expansion, and fission follow the same projection rule. Review projections expose topology, pressures, source-use coverage, gaps, assumptions, public party cards, and only the current viewer's private ledger. diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 45c2016..58d3d50 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -202,4 +202,6 @@ {"note": "Local package verification for the uncommitted Nemesis/causal-context/complete-mismatch/grounding-reconciliation cut is complete. All 464 library tests pass. The package binary run passed 42 of 44 tests; its two failures were fixture-only because test clocks bound the externally controlled player. The shared fixture now binds exact observable consequence scope to one non-active aggregate institution, and both failed binary tests pass when resumed individually. Gate-running steering is retired. Next action is commit/push, then exact-source run40 from immutable run39 revision21 without replay. Run39 independent editorial and grounding FAILs remain authoritative until new reviews reverse them.", "status": "ok", "ts": "2026-08-29T02:37:26+00:00", "type": "verification"} {"note": "Exact-source run40 at 92c7647014dc3ffc53bc89d5bd291b81ba053e81 resumed immutable run39 without replay, committed waves13-14 through revision23, then terminally failed wave15 after five rejected pulses with causal response owner is absent from its exact cell slice. The remote root is /var/lib/gamecult/ghostlight-dungeon/acceptance/elven-realms-autonomous-b040a35-40 and BUILD-WITNESS records live_service_mutated=false. status.json is stale at running/revision23; the wave15 terminal artifact and inactive service own terminal state. Body diagnosis falsifies run40 launch/editorial review as the active next action: responder_cell mapped a materialized member actor to its dormant aggregate Gestalt cell. Current uncommitted source maps materialized members to actor cells and only dormant members to Gestalt cells; materialized_member_responders_use_their_actor_cell passes. Review, commit/push, and provider resume from revision23 without replaying waves13-14 remain pending. Runs38/39 remain immutable and acceptance remains incomplete.", "status": "ok", "ts": "2026-08-29T03:39:51+00:00", "type": "correction"} {"note": "Exact-source run41 at 46810c31b15c880026ee22c0318a563e638c42bc cloned run40 at committed revision23, removed only copied wave15 failure artifacts, and did not replay prior waves. It committed waves15-18 through revision27 with 363 events and 853 news rows, then failed only during final newspaper composition because one-pass grounding reconciliation rejected distinct findings sharing one editable target. The remote root is /var/lib/gamecult/ghostlight-dungeon/acceptance/elven-realms-autonomous-46810c3-41 and BUILD-WITNESS records live_service_mutated=false. Mechanics are complete; no accepted final digest exists. Current uncommitted newspaper.rs groups edits by target, validates each exact phrase once against original copy, rejects overlaps, and atomically applies distinct non-overlapping ranges before complete-finding and fresh-page verification. Its defining and all newspaper tests pass; full lib gate, commit/push, and provider resume of newspaper composition only remain pending. Waves15-18 must not replay. Runs38/39/40 remain preserved and acceptance incomplete.", "status": "ok", "ts": "2026-08-29T04:39:40+00:00", "type": "correction"} -{"ts": "2026-08-29T04:42:11+00:00", "type": "verification", "status": "ok", "note": "The atomic same-target grounding edit fix passes the full Ghostlight Dungeon library gate at 468/468 tests and git diff --check. Full-library-pending steering is retired. The fix remains uncommitted, unpushed, provider-unproven, and editorially unaccepted; next action is commit/push, then resume only final newspaper composition from run41 revision27 without replaying any wave."} +{"note": "The atomic same-target grounding edit fix passes the full Ghostlight Dungeon library gate at 468/468 tests and git diff --check. Full-library-pending steering is retired. The fix remains uncommitted, unpushed, provider-unproven, and editorially unaccepted; next action is commit/push, then resume only final newspaper composition from run41 revision27 without replaying any wave.", "status": "ok", "ts": "2026-08-29T04:42:11+00:00", "type": "verification"} +{"note": "Run41 revision27 contains accepted Issue14, but Issues13 and15-18 are absent because their old grounding repair failed after immutable world-wave commits; final composition also failed. Final-digest-only steering is retired. Pushed 19a66a4 owns atomic same-target grounding repair and passed 468 library tests plus diff check. The current uncommitted strategic-smoke runner cut derives only missing per-wave editions from exact immutable completed-wave campaign snapshots and the prior committed news prefix, writes immutable recomposition receipts, skips accepted Issue14, and cannot replay world mechanics. Review/verify/commit/push, then resume missing Issues13 and15-18 plus the final digest from run41 revision27; independent editorial/provenance acceptance remains pending.", "status": "ok", "ts": "2026-08-29T04:50:24+00:00", "type": "correction"} +{"ts": "2026-08-29T04:56:14+00:00", "type": "verification", "status": "ok", "note": "The missing-edition recomposition runner seam passes its complete local gate: 12/12 strategic-smoke binary tests, 468/468 library tests, and git diff --check. Modeling corrections bind exact prior news prefixes, fail closed when a first-wave or other exact boundary cannot be recovered, and bind immutable recomposition receipts to source, contract, issue, typed model receipts, rendered content, and file digests; typed receipt JSON roundtrip is covered. Review/verification-pending steering is retired. Next action is commit/push, then exact-source run42 from run41 revision27 without world replay, preserving accepted Issue14 while composing only missing Issues13 and15-18 plus the final digest."} diff --git a/state/ghostlight-state.cultcache.jsonl b/state/ghostlight-state.cultcache.jsonl index ab5d96b..2ea7c58 100644 --- a/state/ghostlight-state.cultcache.jsonl +++ b/state/ghostlight-state.cultcache.jsonl @@ -204,3 +204,5 @@ {"key": "2026-08-29T033951Z0000-0202-2b3b6ecf7585", "payload": "eyJub3RlIjogIkV4YWN0LXNvdXJjZSBydW40MCBhdCA5MmM3NjQ3MDE0ZGMzZmZjNTNiYzg5ZDViZDI5MWI4MWJhMDUzZTgxIHJlc3VtZWQgaW1tdXRhYmxlIHJ1bjM5IHdpdGhvdXQgcmVwbGF5LCBjb21taXR0ZWQgd2F2ZXMxMy0xNCB0aHJvdWdoIHJldmlzaW9uMjMsIHRoZW4gdGVybWluYWxseSBmYWlsZWQgd2F2ZTE1IGFmdGVyIGZpdmUgcmVqZWN0ZWQgcHVsc2VzIHdpdGggY2F1c2FsIHJlc3BvbnNlIG93bmVyIGlzIGFic2VudCBmcm9tIGl0cyBleGFjdCBjZWxsIHNsaWNlLiBUaGUgcmVtb3RlIHJvb3QgaXMgL3Zhci9saWIvZ2FtZWN1bHQvZ2hvc3RsaWdodC1kdW5nZW9uL2FjY2VwdGFuY2UvZWx2ZW4tcmVhbG1zLWF1dG9ub21vdXMtYjA0MGEzNS00MCBhbmQgQlVJTEQtV0lUTkVTUyByZWNvcmRzIGxpdmVfc2VydmljZV9tdXRhdGVkPWZhbHNlLiBzdGF0dXMuanNvbiBpcyBzdGFsZSBhdCBydW5uaW5nL3JldmlzaW9uMjM7IHRoZSB3YXZlMTUgdGVybWluYWwgYXJ0aWZhY3QgYW5kIGluYWN0aXZlIHNlcnZpY2Ugb3duIHRlcm1pbmFsIHN0YXRlLiBCb2R5IGRpYWdub3NpcyBmYWxzaWZpZXMgcnVuNDAgbGF1bmNoL2VkaXRvcmlhbCByZXZpZXcgYXMgdGhlIGFjdGl2ZSBuZXh0IGFjdGlvbjogcmVzcG9uZGVyX2NlbGwgbWFwcGVkIGEgbWF0ZXJpYWxpemVkIG1lbWJlciBhY3RvciB0byBpdHMgZG9ybWFudCBhZ2dyZWdhdGUgR2VzdGFsdCBjZWxsLiBDdXJyZW50IHVuY29tbWl0dGVkIHNvdXJjZSBtYXBzIG1hdGVyaWFsaXplZCBtZW1iZXJzIHRvIGFjdG9yIGNlbGxzIGFuZCBvbmx5IGRvcm1hbnQgbWVtYmVycyB0byBHZXN0YWx0IGNlbGxzOyBtYXRlcmlhbGl6ZWRfbWVtYmVyX3Jlc3BvbmRlcnNfdXNlX3RoZWlyX2FjdG9yX2NlbGwgcGFzc2VzLiBSZXZpZXcsIGNvbW1pdC9wdXNoLCBhbmQgcHJvdmlkZXIgcmVzdW1lIGZyb20gcmV2aXNpb24yMyB3aXRob3V0IHJlcGxheWluZyB3YXZlczEzLTE0IHJlbWFpbiBwZW5kaW5nLiBSdW5zMzgvMzkgcmVtYWluIGltbXV0YWJsZSBhbmQgYWNjZXB0YW5jZSByZW1haW5zIGluY29tcGxldGUuIiwgInN0YXR1cyI6ICJvayIsICJ0cyI6ICIyMDI2LTA4LTI5VDAzOjM5OjUxKzAwOjAwIiwgInR5cGUiOiAiY29ycmVjdGlvbiJ9", "stored_at": "2026-08-29T03:39:51.457166+00:00", "type": "ghostlight.evidence_record.v0"} {"key": "2026-08-29T043940Z0000-0203-3baa93dbc5cf", "payload": "eyJub3RlIjogIkV4YWN0LXNvdXJjZSBydW40MSBhdCA0NjgxMGMzMWIxNWM4ODAwMjZlZTIyYzAzMThhNTYzZTYzOGM0MmJjIGNsb25lZCBydW40MCBhdCBjb21taXR0ZWQgcmV2aXNpb24yMywgcmVtb3ZlZCBvbmx5IGNvcGllZCB3YXZlMTUgZmFpbHVyZSBhcnRpZmFjdHMsIGFuZCBkaWQgbm90IHJlcGxheSBwcmlvciB3YXZlcy4gSXQgY29tbWl0dGVkIHdhdmVzMTUtMTggdGhyb3VnaCByZXZpc2lvbjI3IHdpdGggMzYzIGV2ZW50cyBhbmQgODUzIG5ld3Mgcm93cywgdGhlbiBmYWlsZWQgb25seSBkdXJpbmcgZmluYWwgbmV3c3BhcGVyIGNvbXBvc2l0aW9uIGJlY2F1c2Ugb25lLXBhc3MgZ3JvdW5kaW5nIHJlY29uY2lsaWF0aW9uIHJlamVjdGVkIGRpc3RpbmN0IGZpbmRpbmdzIHNoYXJpbmcgb25lIGVkaXRhYmxlIHRhcmdldC4gVGhlIHJlbW90ZSByb290IGlzIC92YXIvbGliL2dhbWVjdWx0L2dob3N0bGlnaHQtZHVuZ2Vvbi9hY2NlcHRhbmNlL2VsdmVuLXJlYWxtcy1hdXRvbm9tb3VzLTQ2ODEwYzMtNDEgYW5kIEJVSUxELVdJVE5FU1MgcmVjb3JkcyBsaXZlX3NlcnZpY2VfbXV0YXRlZD1mYWxzZS4gTWVjaGFuaWNzIGFyZSBjb21wbGV0ZTsgbm8gYWNjZXB0ZWQgZmluYWwgZGlnZXN0IGV4aXN0cy4gQ3VycmVudCB1bmNvbW1pdHRlZCBuZXdzcGFwZXIucnMgZ3JvdXBzIGVkaXRzIGJ5IHRhcmdldCwgdmFsaWRhdGVzIGVhY2ggZXhhY3QgcGhyYXNlIG9uY2UgYWdhaW5zdCBvcmlnaW5hbCBjb3B5LCByZWplY3RzIG92ZXJsYXBzLCBhbmQgYXRvbWljYWxseSBhcHBsaWVzIGRpc3RpbmN0IG5vbi1vdmVybGFwcGluZyByYW5nZXMgYmVmb3JlIGNvbXBsZXRlLWZpbmRpbmcgYW5kIGZyZXNoLXBhZ2UgdmVyaWZpY2F0aW9uLiBJdHMgZGVmaW5pbmcgYW5kIGFsbCBuZXdzcGFwZXIgdGVzdHMgcGFzczsgZnVsbCBsaWIgZ2F0ZSwgY29tbWl0L3B1c2gsIGFuZCBwcm92aWRlciByZXN1bWUgb2YgbmV3c3BhcGVyIGNvbXBvc2l0aW9uIG9ubHkgcmVtYWluIHBlbmRpbmcuIFdhdmVzMTUtMTggbXVzdCBub3QgcmVwbGF5LiBSdW5zMzgvMzkvNDAgcmVtYWluIHByZXNlcnZlZCBhbmQgYWNjZXB0YW5jZSBpbmNvbXBsZXRlLiIsICJzdGF0dXMiOiAib2siLCAidHMiOiAiMjAyNi0wOC0yOVQwNDozOTo0MCswMDowMCIsICJ0eXBlIjogImNvcnJlY3Rpb24ifQ==", "stored_at": "2026-08-29T04:39:40.023790+00:00", "type": "ghostlight.evidence_record.v0"} {"key": "2026-08-29T044211Z0000-0204-047d6e877da9", "payload": "eyJub3RlIjogIlRoZSBhdG9taWMgc2FtZS10YXJnZXQgZ3JvdW5kaW5nIGVkaXQgZml4IHBhc3NlcyB0aGUgZnVsbCBHaG9zdGxpZ2h0IER1bmdlb24gbGlicmFyeSBnYXRlIGF0IDQ2OC80NjggdGVzdHMgYW5kIGdpdCBkaWZmIC0tY2hlY2suIEZ1bGwtbGlicmFyeS1wZW5kaW5nIHN0ZWVyaW5nIGlzIHJldGlyZWQuIFRoZSBmaXggcmVtYWlucyB1bmNvbW1pdHRlZCwgdW5wdXNoZWQsIHByb3ZpZGVyLXVucHJvdmVuLCBhbmQgZWRpdG9yaWFsbHkgdW5hY2NlcHRlZDsgbmV4dCBhY3Rpb24gaXMgY29tbWl0L3B1c2gsIHRoZW4gcmVzdW1lIG9ubHkgZmluYWwgbmV3c3BhcGVyIGNvbXBvc2l0aW9uIGZyb20gcnVuNDEgcmV2aXNpb24yNyB3aXRob3V0IHJlcGxheWluZyBhbnkgd2F2ZS4iLCAic3RhdHVzIjogIm9rIiwgInRzIjogIjIwMjYtMDgtMjlUMDQ6NDI6MTErMDA6MDAiLCAidHlwZSI6ICJ2ZXJpZmljYXRpb24ifQ==", "stored_at": "2026-08-29T04:42:11.214127+00:00", "type": "ghostlight.evidence_record.v0"} +{"key": "2026-08-29T045024Z0000-0205-4297c85a7db1", "payload": "eyJub3RlIjogIlJ1bjQxIHJldmlzaW9uMjcgY29udGFpbnMgYWNjZXB0ZWQgSXNzdWUxNCwgYnV0IElzc3VlczEzIGFuZDE1LTE4IGFyZSBhYnNlbnQgYmVjYXVzZSB0aGVpciBvbGQgZ3JvdW5kaW5nIHJlcGFpciBmYWlsZWQgYWZ0ZXIgaW1tdXRhYmxlIHdvcmxkLXdhdmUgY29tbWl0czsgZmluYWwgY29tcG9zaXRpb24gYWxzbyBmYWlsZWQuIEZpbmFsLWRpZ2VzdC1vbmx5IHN0ZWVyaW5nIGlzIHJldGlyZWQuIFB1c2hlZCAxOWE2NmE0IG93bnMgYXRvbWljIHNhbWUtdGFyZ2V0IGdyb3VuZGluZyByZXBhaXIgYW5kIHBhc3NlZCA0NjggbGlicmFyeSB0ZXN0cyBwbHVzIGRpZmYgY2hlY2suIFRoZSBjdXJyZW50IHVuY29tbWl0dGVkIHN0cmF0ZWdpYy1zbW9rZSBydW5uZXIgY3V0IGRlcml2ZXMgb25seSBtaXNzaW5nIHBlci13YXZlIGVkaXRpb25zIGZyb20gZXhhY3QgaW1tdXRhYmxlIGNvbXBsZXRlZC13YXZlIGNhbXBhaWduIHNuYXBzaG90cyBhbmQgdGhlIHByaW9yIGNvbW1pdHRlZCBuZXdzIHByZWZpeCwgd3JpdGVzIGltbXV0YWJsZSByZWNvbXBvc2l0aW9uIHJlY2VpcHRzLCBza2lwcyBhY2NlcHRlZCBJc3N1ZTE0LCBhbmQgY2Fubm90IHJlcGxheSB3b3JsZCBtZWNoYW5pY3MuIFJldmlldy92ZXJpZnkvY29tbWl0L3B1c2gsIHRoZW4gcmVzdW1lIG1pc3NpbmcgSXNzdWVzMTMgYW5kMTUtMTggcGx1cyB0aGUgZmluYWwgZGlnZXN0IGZyb20gcnVuNDEgcmV2aXNpb24yNzsgaW5kZXBlbmRlbnQgZWRpdG9yaWFsL3Byb3ZlbmFuY2UgYWNjZXB0YW5jZSByZW1haW5zIHBlbmRpbmcuIiwgInN0YXR1cyI6ICJvayIsICJ0cyI6ICIyMDI2LTA4LTI5VDA0OjUwOjI0KzAwOjAwIiwgInR5cGUiOiAiY29ycmVjdGlvbiJ9", "stored_at": "2026-08-29T04:50:24.104359+00:00", "type": "ghostlight.evidence_record.v0"} +{"key": "2026-08-29T045614Z0000-0206-39415f5f633c", "payload": "eyJub3RlIjogIlRoZSBtaXNzaW5nLWVkaXRpb24gcmVjb21wb3NpdGlvbiBydW5uZXIgc2VhbSBwYXNzZXMgaXRzIGNvbXBsZXRlIGxvY2FsIGdhdGU6IDEyLzEyIHN0cmF0ZWdpYy1zbW9rZSBiaW5hcnkgdGVzdHMsIDQ2OC80NjggbGlicmFyeSB0ZXN0cywgYW5kIGdpdCBkaWZmIC0tY2hlY2suIE1vZGVsaW5nIGNvcnJlY3Rpb25zIGJpbmQgZXhhY3QgcHJpb3IgbmV3cyBwcmVmaXhlcywgZmFpbCBjbG9zZWQgd2hlbiBhIGZpcnN0LXdhdmUgb3Igb3RoZXIgZXhhY3QgYm91bmRhcnkgY2Fubm90IGJlIHJlY292ZXJlZCwgYW5kIGJpbmQgaW1tdXRhYmxlIHJlY29tcG9zaXRpb24gcmVjZWlwdHMgdG8gc291cmNlLCBjb250cmFjdCwgaXNzdWUsIHR5cGVkIG1vZGVsIHJlY2VpcHRzLCByZW5kZXJlZCBjb250ZW50LCBhbmQgZmlsZSBkaWdlc3RzOyB0eXBlZCByZWNlaXB0IEpTT04gcm91bmR0cmlwIGlzIGNvdmVyZWQuIFJldmlldy92ZXJpZmljYXRpb24tcGVuZGluZyBzdGVlcmluZyBpcyByZXRpcmVkLiBOZXh0IGFjdGlvbiBpcyBjb21taXQvcHVzaCwgdGhlbiBleGFjdC1zb3VyY2UgcnVuNDIgZnJvbSBydW40MSByZXZpc2lvbjI3IHdpdGhvdXQgd29ybGQgcmVwbGF5LCBwcmVzZXJ2aW5nIGFjY2VwdGVkIElzc3VlMTQgd2hpbGUgY29tcG9zaW5nIG9ubHkgbWlzc2luZyBJc3N1ZXMxMyBhbmQxNS0xOCBwbHVzIHRoZSBmaW5hbCBkaWdlc3QuIiwgInN0YXR1cyI6ICJvayIsICJ0cyI6ICIyMDI2LTA4LTI5VDA0OjU2OjE0KzAwOjAwIiwgInR5cGUiOiAidmVyaWZpY2F0aW9uIn0=", "stored_at": "2026-08-29T04:56:14.185307+00:00", "type": "ghostlight.evidence_record.v0"} diff --git a/state/map.yaml b/state/map.yaml index 7ee1dc8..94be4db 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -325,7 +325,7 @@ data_flow: current_status: summary: Ghostlight's generic consumer boundary admits sparse WorldSeeds, runs the hundreds-class parallel cell machinery used by Dungeon, and projects grounded newspapers from committed public events. Local acceptance proves 1,000 subjects across 200 cells and two-wave durable named-person emergence. Exact-source provider run 38 at `596e50679244b5d42b4cfdf70d723f815751b8ff` resumed the preserved world at revision 8 without replaying compilation or four committed locality elaborations, recovered 51 of 52 strategic-wave cells on pulse three and the final cell on pulse four, then committed all six waves through revision 14. Pushed commit `3a69306f3004e4af2cd57bba612a6ad8d1b8132f` implements the mapped clock-consequence owner and is verified locally: world clocks own declared consequence text and exact observable scope; a bounded agent may propose legacy scope binding, while `WorldKernel` alone admits it and materializes stable threshold-crossing events and public news through the common mutation path. Binding, emitted-event ancestry, mutation receipts, and the next newspaper boundary persist atomically. Exact-source run 39 resumed run 38 without replaying its six committed waves, bound clock consequences, committed six new waves through revision 21, and emitted Issues 7 through 12 plus a final digest. This completes the mechanical provider acceptance path; all inherited wave 1-6 checkpoint hashes match run 38 exactly and live services were unchanged. Independent blind editorial review returns FAIL: only Issue 7 approaches spicy, while Issues 8-12 are dominated by plans, filings, inspections, warnings, and abstract relationship deltas without betrayal, scandal, public reaction, sustained reversal, or material lived cost. The defect belongs upstream in causal follow-through and outcome ownership, not in newsroom spice prompting. Independent grounding review also returns FAIL: Issue 9 inflates a planned expedited agenda into completion, while Issue 8 assigns unsupported feminine pronouns to Sela Venn and Eryn Tal. Other grounding mechanics pass: 208/208 citations resolve, no future events appear, no replay occurred, the player remained unchanged, all 116/116 manifest entries verify, the archive is exact, and root independently confirmed the live service PIDs and invocation IDs remained unchanged. Editorial and grounding acceptance are terminally failed for run 39. Eight titled elaborators use the generic agent harness under deterministic weighted scheduling, bounded parallelism, and Luna/Terra/Sol routing. Workers remain proposal-only; deterministic admission binds exact frozen schedules, dispatch, receipts, conflicts, and semantic verification, and `WorldKernel` alone owns atomic CultCache persistence. Destination reconciliation is a private transactional workbench, provider schema compatibility is lowered once at the shared model-connector boundary, and durable elaboration and strategic-pulse checkpoints bind to their canonical mutation proofs. The generic ModelAgent harness remains consumer-neutral transport. Provider run 23 remains stopped; earlier failed runs and checkpoint archives remain immutable provenance. The live daemons remain outside the acceptance body. Dungeon play remains regression pressure, the Delvehold game-side adapter remains unimplemented, and unpublished Mars and Kalsa destination previews remain unapproved. - provider_acceptance_checkpoint: Exact-source run 41 at `46810c31b15c880026ee22c0318a563e638c42bc` cloned run 40 at committed revision 23, removed only the copied wave-15 failure artifacts, and did not replay prior waves. It committed waves 15 through 18 through revision 27 with 363 events and 853 news rows. The unit then failed final newspaper composition because one-pass grounding reconciliation rejected distinct findings that shared one editable target; all simulation mechanics are complete, but there is no accepted final digest. The remote root is `/var/lib/gamecult/ghostlight-dungeon/acceptance/elven-realms-autonomous-46810c3-41`; `BUILD-WITNESS` binds the run-40 resume root and `live_service_mutated=false`. Runs 38, 39, and 40 remain preserved. Run 39's independent FAIL remains historical acceptance evidence, and overall acceptance is still incomplete. - implementation_checkpoint: Pushed run-41 source contains the materialized-versus-dormant responder-cell fix and provider-proved it through waves 15-18. The remaining defect is isolated to newspaper grounding admission: distinct findings may legitimately name non-overlapping exact phrases in the same headline, deck, dateline, or paragraph, but the prior workbench rejected any repeated editable target. The current uncommitted `newspaper.rs` diff groups replacements by target, validates each phrase exactly once against the original copy, rejects overlapping ranges, and atomically applies distinct non-overlapping replacements in reverse source order before requiring the complete finding set and fresh whole-page verification. Its defining regression, all newspaper tests, and the full 468-test library gate pass; `git diff --check` also passes. The fix is not yet committed, pushed, provider-proven, or editorially accepted. - next_action: Commit and push the locally verified atomic same-target grounding edit fix. Resume only final newspaper composition from run 41's committed revision 27; do not replay waves 15-18 or any earlier wave. Require the resulting unedited final digest to pass fresh independent editorial and provenance review before claiming acceptance. Preserve runs 38/39/40, the run-41 root and committed world lineage, stopped run 23, exact acceptance conditions, and unchanged live daemons. + provider_acceptance_checkpoint: Exact-source run 41 at `46810c31b15c880026ee22c0318a563e638c42bc` cloned run 40 at committed revision 23, removed only the copied wave-15 failure artifacts, and did not replay prior waves. It committed waves 15 through 18 through revision 27 with 363 events and 853 news rows. Issue 14 is accepted, but Issues 13 and 15 through 18 are absent because their old grounding repair failed after each immutable world-wave commit; final newspaper composition also failed. All simulation mechanics are complete, but the derived publication sequence and final digest are incomplete. The remote root is `/var/lib/gamecult/ghostlight-dungeon/acceptance/elven-realms-autonomous-46810c3-41`; `BUILD-WITNESS` binds the run-40 resume root and `live_service_mutated=false`. Runs 38, 39, and 40 remain preserved. Run 39's independent FAIL remains historical acceptance evidence, and overall acceptance is still incomplete. + implementation_checkpoint: Pushed `19a66a4` owns the locally verified atomic same-target grounding repair: it validates every exact phrase once against original copy, rejects overlapping ranges, applies distinct non-overlapping replacements atomically, and then requires the complete finding set and fresh whole-page verification. The current uncommitted strategic-smoke runner diff adds a presentation-only recovery seam for missing per-wave editions: it derives each absent issue from that wave's immutable committed campaign snapshot and exact prior committed news boundary, publishes an immutable recomposition receipt, and skips already accepted issues such as Issue 14. It fails closed when the exact prior boundary is unavailable, including a missing first-wave issue, and binds each receipt to source, contract, issue, typed model receipts, rendered content, and file digests; typed receipt JSON roundtrip is covered. The complete local gate passes 12/12 strategic-smoke binary tests, 468/468 library tests, and `git diff --check`. It cannot replay or mutate world mechanics and is not yet committed, pushed, or provider-proven. + next_action: Commit and push the locally verified missing-edition recomposition boundary, then launch exact-source run 42 from run 41's committed revision 27. Compose only missing Issues 13 and 15 through 18 plus the final digest; preserve accepted Issue 14, all wave checkpoints, and canonical world state without replaying any mechanics. Require the resulting unedited publications and final digest to pass fresh independent editorial and provenance review before claiming acceptance. Preserve runs 38/39/40, the run-41 root and committed world lineage, stopped run 23, exact acceptance conditions, and unchanged live daemons. gestalt_population: Gestalt Personas own shared population baselines; persistent member deltas own named individuality; WorldKernel materialization and approval-gated fission preserve lineage and identity. Materialisation/folding changes only representation and the exact member delta, advances resolution epoch, and retires the old cover; it has no Gestalt-wide effect authority. Selected actionable member exceptions are mandatory Projector perspective owners, so aggregation cannot silently erase their decision. Presence projection is limited to nearby active leaves and exact-location dormant members, includes reciprocal player relationship evidence, and may propose an earned cast without committing it. Simulation cells are derived covers only.