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:
parent
9747957649
commit
9308c7d043
2 changed files with 118 additions and 13 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue