diff --git a/crates/ghostlight-dungeon/src/resolution.rs b/crates/ghostlight-dungeon/src/resolution.rs index c4c2728..3b517d7 100644 --- a/crates/ghostlight-dungeon/src/resolution.rs +++ b/crates/ghostlight-dungeon/src/resolution.rs @@ -2197,7 +2197,19 @@ fn validate_strategic_individuations( wave: &ResolutionWaveCommit, selected_actions: &[CellActionProposal], ) -> Result<()> { - if wave.strategic_individuations.len() > 1 { + validate_strategic_individuation_proposals( + campaign, + &wave.strategic_individuations, + selected_actions, + ) +} + +pub(crate) fn validate_strategic_individuation_proposals( + campaign: &Campaign, + proposals: &[StrategicGestaltIndividuation], + selected_actions: &[CellActionProposal], +) -> Result<()> { + if proposals.len() > 1 { return Err(anyhow!( "one strategic wave can admit at most one new person" )); @@ -2206,7 +2218,7 @@ fn validate_strategic_individuations( .iter() .map(|action| Ok((cell_action_digest(action)?, action))) .collect::>>()?; - for proposal in &wave.strategic_individuations { + for proposal in proposals { let action = selected .get(&proposal.action_digest) .ok_or_else(|| anyhow!("strategic individuation is not bound to a selected action"))?; diff --git a/crates/ghostlight-dungeon/src/scheduler.rs b/crates/ghostlight-dungeon/src/scheduler.rs index 8de3f40..26980d5 100644 --- a/crates/ghostlight-dungeon/src/scheduler.rs +++ b/crates/ghostlight-dungeon/src/scheduler.rs @@ -1037,6 +1037,15 @@ async fn propose_strategic_individuation( location_id: location_id.clone(), }, }; + if let Err(error) = crate::resolution::validate_strategic_individuation_proposals( + campaign, + std::slice::from_ref(&proposal), + selected_actions, + ) { + output.receipt.validation_result = "semantic_invalid".into(); + output.receipt.local_validation_error = Some(error.to_string()); + return (Vec::new(), vec![output]); + } let proposal_digest = match strategic_individuation_proposal_digest(&proposal) { Ok(digest) => digest, Err(_) => return (Vec::new(), vec![output]), @@ -1971,7 +1980,9 @@ mod tests { calls: Mutex>, } - struct PersonFixtureModel; + struct PersonFixtureModel { + invalid_relationship: bool, + } #[test] fn gestalt_state_references_qualify_canonical_ids_exactly_once() { @@ -2278,6 +2289,11 @@ mod tests { }) .and_then(serde_json::Value::as_str) .ok_or_else(|| anyhow!("person fixture lacks Gestalt"))?; + let relationships = if self.invalid_relationship { + serde_json::json!({"unknown-court":"owes a private favor"}) + } else { + serde_json::json!({}) + }; Ok(serde_json::json!({"proposals":[{ "action_digest":digest, "gestalt_id":gestalt_id, @@ -2285,7 +2301,7 @@ mod tests { "name":"Veska Rill", "goals":["control the grain delegation"], "obligations":["answer to the river wards"], - "relationships":{}, + "relationships":relationships, "memories":["the lower road vanished after the dwarven excavation"], "rationale":"The delegation needs one accountable broker." }]}) @@ -3182,9 +3198,14 @@ mod tests { }], }; let selected_actions = vec![action, institution_action]; - let (proposals, stages) = - propose_strategic_individuation(&PersonFixtureModel, &campaign, &selected_actions) - .await; + let (proposals, stages) = propose_strategic_individuation( + &PersonFixtureModel { + invalid_relationship: false, + }, + &campaign, + &selected_actions, + ) + .await; assert_eq!(proposals.len(), 1); assert_eq!(stages.len(), 1); assert_eq!(proposals[0].action_digest, digest); @@ -3202,6 +3223,65 @@ mod tests { ); } + #[tokio::test] + async fn invalid_strategic_person_is_rejected_before_wave_admission() { + use crate::domain::{AgencySubjectKind, GestaltPersonaState, StrategicCellEffect}; + let mut campaign = crate::resolution::tests::campaign(1, 1); + let mut profile = campaign.agency_profiles["faction-0000"].clone(); + profile.subject_id = "river-wards".into(); + profile.subject_kind = AgencySubjectKind::Gestalt; + profile.location_ids = BTreeSet::from(["center".into()]); + campaign + .agency_profiles + .insert(profile.subject_id.clone(), profile); + campaign.gestalts.insert( + "river-wards".into(), + GestaltPersonaState { + schema: "ghostlight.gestalt_persona_state.v1".into(), + id: "river-wards".into(), + name: "River Wards".into(), + version: 0, + home_location_id: "center".into(), + shared_capabilities: BTreeSet::new(), + shared_knowledge: BTreeSet::new(), + resources: BTreeSet::new(), + goals: vec!["keep the wards fed".into()], + pressures: vec!["the lower road failed".into()], + }, + ); + let action = crate::domain::CellActionProposal { + subject_id: "river-wards".into(), + intent: "Send a delegation.".into(), + intended_effect: "Find an accountable route broker.".into(), + priority: 80, + state_references: vec![], + public_channels: vec![], + effects: vec![StrategicCellEffect::Gestalt { + gestalt_id: "river-wards".into(), + pressure_additions: vec!["a broker must answer publicly".into()], + pressure_resolutions: vec![], + }], + }; + let (proposals, stages) = propose_strategic_individuation( + &PersonFixtureModel { + invalid_relationship: true, + }, + &campaign, + &[action], + ) + .await; + assert!(proposals.is_empty()); + assert_eq!(stages.len(), 1); + assert_eq!(stages[0].receipt.validation_result, "semantic_invalid"); + assert!( + stages[0] + .receipt + .local_validation_error + .as_deref() + .is_some_and(|error| error.contains("exact Gestalt action authority")) + ); + } + #[tokio::test] async fn one_malformed_cell_aborts_the_whole_wave() { let campaign = crate::resolution::tests::campaign(6, 2); diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index de2bc91..d21dee6 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -480,11 +480,21 @@ Ghostlight-owned organ or special runtime. Ghostlight owns the world beyond the Greathold as a persistent multiresolution political simulation, while Delvehold retains player sovereignty and quantitative economy. WorldKernel now admits at most one action-bound strategic individuation per wave through the existing -individuation commit primitive. The selector proposes identity content; the -kernel retains revision, lineage, location, uniqueness, materialization, and -atomic commit authority. The active budget ceiling is 240, independent of the -32-call provider gate. A fixture proves 1,000 subjects across 200 cells and all -200 membrane pipelines under a seven-call gate. +individuation commit primitive. Resolution owns the exact strategic- +individuation validator. The scheduler now calls that shared validator at +proposal production, records `semantic_invalid`, and drops an invalid optional +person before wave assembly; WorldKernel reuses the same validator at admission +and retains revision, lineage, location, uniqueness, materialization, and +atomic commit authority. The selector proposes identity content only. The +active budget ceiling is 240, independent of the 32-call provider gate. A +fixture proves 1,000 subjects across 200 cells and all 200 membrane pipelines +under a seven-call gate. + +Run 67 is preserved as a failed full-world witness at revision 15. All five +requested realms and all five locality elaborations committed, but no strategic +wave committed. Its failure exposed late rejection of an optional malformed +strategic person, not loss of the admitted world. Resume must preserve those +realm and elaboration commits and begin at the first strategic-wave boundary. Sparse inhabited destinations now compile a versioned civic apparatus in place. The first pass establishes institutions, resident populations, political edges, diff --git a/notes/ghostlight-current-system-map.md b/notes/ghostlight-current-system-map.md index d6f4195..7d04a7f 100644 --- a/notes/ghostlight-current-system-map.md +++ b/notes/ghostlight-current-system-map.md @@ -839,11 +839,19 @@ A separate strategic selector may propose one first-relevance identity only for an exact selected Gestalt-owned action in the current resolution wave. Its receipt binds the eligible Gestalt action digest set, exact locally admitted proposal digest, and world revision. -WorldKernel revalidates the action/Gestalt pair, active simulation authority, -location, version, lineage, bounded identity payload, and uniqueness, then calls -the same `apply_individuation` primitive used by the system-only direct command. -The proposal and strategic plan commit atomically; the selector cannot own -entity creation, location, revision, materialization, or commit. +`resolution::validate_strategic_individuation_proposals` is the single owner of +exact strategic-individuation semantics: one-person budget, selected-action +binding, Gestalt action authority, location, version, lineage, bounded identity +payload, relationships, and uniqueness. The scheduler calls that shared owner +immediately after proposal production. An invalid optional person marks the +selector receipt `semantic_invalid`, records the exact local finding, and is +dropped before wave assembly; the otherwise valid strategic wave may continue. +WorldKernel calls the same validator again against the admitted wave snapshot, +then invokes the same `apply_individuation` primitive used by the system-only +direct command. Early validation is proposal hygiene, not mutation authority or +a substitute for kernel revalidation. The proposal and strategic plan commit +atomically; the selector cannot own entity creation, location, revision, +materialization, or commit. Institution `already_committed_posture` is projected as durable state, distinct from unresolved pressure and fresh choice. Maintaining it is inaction. A model diff --git a/notes/ghostlight-implementation-plan.md b/notes/ghostlight-implementation-plan.md index b5b0ad9..07964dc 100644 --- a/notes/ghostlight-implementation-plan.md +++ b/notes/ghostlight-implementation-plan.md @@ -192,6 +192,12 @@ Competing publications may frame the same committed event substrate differently. Grounding constrains factual assertions and preserves exact event provenance without neutralizing legible interpretation, selection, framing, or insinuation; publication Personas cannot write world state. +Strategic named-person selection is optional proposal production. Resolution +owns one exact strategic-individuation validator; the scheduler uses it to +discard a semantically invalid optional person before wave assembly, while +WorldKernel invokes the same validator again at admission before the shared +individuation commit primitive. Early rejection cannot weaken kernel authority +or turn the scheduler into a second semantic owner. Initial compiler publication is explicitly bounded as an empty-store creation transaction owned by the CampaignRegistry, not a runtime mutation. Named-person presence is a resolution diff --git a/state/map.yaml b/state/map.yaml index deba169..96ea44f 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -91,7 +91,7 @@ live_architecture: - Mutation-scope and action-assessment proposals are content-addressed by the exact scope, assessor, and verifier models, stable instructions, scoped action-specific schema, and permitted typed packet. Private CultCache cache rows are derived inference evidence only. Revision and expiry bind the consumable assessment and roll but do not invalidate an otherwise identical semantic proposal; cache hits revalidate it, emit an explicit zero-token receipt linked to the source scope, assessment, and verifier receipts, and issue a fresh revision-bound digest. Changed actor state, occupancy, facts, institutions, permissions, contract, boundaries, schema, or model contract changes the key. Eve projections itemize every signed modifier and exact reference. - The hosted player story is the chronological lowering of exact committed NarrativeTurn rows. No narrator or verifier model rewrites those turns; historical narration projections are inert and never enter Eve or CultMesh player surfaces. - Epiphany consumes the extracted Ghostlight projection crate at an exact Git revision while retaining her canonical Mind, effect admission, and consequence authority. - - Gestalt Personas own shared population state; durable member deltas preserve specific people between appearances. WorldKernel owns one revision-bound individuation primitive that validates exact Gestalt lineage, location, version, and identity uniqueness before atomically materializing the member. Foreground presence may call it after exact player speech; strategic waves may call it for at most one selected Gestalt action through an action-bound selector receipt. The model proposes identity content only. + - Gestalt Personas own shared population state; durable member deltas preserve specific people between appearances. WorldKernel owns one revision-bound individuation primitive that validates exact Gestalt lineage, location, version, and identity uniqueness before atomically materializing the member. Foreground presence may call it after exact player speech; strategic waves may call it for at most one selected Gestalt action through an action-bound selector receipt. Resolution owns the exact strategic-individuation validator. The scheduler calls that shared validator at proposal production, records semantic_invalid, and drops an invalid optional proposal before wave assembly; WorldKernel revalidates with the same owner at admission. The model proposes identity content only. - The multiresolution agency graph derives a connected cohesive/arena cell cover from canonical actors, institutions, and gestalt leaves under a campaign-selected 1–240 pooled entitlement budget; provider concurrency is independently capped at 32. A 1,000-subject, 200-cell fixture proves complete unique coverage and parallel membrane dispatch under a seven-call provider gate. - Model-driven strategic cell waves can change remote actors, institutions, gestalts, clocks, and exact-channel news while the absent player remains outside their write authority; the entire wave validates and commits atomically. - Population activity effects represent exact attributed attempts to prepare, coordinate, investigate, recruit, obstruct, trade, or communicate; graph scope, locations, channels, and semantics validate before the kernel derives an event, and the effect never claims its own success. @@ -196,7 +196,7 @@ live_architecture: - Institution, faction, and consumer decision models active_subgoals: - - Complete a fresh immutable full-world provider acceptance through the generic strategic driver: compile one bounded root, sequentially admit the ordered outer realms through the existing destination compiler plus WorldKernel ExpandRegion, elaborate those committed jurisdictions, simulate the eligible world under the bounded multiresolution cell cover, advance strategic waves, and emit grounded newspapers. Preserve stopped Runs 64 and 65 as failure evidence. The Delvehold adapter is not a prerequisite while the Greathold remains an external boundary observer and no live Delvehold-owned player, civic, dungeon, or quantitative-economy state crosses the boundary. + - Complete full-world provider acceptance by resuming preserved Run 67 at world revision 15 after the shared strategic-individuation admission cut is verified and pushed. Its five ordered regions and five elaborations are already committed and must not replay. Invalid optional individuation must yield a semantic-invalid receipt and no individuation while leaving an otherwise recoverable strategic wave eligible to proceed. Preserve Runs 64 through 67 as immutable evidence. The Delvehold adapter is not a prerequisite while the Greathold remains an external boundary observer and no live Delvehold-owned player, civic, dungeon, or quantitative-economy state crosses the boundary. - Connect a Delvehold-owned adapter to the admitted generic consumer contract: shared compiler/consumer WorldSeed admission, externally authoritative institution-or-Gestalt snapshots, attributed proposal acknowledgement, and committed-event newspaper composition over typed CultNet, without consumer-specific IDs or schemas in Ghostlight. - Extend strategic/offscreen named-person individuation beyond the proven emerge-then-act path so people plot across sustained political waves, acquire relationships, fold, and return without granting a cell, arena, or model commit authority. - Pressure the proven 200-cell dispatcher with authored consumer hierarchies and measure provider cost; the active budget ceiling is 240 while provider concurrency remains separately capped at 32. @@ -328,7 +328,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. That run-39 diagnosis is superseded for the current phase: run 51 closes the world and reconciliation mechanics, and the active defect belongs to newsroom narrative selection and self-contained semantic provenance over immutable facts. 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 pushed commit `139b8a6` completed Run 60 under `character-newsroom.v10` without world or per-wave replay. Its acceptance witness and terminal status bind the completed page and exact resume lineage. The independently dispatched blind reviewer, shown only the reader-facing fantasy newspaper, read it as a real left-populist civic broadsheet and did not immediately identify procedural generation, so the narrative reverse-Turing criterion passes. The review's substantive criticism is that the three stories feel like three rewrites of one briefing; accepted Run 60 remains immutable. The run used 96,902 prompt tokens and reported zero cache hits. Its delivered typeset artifact is `output/pdf/canopy-ledger-run-60-front-page.pdf`; frozen-source and visual verification remain accepted. Exact pushed `bc2ca39` then produced frozen-fact newsroom-only Run 61 Issues 13, 15, 16, 17, and 18 without world or wave model replay. Provider output proves `special_issue` convergence in Issues 13 and 15, independent `general_issue` beats in Issues 16 through 18, and assignment-selector cache hits. Blind review of Issues 13, 15, 16, and 17 passes both distinct recurring voices and issue-shape diversity, recognizing Ossan, Mera, Corin, and Lysa's biases plus modest cross-issue evolution. Run 62 exercised v12 without world or wave replay and failed only because two lead articles proposed invalid datelines; recurring reporter calls still recorded zero cache hits. Run 63 used exact pushed v13 `a52d57b`; Issues 17 and 18 completed before root stopped the final weekly recomposition, both repeated bylines again recorded zero cache hits, and live services remained unchanged. Preserve Runs 61 through 63 as stopped or completed immutable evidence. Copy defects remain later deadline-correction work rather than a reason to reopen accepted issue artifacts. - implementation_checkpoint: Exact pushed v13 `a52d57b` keeps one stable reporter action schema, places a common stable house charter before recurring identity and dynamic assignment, and projects deterministic `allowed_datelines` from cited records while retaining copy-desk factual authority and deterministic cited-place admission; 480 library tests and the all-target check pass. Run 63 falsifies reporter-cache pursuit as a useful acceptance target for sporadic independent filings. The proposed v14 cache-floor prompt growth was removed. Append-only agent contexts remain the natural cache beneficiary; accepted Run 60 remains immutable. + implementation_checkpoint: Exact pushed v13 `a52d57b` keeps one stable reporter action schema, places a common stable house charter before recurring identity and dynamic assignment, and projects deterministic `allowed_datelines` from cited records while retaining copy-desk factual authority and deterministic cited-place admission; 480 library tests and the all-target check pass. Run 63 falsifies reporter-cache pursuit as a useful acceptance target for sporadic independent filings. The proposed v14 cache-floor prompt growth was removed. Append-only agent contexts remain the natural cache beneficiary; accepted Run 60 remains immutable. The current strategic cut extracts one shared `validate_strategic_individuation_proposals` owner used by resolution validation and by the scheduler before it returns an optional proposal. An invalid optional identity now emits a semantic-invalid receipt and produces no individuation instead of aborting the recoverable wave; focused valid and invalid regressions pass. Broader verification, commit, push, and provider resume remain pending. presentation_checkpoint: The historical press-layout and seed-ownership split are complete. `notes/historical-newspaper-layout-grammar.md` remains the durable grammar. Manifest v2 at `output/tex/canopy-ledger-run-61-style-1847-flow-723.manifest.toml` independently binds `style_seed=1847`, `flow_seed=723`, frozen source SHA-256 `8f195140f52a40ba705d6a66987146a1500ae89668e5d82b01d1792eb3afb882`, and the final artifact `output/pdf/canopy-ledger-run-61-style-1847-flow-723.pdf`, SHA-256 `a457f4b1702a7a52480e42fce0039f0dbe677ff32958e98f1c412b346237190c`. An identical seed pair produces byte-identical TeX and PDF; changing only flow preserves the exact style section while materially changing a plausible layout, and changing only style preserves flow topology. Cut-first and cut-midstory templates pass visual inspection; the failed split-card template is deleted. Frozen copy/assets, actual PDF `/BM /Multiply`, and economic-layer advertising authority remain invariant. - next_action: Continue the fresh immutable full-world provider run from its exact checkpoint boundary through `ghostlight-strategic-smoke`. Bind the exact bounded `GHOSTLIGHT_WORLD_DESCRIPTION`, ordered `GHOSTLIGHT_WORLD_REGION_REQUESTS`, world identity and external boundary observer, pressure, public channel, elaboration-pass count, cell budget, wave count, provider/source revision, result root, and acceptance checks. Require every requested realm to have an immutable region-expansion checkpoint and canonical jurisdiction before locality elaboration and strategic waves; do not replay completed regions. The multiresolution cover may use up to the 240-cell ceiling and the driver may advance up to 32 waves with per-wave plus final newspapers. Runs 64 and 65 remain stopped evidence, not acceptance. Require the Delvehold-owned adapter before any live Greathold player, civic, dungeon, contract, or quantitative-economy state or effects participate. Preserve the completed two-seed press checkpoint and economic-layer advertising boundary. + next_action: Finish bounded verification and review of the shared strategic-individuation validator cut, commit and push its exact source, then resume the same immutable Run 67 root at world revision 15 through `ghostlight-strategic-smoke`. Do not replay its five committed regions or five elaborations. Prove that an over-authority optional identity is recorded as semantic-invalid with no individuation while the otherwise recoverable wave proceeds, then continue the configured waves and newspapers under the frozen Run 67 bindings. Preserve Runs 64 through 67 as evidence. Require the Delvehold-owned adapter before any live Greathold player, civic, dungeon, contract, or quantitative-economy state or effects participate. Preserve the completed two-seed press checkpoint and economic-layer advertising boundary. 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.