mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 04:18:17 +08:00
test(goal): cover unselected queue goal race
This commit is contained in:
parent
ab2589550e
commit
ad7b576c40
@ -53,7 +53,7 @@ The host clock, database credentials and service host remain trusted. Run arbitr
|
||||
|
||||
V198 also stores absolute scheduler lease deadlines. Existing leases expire during upgrade and are recovered from their persisted checkpoints: safe work may receive a new attempt; uncertain side effects remain blocked for review. Expired owners cannot renew, checkpoint, settle or use managed JSON tools. Renewal checks both current lease records after acquiring the goal lock, so a delayed scheduler tick cannot reuse an old timestamp to revive its owner. New valid owners can continue under the existing requirements. Recovery skips a scanned attempt while its continuation lease is still live or its owner has changed, so other eligible recoveries can proceed. A passing JSON binding does not override a pause caused by an uncertain tool outcome.
|
||||
|
||||
Validation snapshot (2026-09-15): the full default backend test run passed 5,359 executed tests with 46 conditional skips; the frontend passed 391 tests. The managed contract also has real compiled ReAct/Plan graph tests for account and scheduled-owner execution. Their model choices and semantic verdicts are controlled fixtures, not online-model benchmarks. Specialized integration profiles and the proprietary Kingbase engine are outside that full-default-suite claim.
|
||||
Validation snapshot (2026-09-15): the full default backend test run passed 5,392 executed tests with 46 conditional skips; the frontend passed 391 tests. The managed contract also has real compiled ReAct/Plan graph tests for account and scheduled-owner execution. Its opt-in HTTP, approval and queue subset passed 32 tests each on MySQL 8.0.46 and PostgreSQL 16.14, including an explicitly unselected queued turn that becomes stale when a managed Goal appears; the broader managed JSON protocol suite passed 72 tests on each database. Their model choices and semantic verdicts are controlled fixtures, not online-model benchmarks. The MySQL run uses a test-only workaround for the unrelated V192 temporary-table migration, the PostgreSQL run uses the Kingbase-compatible migration tree with an unrelated skill bootstrap mocked, and the proprietary Kingbase engine remains outside these claims.
|
||||
|
||||
Built-in shell/code execution is not OS-isolated from the service host. Selecting JSON acceptance does not sandbox those tools, and the protocol cannot defend against host code that can access database credentials or files. Environment-name filtering and workspace path checks do not replace that isolation. Lease deadlines are calculated from absolute instants, including daylight-saving clock rollback; scheduling display fields remain local timestamps.
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ V197 使用 epoch 秒作为有效期依据,不受 JVM/JDBC 时区变化影响
|
||||
|
||||
V198 同样以绝对时间保存调度租约截止。升级时旧租约失效,按已有检查点恢复:安全工作可建立新 attempt,不确定副作用仍阻断并要求核实。过期 owner 不能续租、提交检查点、结算或使用受管 JSON 工具。续租在获得 Goal 锁后检查两侧当前租约,迟到调度 tick 不能利用旧时间戳复活 owner;新的有效 owner 可按现有要求继续。恢复扫描遇到 continuation 租约仍有效或 owner 已变化的记录时会跳过,继续处理其他可恢复目标。JSON 绑定通过也不能越过不确定工具结果导致的暂停。
|
||||
|
||||
验证快照(2026-09-15):默认后端完整测试集实际通过 5,359 项、条件跳过 46 项,前端通过 391 项。受管契约还覆盖真实编译的 ReAct/Plan 图及账户/调度 owner 四种组合;模型选择和语义评价是受控夹具,不是在线模型基准。专门集成 profile 与 Kingbase 专有引擎不包含在“默认完整测试集”结论中。
|
||||
验证快照(2026-09-15):默认后端完整测试集实际通过 5,392 项、条件跳过 46 项,前端通过 391 项。受管契约还覆盖真实编译的ReAct/Plan图及账户/调度owner组合;MySQL 8.0.46与PostgreSQL 16.14各有32项显式HTTP、审批和队列子集通过,包括明确未选定队列在等待期出现新受管Goal后判为过期,各库更广的受管JSON协议集为72项通过。模型选择和语义评价是受控夹具,不是在线模型基准。MySQL使用仅供测试的无关V192临时表迁移替代,PostgreSQL使用Kingbase兼容迁移树并mock无关skill启动;Kingbase专有引擎仍不在这些结论中。
|
||||
|
||||
内置 shell/code 执行没有与服务宿主做操作系统隔离。选择 JSON 验收不会把这些工具变成沙箱;此协议不能抵抗能访问数据库凭据或文件的宿主代码,环境变量名称过滤和工作区路径检查也不能替代隔离。租约截止从绝对时刻计算,覆盖夏令时回拨;调度显示字段仍使用本地时间戳。
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ class GoalJsonExternalApprovalIntegrationTest extends GoalJsonHttpRuntimeIntegra
|
||||
@Override
|
||||
@ParameterizedTest
|
||||
@CsvSource({"false,sync,true", "false,approval,true", "true,approval,true",
|
||||
"false,queued-unselected-then-goal,true", "true,queued-unselected-then-goal,true",
|
||||
"false,scheduled-queued,true", "true,scheduled-queued,true",
|
||||
"false,scheduled-queued-foreign,true", "true,scheduled-queued-foreign,true",
|
||||
"false,scheduled-queued-legacy,true", "true,scheduled-queued-legacy,true",
|
||||
|
||||
@ -85,7 +85,8 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
"false,scheduled-queued-terminal-unselected,true", "true,scheduled-queued-terminal-unselected,true",
|
||||
"false,scheduled-queued-paused,true", "true,scheduled-queued-paused,true", "false,recovered,true", "true,recovered,true",
|
||||
"false,scheduled,false", "true,scheduled,false", "false,recovered,false", "true,recovered,false",
|
||||
"false,queued,true", "false,reuse,true", "true,reuse,true", "false,recheck,true", "true,recheck,true",
|
||||
"false,queued,true", "false,queued-unselected-then-goal,true", "true,queued-unselected-then-goal,true",
|
||||
"false,reuse,true", "true,reuse,true", "false,recheck,true", "true,recheck,true",
|
||||
"false,supervised,true", "true,supervised,true", "false,supervised-recovered,true", "true,supervised-recovered,true",
|
||||
"false,supervised,false", "true,supervised,false", "false,supervised-recovered,false", "true,supervised-recovered,false",
|
||||
"false,approval,true", "true,approval,true", "false,scheduled-approval,true", "true,scheduled-approval,true",
|
||||
@ -117,7 +118,8 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
boolean scheduled = entry.startsWith("scheduled") || entry.equals("recovered") || supervised;
|
||||
boolean reuse = entry.equals("reuse");
|
||||
boolean recheck = entry.equals("recheck");
|
||||
boolean queued = entry.equals("queued") || queuedPreflightRejected;
|
||||
boolean queuedReplacement = entry.equals("queued-unselected-then-goal");
|
||||
boolean queued = entry.equals("queued") || queuedReplacement || queuedPreflightRejected;
|
||||
boolean recovered = entry.equals("recovered") || entry.equals("supervised-recovered");
|
||||
String username = "http-json-" + UUID.randomUUID();
|
||||
String conversation = UUID.randomUUID().toString();
|
||||
@ -559,16 +561,43 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
});
|
||||
try {
|
||||
assertTrue(firstSubscribed.await(10, java.util.concurrent.TimeUnit.SECONDS), "Initial HTTP turn must reach the actual model boundary");
|
||||
if (queuedReplacement) goals.abandon(goal.getId(), username);
|
||||
JsonNode enqueue = request("POST", "/api/v1/chat/" + conversation + "/interrupt", token,
|
||||
Map.of("agentId", String.valueOf(agentId), "message", message));
|
||||
assertTrue(enqueue.path("data").path("queued").asBoolean(), enqueue.toString());
|
||||
long queueId = Long.parseLong(enqueue.path("data").path("queueItemId").asText());
|
||||
assertEquals(userId, jdbc.queryForObject("SELECT requester_user_id FROM mate_conversation_input_queue WHERE id=?", Long.class, queueId));
|
||||
GoalEntity replacement = null;
|
||||
if (queuedReplacement) {
|
||||
assertEquals(0L, jdbc.queryForObject(
|
||||
"SELECT selected_goal_id FROM mate_conversation_input_queue WHERE id=?", Long.class, queueId));
|
||||
var replacementCreate = new GoalCreateRequest(); replacementCreate.setConversationId(conversation);
|
||||
replacementCreate.setAgentId(agentId); replacementCreate.setWorkspaceId(1L);
|
||||
replacementCreate.setTitle("Managed Goal created while input waits");
|
||||
replacementCreate.setDescription("Do not attach the explicit zero queue snapshot");
|
||||
replacement = goals.create(replacementCreate, username);
|
||||
JsonNode replacementConfigured = request("PUT", "/api/v1/goals/" + replacement.getId()
|
||||
+ "/json-acceptance/requirements/r", token,
|
||||
Map.of("expectedRevision", "0", "artifactSlot", "report", "requiredFields", List.of("summary")));
|
||||
assertEquals(200, replacementConfigured.path("code").asInt(), replacementConfigured.toString());
|
||||
}
|
||||
String initialAnswer = plan
|
||||
? "{\"needs_planning\":false,\"direct_answer\":\"Initial fixture turn finished.\"}"
|
||||
: "Initial fixture turn finished.";
|
||||
assertEquals(reactor.core.publisher.Sinks.EmitResult.OK, initialResponse.tryEmitValue(
|
||||
new ChatResponse(List.of(new Generation(new AssistantMessage("Initial fixture turn finished."))))));
|
||||
new ChatResponse(List.of(new Generation(new AssistantMessage(initialAnswer))))));
|
||||
String events = response.get(30, java.util.concurrent.TimeUnit.SECONDS);
|
||||
assertTrue(events.contains("Managed JSON fixture completed."), events);
|
||||
assertEquals("consumed", jdbc.queryForObject("SELECT state FROM mate_conversation_input_queue WHERE id=?", String.class, queueId));
|
||||
if (queuedReplacement) {
|
||||
assertTrue(events.contains("queued_input_skipped"), events);
|
||||
assertEquals(0, calls.get(), "The explicit zero snapshot must not start a second model turn");
|
||||
assertEquals(GoalStatus.ACTIVE, goals.getById(replacement.getId()).getStatus());
|
||||
assertEquals(0, jdbc.queryForObject("SELECT COUNT(*) FROM mate_goal_json_artifact WHERE goal_id=?",
|
||||
Integer.class, replacement.getId()));
|
||||
return;
|
||||
} else {
|
||||
assertTrue(events.contains("Managed JSON fixture completed."), events);
|
||||
}
|
||||
} finally {
|
||||
initialResponse.tryEmitEmpty();
|
||||
response.cancel(true);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user