diff --git a/crates/ghostlight-dungeon/src/persona.rs b/crates/ghostlight-dungeon/src/persona.rs index f08e21d..ed886c0 100644 --- a/crates/ghostlight-dungeon/src/persona.rs +++ b/crates/ghostlight-dungeon/src/persona.rs @@ -67,6 +67,7 @@ pub struct CellConstituentSlice { pub reachable_destination_ids: BTreeSet, pub activity_target_ids: BTreeSet, pub goals: Vec, + pub current_posture: Option, pub pressures: Vec, } @@ -342,6 +343,7 @@ fn cell_projector_context(slice: &PermittedCellSlice) -> serde_json::Value { "reachable_destination_ids": subject.reachable_destination_ids, "activity_target_ids": subject.activity_target_ids, "goals": subject.goals, + "current_posture": subject.current_posture, "pressures": subject.pressures, })).collect::>(), "member_exceptions": slice.member_exceptions.iter().map(|member| serde_json::json!({ @@ -387,6 +389,7 @@ fn cell_interpreter_context(slice: &PermittedCellSlice) -> serde_json::Value { "permitted_state_references": subject.permitted_state_references, "reachable_destination_ids": subject.reachable_destination_ids, "activity_target_ids": subject.activity_target_ids, + "current_posture": subject.current_posture, "current_pressures": subject.pressures, })).collect::>(), "member_permissions": slice.member_exceptions.iter().map(|member| serde_json::json!({ @@ -880,7 +883,12 @@ fn validate_constituent_effect( subject.location_ids )); } - let current = subject.pressures.first().map(String::as_str).unwrap_or(""); + let current = subject.current_posture.as_deref().ok_or_else(|| { + anyhow!( + "institution {} is missing its exact current posture", + subject.subject_id + ) + })?; if !crate::resolution::substantive_text_change(current, posture) { return Err(anyhow!( "institution {} proposed posture {:?}, but its exact current posture is {:?}; emit a specific different commitment or choose inaction", @@ -1184,6 +1192,7 @@ mod tests { reachable_destination_ids: BTreeSet::new(), activity_target_ids: BTreeSet::new(), goals: vec!["publish a position".into()], + current_posture: Some("weighing whether to publish a position".into()), pressures: vec!["the vote is near".into()], }], member_exceptions: vec![], @@ -1520,13 +1529,26 @@ mod tests { &slice.constituents[0], &StrategicCellEffect::Institution { institution_id: "faction-06".into(), - posture: "the vote is near".into(), + posture: "weighing whether to publish a position".into(), location_ids: vec!["forum".into()], }, ) .unwrap_err(); assert!(error.to_string().contains("exact current posture")); - assert!(error.to_string().contains("the vote is near")); + assert!( + error + .to_string() + .contains("weighing whether to publish a position") + ); + let projector_context = cell_projector_context(&slice); + assert_eq!( + projector_context["constituents"][0]["current_posture"], + "weighing whether to publish a position" + ); + assert_eq!( + projector_context["constituents"][0]["pressures"][0], + "the vote is near" + ); } #[tokio::test] diff --git a/crates/ghostlight-dungeon/src/scheduler.rs b/crates/ghostlight-dungeon/src/scheduler.rs index 55d6a70..3942f75 100644 --- a/crates/ghostlight-dungeon/src/scheduler.rs +++ b/crates/ghostlight-dungeon/src/scheduler.rs @@ -690,6 +690,7 @@ fn constituent_slice(campaign: &Campaign, id: &str) -> Result Result { let gestalt = campaign.gestalts.get(id).context("cell gestalt vanished")?; @@ -1040,6 +1041,18 @@ mod tests { ); } + #[test] + fn institution_slice_does_not_disguise_committed_posture_as_pressure() { + let campaign = crate::resolution::tests::campaign(1, 1); + let institution = &campaign.institutions["faction-0000"]; + let slice = constituent_slice(&campaign, "faction-0000").unwrap(); + assert_eq!( + slice.current_posture.as_deref(), + Some(institution.posture.as_str()) + ); + assert!(slice.pressures.is_empty()); + } + #[test] fn arena_event_projection_preserves_exact_viewers_including_named_members() { use crate::domain::*; diff --git a/docs/architecture/ghostlight-multiresolution-agency.md b/docs/architecture/ghostlight-multiresolution-agency.md index 3b249e5..dc8c2e0 100644 --- a/docs/architecture/ghostlight-multiresolution-agency.md +++ b/docs/architecture/ghostlight-multiresolution-agency.md @@ -284,6 +284,12 @@ knowledge is never treated as an information channel; while dematerialized, the member can publish only through channels owned by their current leaf gestalt's agency profile. +Institution rows distinguish `current_posture` from unresolved `pressures`. +The committed posture is projected as already true and is the exact baseline +against which a new institutional commitment is validated. It is never hidden +in a generic pressure list, where a Projector could mistake a completed choice +for a dilemma still awaiting action. + A committed strategic effect must produce an exact canonical consequence. Institutions cannot re-adopt their current posture. Population pressure effects carry an explicit lifecycle: `pressure_resolutions` must name exact current diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 72dbce6..b8288de 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -107,6 +107,14 @@ internal perspective for actual cell constituents/member exceptions, Personas cannot conjure absent entities into contact, and empty target errors explicitly tell the correction stage that anonymous targets cannot be encoded. +The following run migrated Mira and completed two sustained waves before a +third safe abort. An institution's committed posture had been stored in the +generic cell `pressures` list; its Projector treated that already-made decision +as a live dilemma and the Persona repeated it. `CellConstituentSlice` now has an +explicit `current_posture`; institution pressure is no longer smuggled through +`pressures[0]`. Projector, Interpreter, and local validation share the same +unambiguous baseline. + The multiresolution body is deployed on Starfire. Typed health matched the immutable release, DeepSeek was ready, and the disposable live strategic-cell smoke committed three offscreen events plus three accessible news items without diff --git a/notes/ghostlight-current-system-map.md b/notes/ghostlight-current-system-map.md index b1080d2..165ccea 100644 --- a/notes/ghostlight-current-system-map.md +++ b/notes/ghostlight-current-system-map.md @@ -79,6 +79,11 @@ perspective. Event mentions do not transfer a person into another cell, and an unnamed or unsupplied entity cannot become an activity target merely because a Persona found it plausible. +Institution slices carry committed `current_posture` separately from unresolved +pressure. Projectors present the posture as existing state; Interpreters and +validators compare any new commitment directly against it. The old overloaded +`pressures[0]` convention is not an authority path. + Gestalt background choices use two distinct typed paths. Pressure transitions change exact unresolved markers. `gestalt_activity` records an attributed preparation, coordination, investigation, recruitment, obstruction, trade, or diff --git a/state/map.yaml b/state/map.yaml index 7621e14..4c0f862 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -265,5 +265,5 @@ data_flow: current_status: summary: GhostlightDungeon production remains on the verified Starfire release. The acceptance candidate is pressuring multiresolution agency: exact Gestalt activity attempts, typed Gestalt event participation, nested lineage identity preservation, location-bounded presence projection, and automatic refugee callback rematerialization. Production state is untouched while the strict live-fire harness and receipts falsify the candidate. - next_action: Run the strict multiresolution refugee-return live fire after the shared-location, spatial-partition, and perspective-ownership membrane repairs. Require meaningful whole-setting background activity, exact rival-arena attribution, migration across unrelated nested lineage trees, and automatic presence-planner rematerialization of the same durable member. Profile stage receipts and repair only the owning context or typed effect seam. + next_action: Run the strict multiresolution refugee-return live fire after the shared-location, perspective-ownership, and explicit institution-posture repairs. Require meaningful whole-setting background activity, exact rival-arena attribution, migration across unrelated nested lineage trees, and automatic presence-planner rematerialization of the same durable member. Profile stage receipts and repair only the owning context or typed effect seam. gestalt_population: Gestalt Personas own shared population baselines; persistent member deltas own named individuality; WorldKernel materialization and approval-gated fission preserve lineage and identity. Presence projection is limited to nearby active leaves and exact-location dormant members. Simulation cells are derived covers only.