Align internal gestalt coordination authority
This commit is contained in:
parent
dc5787e8fd
commit
b98ca9abc3
4 changed files with 136 additions and 3 deletions
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue