verse run: pin the membership guard and the unattributed note by size, position and cell; delete the fold's tool-name cap

membership-guard-pins-degenerate: a three-member cell at positions 0, 1 and 2 with a foreign extra, a duplicate, a missing last and middle member and a short list; the unattributed note recorded at cells 0, 1 and 4.
tool-name-bound-twice: UNATTRIBUTED_TOOL_CAP, its take and its direct-fold test are deleted; the provider contract owns the 64-byte bound.

Not built or run locally; verified on Yggdrasil in the next report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-11 19:08:17 +02:00
commit 9308c7d043
2 changed files with 118 additions and 13 deletions

View file

@ -2924,9 +2924,6 @@ pub enum ConstituentRefusalReason {
CallUnattributed,
}
/// The longest tool name a run keeps of a call that named no handle.
pub const UNATTRIBUTED_TOOL_CAP: usize = 64;
/// One coarse cell's outcome: one inference, N ordinary one-opportunity
/// submissions, and whatever the decode could not attribute.
#[derive(Debug)]
@ -2936,8 +2933,8 @@ pub struct GroupedRun {
pub submissions: Vec<ConstituentSubmission>,
/// Decode problems the grouped decode could not attribute to a constituent.
pub needs: Vec<ControllerNeed>,
/// The tool names of the calls that named no handle, each capped at
/// [`UNATTRIBUTED_TOOL_CAP`] characters; never their arguments.
/// The tool names of the calls that named no handle, already valid under
/// the provider contract; never their arguments.
pub unattributed: Vec<String>,
/// Constituents of this cell that did not commit, one entry each, with the
/// reason. A constituent is in `submissions` or here, never lost.
@ -6005,8 +6002,7 @@ pub(super) fn grouped_tool_result(
// A model that writes `c99__carry` has not proposed anything. It has
// produced a gap.
None => {
fold.unattributed
.push(name.chars().take(UNATTRIBUTED_TOOL_CAP).collect());
fold.unattributed.push(name.to_string());
fold.needs.push(tool_decode_need(
name,
arguments,
@ -15269,14 +15265,13 @@ mod tests {
}
}
/// The name a run keeps of a call that named no handle is bounded; the
/// call's arguments are not kept at all.
/// The call's arguments are not kept at all: only the name is.
#[test]
fn the_name_kept_of_a_call_naming_no_handle_is_capped() {
fn the_name_of_a_call_naming_no_handle_is_kept_and_its_arguments_are_not() {
let mut fold = GroupedFold::new();
let long = "x".repeat(UNATTRIBUTED_TOOL_CAP + 36);
grouped_tool_result(&[], &mut fold, &long, r#"{"text":"SENTINEL-ARGUMENT"}"#).unwrap();
assert_eq!(fold.unattributed, vec!["x".repeat(UNATTRIBUTED_TOOL_CAP)]);
grouped_tool_result(&[], &mut fold, "c9__speak", r#"{"text":"SENTINEL-ARGUMENT"}"#)
.unwrap();
assert_eq!(fold.unattributed, vec!["c9__speak".to_string()]);
}
/// Verification 20b. No spelling of a handle outside the cell decodes, and

View file

@ -2595,6 +2595,116 @@ async fn a_grouped_cell_whose_lists_do_not_match_its_members_ends_the_run() {
assert!(committed.is_empty() && refusals.is_empty());
}
/// A cell of three members at a later position, so that the guard is held to
/// its length, to every member, and to the cell it is asked about rather than
/// to the two-member first cell the fixture covers.
async fn three_member_group(verse: &Harness) -> Cell {
let cover = cover_of(verse, 1).await;
let Cell::Group { id, tick, members } = &cover.cells[0] else {
panic!("a group")
};
let mut members = members.clone();
let mut third = members[0].clone();
third.subject = SubjectId::issue();
members.push(third);
Cell::Group {
id: *id,
tick: *tick,
members,
}
}
#[tokio::test]
async fn a_finished_cell_of_three_must_list_each_member_exactly_once_wherever_it_sits() {
let (_dir, verse) = Harness::fresh().await;
let group = three_member_group(&verse).await;
let Cell::Group { id, members, .. } = &group else {
panic!("a group")
};
let left = ConstituentRefusalReason::LeftBeforeSelection;
let run = |listed: Vec<SubjectId>| {
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: listed
.into_iter()
.map(|subject| crate::controllers::ConstituentRefusal { subject, reason: left })
.collect(),
pending: None,
}))
};
let [a, b, c] = [members[0].subject, members[1].subject, members[2].subject];
let broken = Some(failed("cell_membership"));
for position in [0, 1, 2] {
let (status, _, _) = fold(run(vec![a, b, c]), &group, position);
assert_eq!(status, None, "every member listed once, at cell {position}");
// Every member listed, and one subject the cell does not hold.
let (status, _, _) = fold(run(vec![a, b, c, SubjectId::issue()]), &group, position);
assert_eq!(status, broken, "a foreign extra at cell {position}");
// Every member listed, and one of them twice.
let (status, _, _) = fold(run(vec![a, b, c, b]), &group, position);
assert_eq!(status, broken, "a duplicate at cell {position}");
// The right number of entries, and the last member missing: only the
// member at the end of the cell can be the one that is not there.
let (status, _, _) = fold(run(vec![a, b, SubjectId::issue()]), &group, position);
assert_eq!(status, broken, "a missing last member at cell {position}");
let (status, _, _) = fold(run(vec![a, c, a]), &group, position);
assert_eq!(status, broken, "a missing middle member at cell {position}");
let (status, _, _) = fold(run(vec![a, b]), &group, position);
assert_eq!(status, broken, "a short list at cell {position}");
}
}
#[tokio::test]
async fn a_call_naming_no_handle_is_recorded_at_the_cell_it_was_made_in() {
let (_dir, verse) = Harness::fresh().await;
let group = three_member_group(&verse).await;
let Cell::Group { id, members, .. } = &group else {
panic!("a group")
};
for position in [0, 1, 4] {
let run = CellRun::Grouped(crate::controllers::GroupedRun {
cell: *id,
resolution: crate::cover::Resolution::Coarse {
constituents: members.len(),
},
submissions: Vec::new(),
needs: Vec::new(),
unattributed: vec!["c9__speak".to_string()],
refused: members
.iter()
.map(|member| crate::controllers::ConstituentRefusal {
subject: member.subject,
reason: ConstituentRefusalReason::CallUnattributed,
})
.collect(),
pending: None,
});
let mut unattributed = Vec::new();
let status = absorb(
Ok(run),
&group,
position,
&mut Vec::new(),
&mut Vec::new(),
&mut unattributed,
);
assert_eq!(status, None);
assert_eq!(
unattributed,
vec![UnattributedCall {
cell: position,
tool: "c9__speak".into()
}]
);
}
}
#[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;