Align internal gestalt coordination authority

This commit is contained in:
Meta Vens 2026-08-27 04:41:47 +02:00
commit b98ca9abc3
4 changed files with 136 additions and 3 deletions

View file

@ -548,6 +548,10 @@ impl StrategicActivityKind {
Self::Prepare | Self::Investigate | Self::Obstruct | Self::Communicate
)
}
pub fn requires_explicit_target_for_gestalt(&self) -> bool {
!self.allows_targetless_local_attempt() && !matches!(self, Self::Coordinate)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]

View file

@ -2872,7 +2872,7 @@ fn apply_strategic_tick_plan(
crate::resolution::strategic_activity_targets(campaign, &action.gestalt_id);
let unique_targets = action.target_subject_ids.iter().collect::<BTreeSet<_>>();
let unique_locations = action.location_ids.iter().collect::<BTreeSet<_>>();
let needs_target = !action.activity.allows_targetless_local_attempt();
let needs_target = action.activity.requires_explicit_target_for_gestalt();
if action.target_subject_ids.len() > 4
|| unique_targets.len() != action.target_subject_ids.len()
|| action
@ -3473,6 +3473,9 @@ fn strategic_activity_summary(
(StrategicActivityKind::Coordinate, false) => {
format!("{source_name} attempts to coordinate with {targets}.")
}
(StrategicActivityKind::Coordinate, true) => {
format!("{source_name} attempts internal coordination.")
}
(StrategicActivityKind::Investigate, true) => {
format!("{source_name} begins a local investigation.")
}
@ -5749,6 +5752,72 @@ pub(crate) mod tests {
);
}
#[test]
fn cohesive_gestalt_internal_coordination_reaches_canonical_and_legacy_commit_paths() {
let action = CellActionProposal {
subject_id: "refugees-east".into(),
intent: "coordinate the households' own water watch".into(),
intended_effect: "coordinate internal water-watch shifts without naming an external party"
.into(),
priority: 80,
state_references: vec![],
public_channels: vec![],
effects: vec![StrategicCellEffect::GestaltActivity {
gestalt_id: "refugees-east".into(),
activity: StrategicActivityKind::Coordinate,
target_subject_ids: vec![],
location_ids: vec!["camp".into()],
}],
};
let action_digest = crate::resolution::cell_action_digest(&action).unwrap();
let mut canonical = hierarchical_refugee_campaign();
let canonical_events = apply_strategic_tick_plan(
&mut canonical,
StrategicTickPlan {
selected_actions: vec![action],
activity_outcomes: vec![StrategicActivityOutcome {
schema: "ghostlight.strategic_activity_outcome.v1".into(),
action_digest,
source_subject_id: "refugees-east".into(),
band: StrategicOutcomeBand::Mixed,
summary: "The households arrange the watch; its efficacy remains unsettled."
.into(),
supporting_state_references: vec![],
effect: StrategicOutcomeEffect::NoMaterialChange {
reason: "No external response or durable result is established.".into(),
},
}],
..Default::default()
},
)
.unwrap();
assert_eq!(canonical_events.len(), 1);
assert_eq!(canonical_events[0].kind, "gestalt_activity");
assert!(canonical_events[0].summary.contains("internal water-watch shifts"));
let mut legacy = hierarchical_refugee_campaign();
let legacy_events = apply_strategic_tick_plan(
&mut legacy,
resolve_test_activities(StrategicTickPlan {
gestalt_activities: vec![StrategicGestaltActivity {
action_digest: test_action_digest("internal water-watch coordination"),
gestalt_id: "refugees-east".into(),
activity: StrategicActivityKind::Coordinate,
target_subject_ids: vec![],
location_ids: vec!["camp".into()],
public_channels: vec![],
}],
..Default::default()
}),
)
.unwrap();
assert_eq!(legacy_events.len(), 1);
assert_eq!(
legacy_events[0].summary,
"Eastern transit refugees attempts internal coordination."
);
}
#[test]
fn canonical_action_commits_distinct_scopes_of_one_activity_kind() {
let mut value = hierarchical_refugee_campaign();

View file

@ -2433,7 +2433,7 @@ fn validate_constituent_effect(
} => {
let unique_targets = target_subject_ids.iter().collect::<BTreeSet<_>>();
let unique_locations = location_ids.iter().collect::<BTreeSet<_>>();
let needs_target = !activity.allows_targetless_local_attempt();
let needs_target = activity.requires_explicit_target_for_gestalt();
if needs_target && target_subject_ids.is_empty() {
return Err(anyhow!(
"gestalt {} activity {:?} requires one or more exact target IDs; no anonymous or unsupplied target can be encoded. Remove the action unless the Persona explicitly attempted one of {:?}",
@ -4430,6 +4430,14 @@ mod tests {
};
validate_constituent_effect(subject, &valid, None).unwrap();
let internal_coordination = StrategicCellEffect::GestaltActivity {
gestalt_id: "refugees".into(),
activity: crate::domain::StrategicActivityKind::Coordinate,
target_subject_ids: vec![],
location_ids: vec!["forum".into()],
};
validate_constituent_effect(subject, &internal_coordination, None).unwrap();
let invented_target = StrategicCellEffect::GestaltActivity {
gestalt_id: "refugees".into(),
activity: crate::domain::StrategicActivityKind::Coordinate,
@ -4561,6 +4569,19 @@ mod tests {
.unwrap();
validate_cell_appraisal(&slice, &appraisal).unwrap();
let mut targetless_coordination = appraisal.clone();
let StrategicCellEffect::MemberActivity {
activity,
target_subject_ids,
..
} = &mut targetless_coordination.actions[0].effects[0]
else {
unreachable!()
};
*activity = crate::domain::StrategicActivityKind::Coordinate;
target_subject_ids.clear();
assert!(validate_cell_appraisal(&slice, &targetless_coordination).is_err());
let mut stolen = appraisal;
let StrategicCellEffect::MemberActivity { member_id, .. } =
&mut stolen.actions[0].effects[0]

View file

@ -2339,7 +2339,7 @@ fn validate_cell_proposal(
let allowed_targets = strategic_activity_targets(campaign, gestalt_id);
let unique_targets = target_subject_ids.iter().collect::<BTreeSet<_>>();
let unique_locations = location_ids.iter().collect::<BTreeSet<_>>();
let needs_target = !activity.allows_targetless_local_attempt();
let needs_target = activity.requires_explicit_target_for_gestalt();
if gestalt_id != &proposal.subject_id
|| !campaign.gestalts.contains_key(gestalt_id)
|| target_subject_ids.len() > 4
@ -4467,6 +4467,21 @@ pub(crate) mod tests {
target_subject_ids.clear();
assert!(select_resolution_wave(&value, &make_wave(local_member_communication)).is_ok());
let mut targetless_member_coordination = member_activity.clone();
let StrategicCellEffect::MemberActivity {
activity,
target_subject_ids,
..
} = &mut targetless_member_coordination.effects[0]
else {
unreachable!()
};
*activity = StrategicActivityKind::Coordinate;
target_subject_ids.clear();
assert!(
select_resolution_wave(&value, &make_wave(targetless_member_coordination)).is_err()
);
let same_member_wave = ResolutionWaveCommit {
schema: "ghostlight.resolution_wave_commit.v1".into(),
world_revision: value.revision,
@ -4527,6 +4542,30 @@ pub(crate) mod tests {
assert_eq!(activity_plan.gestalt_activities.len(), 1);
assert!(activity_plan.gestalt_actions.is_empty());
let internal_coordination = CellActionProposal {
subject_id: "refugees".into(),
intent: "coordinate the refugee households' own water watch".into(),
intended_effect: "attempt internal coordination without inventing an external target"
.into(),
priority: 80,
state_references: vec!["subject:refugees".into()],
public_channels: vec![],
effects: vec![StrategicCellEffect::GestaltActivity {
gestalt_id: "refugees".into(),
activity: StrategicActivityKind::Coordinate,
target_subject_ids: vec![],
location_ids: vec!["center".into()],
}],
};
let internal_plan =
select_resolution_wave(&value, &make_wave(internal_coordination))
.unwrap()
.plan;
assert_eq!(internal_plan.gestalt_activities.len(), 1);
assert!(internal_plan.gestalt_activities[0]
.target_subject_ids
.is_empty());
let lower_priority_pressure = CellActionProposal {
subject_id: "refugees".into(),
intent: "register the unresolved exclusion".into(),