P5:把委派栅栏 detail 的「拼串→再解析」锁成等价关系
M1C-1 把 userRevisionPending 同时加进了 StaticDelegateCompletionBarrier::detail()
的输出和 project_gates 的解析门,两边靠一个字段名字符串隔空对齐,中间没有共享 schema。
当时生产侧只有 delegation.rs 一条 detail().contains("userRevisionPending=1"),解析侧
一条测试都没有,两者之间也没有任何东西相连:字段名一改,生产侧那条照过,而三个门静默
返回 false,父 run 就会越过用户修订边界收束。
已核实这条路径是「单一生产者 → 四个解析点」:static_delegate_completion_blocker_at
原样使用 detail: Some(barrier.detail()),main_loop 的四处解析都以
tool == "runtime.delegate_receipts" 为前提。
新增用例锁的是等价关系而不是拼写:对七个计数的全部 128 种 0/1 组合,断言三个门的判定
与 barrier 自己的语义谓词逐一相等;另加多位数计数与「键之间互不为前缀」两条护栏——后者
是 strip_prefix 读对值的隐含前提,等价关系测试抓不到这层前提何时被打破。
鉴别力已用变异验证:把解析器的 userRevisionPending 改名为 userRevisionRequested,
或往 has_waiting() 里加一个解析器不认的计数,两个漂移方向都会被抓住。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1729,3 +1729,97 @@ pub(crate) fn acquire_game_creator_agent_provider_plan_project_write_lock_with_w
|
||||
) -> Result<ProjectWriteLock, String> {
|
||||
acquire_game_creator_agent_runtime_project_write_lock_with_wait(root, command_id)
|
||||
}
|
||||
|
||||
/// M1C-1 把 `userRevisionPending` 同时加进了 `StaticDelegateCompletionBarrier::detail()`
|
||||
/// 的输出和本模块的三个解析门,两边靠一个字段名字符串隔空对齐,中间没有共享 schema。
|
||||
///
|
||||
/// 当时生产侧只有 `delegation.rs` 一条 `detail().contains("userRevisionPending=1")`,
|
||||
/// 解析侧一条测试都没有,两者之间也没有任何东西相连。字段名一改,生产侧那条照过,而这
|
||||
/// 三个门会静默返回 false——父 run 于是越过用户修订边界收束。同样,往 `has_waiting()`
|
||||
/// 里加一个计数而忘了加进解析器(或反之),也没有任何用例会报警。
|
||||
///
|
||||
/// 所以这里锁的不是拼写,是**等价关系**:对七个计数的全部组合,解析门的判定必须与
|
||||
/// barrier 自己的语义谓词逐一相等。
|
||||
#[cfg(test)]
|
||||
mod static_delegate_barrier_detail_gate_tests {
|
||||
use super::*;
|
||||
use crate::delegation::StaticDelegateCompletionBarrier;
|
||||
|
||||
fn assert_gates_agree_with_barrier(barrier: StaticDelegateCompletionBarrier) {
|
||||
let detail = barrier.detail();
|
||||
assert_eq!(
|
||||
static_delegate_barrier_has_waiting_deliveries(&detail),
|
||||
barrier.has_waiting(),
|
||||
"has_waiting() 与 detail 解析必须等价:{barrier:?}\ndetail={detail}"
|
||||
);
|
||||
assert_eq!(
|
||||
static_delegate_barrier_requires_repair(&detail),
|
||||
barrier.repair_required_count > 0,
|
||||
"repairRequired 往返失真:{barrier:?}\ndetail={detail}"
|
||||
);
|
||||
assert_eq!(
|
||||
static_delegate_barrier_requires_user_revision(&detail),
|
||||
barrier.user_revision_pending_count > 0,
|
||||
"userRevisionPending 往返失真:{barrier:?}\ndetail={detail}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn barrier_detail_round_trips_through_every_gate_for_all_count_combinations() {
|
||||
let mut checked = 0usize;
|
||||
for bits in 0u32..(1 << 7) {
|
||||
let present = |index: u32| usize::from(bits & (1 << index) != 0);
|
||||
assert_gates_agree_with_barrier(StaticDelegateCompletionBarrier {
|
||||
waiting_count: present(0),
|
||||
ready_unclaimed_count: present(1),
|
||||
unobserved_claim_count: present(2),
|
||||
repair_required_count: present(3),
|
||||
user_input_required_count: present(4),
|
||||
user_revision_pending_count: present(5),
|
||||
unknown_contract_status_count: present(6),
|
||||
});
|
||||
checked += 1;
|
||||
}
|
||||
assert_eq!(checked, 128, "必须覆盖七个计数的全部 0/1 组合");
|
||||
}
|
||||
|
||||
/// 门读的是计数而不是「等于 1」:`detail()` 里出现多位数时不能失配。
|
||||
#[test]
|
||||
fn barrier_detail_gates_read_multi_digit_counts() {
|
||||
assert_gates_agree_with_barrier(StaticDelegateCompletionBarrier {
|
||||
waiting_count: 12,
|
||||
ready_unclaimed_count: 34,
|
||||
unobserved_claim_count: 56,
|
||||
repair_required_count: 78,
|
||||
user_input_required_count: 90,
|
||||
user_revision_pending_count: 123,
|
||||
unknown_contract_status_count: 456,
|
||||
});
|
||||
}
|
||||
|
||||
/// 没有任何键是另一个键的前缀——否则 `strip_prefix` 会读到隔壁字段的值。
|
||||
/// 这条是给未来改名加的护栏:等价关系测试能抓到读错值,但抓不到「读对了值却
|
||||
/// 是因为两个键碰巧不冲突」这层前提何时被打破。
|
||||
#[test]
|
||||
fn barrier_detail_keys_are_prefix_free() {
|
||||
let detail = StaticDelegateCompletionBarrier::default().detail();
|
||||
let keys = detail
|
||||
.split_whitespace()
|
||||
.filter_map(|part| part.split_once('=').map(|(key, _)| key))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(
|
||||
keys.len() >= 7,
|
||||
"detail 必须仍以 key=value 形式给出全部计数:{detail}"
|
||||
);
|
||||
for (index, key) in keys.iter().enumerate() {
|
||||
for (other_index, other) in keys.iter().enumerate() {
|
||||
if index != other_index {
|
||||
assert!(
|
||||
!other.starts_with(key),
|
||||
"detail 键 `{key}` 是 `{other}` 的前缀,strip_prefix 会读错字段"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user