Verse run tests: a group at a later cell, the resumed row's disagreement, the invariant break, the unattributed note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-11 17:43:02 +02:00
commit b3699908f7

View file

@ -713,7 +713,7 @@ async fn a_cell_the_world_refuses_is_a_refusal_and_the_tick_closes() {
#[test]
fn the_operator_reads_refusals_and_how_a_run_ended() {
let [one, two, three, four] = [(); 4].map(|()| crate::SubjectId::issue());
let [one, two, three, four, five, six] = [(); 6].map(|()| crate::SubjectId::issue());
let refusal = |cell, subject, reason| Refusal {
cell,
subject,
@ -730,7 +730,13 @@ fn the_operator_reads_refusals_and_how_a_run_ended() {
refusal(2, two, RefusalReason::OpportunityGone),
refusal(0, three, RefusalReason::Interrupted),
refusal(5, four, RefusalReason::ScopeMoved),
refusal(3, five, RefusalReason::RowDisagreesWithCell),
refusal(3, six, RefusalReason::CallUnattributed),
],
unattributed: vec![UnattributedCall {
cell: 3,
tool: "c9__speak".into(),
}],
tokens: 7,
},
acts: Vec::new(),
@ -739,6 +745,8 @@ fn the_operator_reads_refusals_and_how_a_run_ended() {
(two, "Brann".to_owned()),
(three, "Cato".to_owned()),
(four, "Dura".to_owned()),
(five, "Eve".to_owned()),
(six, "Fay".to_owned()),
]),
spent: 40,
remaining: 60,
@ -746,11 +754,14 @@ fn the_operator_reads_refusals_and_how_a_run_ended() {
assert_eq!(
report.to_string(),
concat!(
"tick 4 open: 3 cells, 0 acts, 4 refused\n",
"tick 4 open: 3 cells, 0 acts, 6 refused\n",
" refused: cell 1 Ada world_rejected\n",
" refused: cell 2 Brann opportunity_gone\n",
" refused: cell 0 Cato interrupted\n",
" refused: cell 5 Dura scope_moved\n",
" refused: cell 3 Eve row_disagrees_with_cell\n",
" refused: cell 3 Fay call_unattributed\n",
" note: cell 3 call names no handle: tool \"c9__speak\"\n",
" tokens: 7 this tick, 40 spent, 60 remaining\n",
)
);
@ -2188,24 +2199,45 @@ async fn a_tick_refuses_the_frozen_members_that_hold_no_opportunity_by_cell_and_
#[tokio::test]
async fn a_frozen_member_the_resumed_row_lacks_is_refused_not_lost() {
// The member the row lacks is the cell's second member, then its first.
for lacking_first in [false, true] {
a_row_lacking_a_frozen_member(lacking_first).await;
// The member the row lacks is the cell's second member, then its first,
// with the group first in the tick and then behind a cell that is gone.
for behind_a_gone_cell in [false, true] {
for lacking_first in [false, true] {
a_row_lacking_a_frozen_member(lacking_first, behind_a_gone_cell).await;
}
}
}
async fn a_row_lacking_a_frozen_member(lacking_first: bool) {
async fn a_row_lacking_a_frozen_member(lacking_first: bool, behind_a_gone_cell: bool) {
let (_dir, verse) = Harness::fresh().await;
let (group, real) = the_group(&verse).await;
let (a, b) = (real[0], real[1]);
let single = match &cover_of(&verse, 2).await.cells[1] {
Cell::Singleton { id, .. } => *id,
Cell::Group { .. } => panic!("the second cell of two is a singleton"),
};
let gone = SubjectId::issue();
let leading = |cells: Vec<FrozenCell>| -> Vec<FrozenCell> {
if behind_a_gone_cell {
let mut all = vec![FrozenCell {
id: single,
group: false,
members: vec![gone],
}];
all.extend(cells);
all
} else {
cells
}
};
// The first attempt froze [a, ghost]: the row it persists holds only a.
open_tick_with(
&verse,
vec![FrozenCell {
leading(vec![FrozenCell {
id: group,
group: true,
members: vec![a, SubjectId::issue()],
}],
}]),
)
.await;
let first = verse
@ -2217,12 +2249,13 @@ async fn a_row_lacking_a_frozen_member(lacking_first: bool) {
// On resume the frozen cell names a and b; the row still holds only a.
let world = verse.mailbox.snapshot().await.unwrap().world_id;
let mut tick = verse.runs.tick(world, 0).unwrap().unwrap();
tick.cells = vec![FrozenCell {
tick.cells = leading(vec![FrozenCell {
id: group,
group: true,
members: if lacking_first { vec![b, a] } else { vec![a, b] },
}];
}]);
verse.runs.put_tick(&tick).unwrap();
let cell = u32::from(behind_a_gone_cell);
let second = verse
.run_under(ScriptedPort::new(grouped_reply), u64::MAX, one_cell(1), |_| {
ControlFlow::Continue(())
@ -2231,14 +2264,20 @@ async fn a_row_lacking_a_frozen_member(lacking_first: bool) {
let checkpoint = &second.record.checkpoints[0];
assert!(checkpoint.closed);
assert_eq!(checkpoint.committed, vec![command_for(world, group, a)]);
assert_eq!(
checkpoint.refusals,
vec![Refusal {
let mut expected = Vec::new();
if behind_a_gone_cell {
expected.push(Refusal {
cell: 0,
subject: b,
reason: RefusalReason::ScopeMoved
}]
);
subject: gone,
reason: RefusalReason::OpportunityGone,
});
}
expected.push(Refusal {
cell,
subject: b,
reason: RefusalReason::RowDisagreesWithCell,
});
assert_eq!(checkpoint.refusals, expected);
}
#[tokio::test]
@ -2292,11 +2331,10 @@ fn fold(
let status = absorb(
result,
cell,
&FrozenCell::of(cell),
&[],
position,
&mut committed,
&mut refusals,
&mut Vec::new(),
);
(status, committed, refusals)
}
@ -2315,6 +2353,7 @@ fn grouped_refusing(
},
submissions: Vec::new(),
needs: Vec::new(),
unattributed: Vec::new(),
refused: members
.iter()
.zip(reasons)
@ -2446,3 +2485,161 @@ async fn a_grouped_member_whose_proposal_fails_to_decode_is_refused_not_declined
}
}
}
#[tokio::test]
async fn a_grouped_cell_behind_another_is_accounted_at_its_own_position() {
let (_dir, verse) = Harness::fresh().await;
let (group, real) = the_group(&verse).await;
let single = match &cover_of(&verse, 2).await.cells[1] {
Cell::Singleton { id, .. } => *id,
Cell::Group { .. } => panic!("the second cell of two is a singleton"),
};
let gone = SubjectId::issue();
open_tick_with(
&verse,
vec![
FrozenCell {
id: single,
group: false,
members: vec![gone],
},
FrozenCell {
id: group,
group: true,
members: real.clone(),
},
],
)
.await;
let outcome = verse
.run_under(ScriptedPort::new(grouped_reply), u64::MAX, one_cell(1), |_| {
ControlFlow::Continue(())
})
.await;
let [checkpoint] = outcome.record.checkpoints.as_slice() else {
panic!("one tick, one checkpoint")
};
assert!(checkpoint.closed);
let world = verse.mailbox.snapshot().await.unwrap().world_id;
let [first, second] = checkpoint.refusals.as_slice() else {
panic!("the gone cell and the refused member: {:?}", checkpoint.refusals)
};
assert_eq!(
*first,
Refusal {
cell: 0,
subject: gone,
reason: RefusalReason::OpportunityGone
}
);
assert_eq!(second.cell, 1);
assert_eq!(second.reason, RefusalReason::ScopeMoved);
let acted: Vec<SubjectId> = real.iter().copied().filter(|s| *s != second.subject).collect();
assert_eq!(acted.len(), 1);
assert_eq!(checkpoint.committed, vec![command_for(world, group, acted[0])]);
}
#[tokio::test]
async fn a_grouped_cell_whose_lists_do_not_match_its_members_ends_the_run() {
let (_dir, verse) = Harness::fresh().await;
let cover = cover_of(&verse, 1).await;
let group = &cover.cells[0];
let Cell::Group { id, members, .. } = group else {
panic!("a group")
};
let run = |refused: Vec<crate::controllers::ConstituentRefusal>, pending| {
Ok(CellRun::Grouped(crate::controllers::GroupedRun {
cell: *id,
resolution: crate::cover::Resolution::Coarse {
constituents: members.len(),
},
submissions: Vec::new(),
needs: Vec::new(),
unattributed: Vec::new(),
refused,
pending,
}))
};
let refusal = |subject, reason| crate::controllers::ConstituentRefusal { subject, reason };
let left = ConstituentRefusalReason::LeftBeforeSelection;
let broken = Some(failed("cell_membership"));
// A finished cell that lists one of its two members.
let (status, _, _) = fold(run(vec![refusal(members[0].subject, left)], None), group, 0);
assert_eq!(status, broken);
// One that lists a subject the cell does not hold, and misses one it does.
let (status, _, _) = fold(
run(
vec![refusal(members[0].subject, left), refusal(SubjectId::issue(), left)],
None,
),
group,
0,
);
assert_eq!(status, broken);
// One that lists a member twice.
let (status, _, _) = fold(
run(
vec![refusal(members[0].subject, left), refusal(members[0].subject, left)],
None,
),
group,
0,
);
assert_eq!(status, broken);
// A cell that stopped pending has not listed its unreached members, and
// that is not a broken invariant: the tick stays open and they resume.
let reason = crate::controllers::ControllerPendingReason::WorldUnavailable;
let (status, committed, refusals) = fold(run(Vec::new(), Some(reason)), group, 0);
assert_eq!(status, Some(failed(pending_code(reason))));
assert!(committed.is_empty() && refusals.is_empty());
}
#[tokio::test]
async fn a_call_naming_no_handle_is_printed_and_its_quiet_member_is_refused_not_declined() {
let (_dir, verse) = Harness::fresh().await;
let port = ScriptedPort::new(|request| {
if request.contains("unattributed tool recorded as a need") {
return Vec::new();
}
let mut events = grouped_reply(request);
events.truncate(1);
events.push(InferenceEvent::ToolCall {
call_id: "stray".into(),
name: "c9__speak".into(),
arguments: json!({ "text": "SENTINEL-ARGUMENT" }).to_string(),
});
events
});
let mut reports = Vec::new();
let outcome = verse
.run_under(port, u64::MAX, one_cell(1), |report| {
reports.push(report.clone());
ControlFlow::Continue(())
})
.await;
let [checkpoint] = outcome.record.checkpoints.as_slice() else {
panic!("one tick, one checkpoint")
};
assert!(checkpoint.closed);
assert_eq!(checkpoint.committed.len(), 1);
let [refusal] = checkpoint.refusals.as_slice() else {
panic!("the quiet member is the one refusal")
};
assert_eq!(refusal.reason, RefusalReason::CallUnattributed);
assert_eq!(
checkpoint.unattributed,
vec![UnattributedCall {
cell: 0,
tool: "c9__speak".into()
}]
);
let printed = reports[0].to_string();
assert!(printed.contains("call_unattributed"), "{printed}");
assert!(
printed.contains("note: cell 0 call names no handle: tool \"c9__speak\""),
"{printed}"
);
assert!(!printed.contains("SENTINEL-ARGUMENT"));
assert_every_member_accounted_for(&verse, &outcome).await;
}