mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +08:00
Verify settled Goal approval fences and document replay boundary
This commit is contained in:
parent
92723d6919
commit
8a15c0b573
@ -65,4 +65,6 @@ Approval replay restores the persisted runtime identity; approval does not renew
|
||||
|
||||
JWT requests match the signed userId to the current enabled account ID. Recreating an account with the same username does not let the old token modify managed requirements or acquire the new runtime identity. A missing or malformed ID requires a fresh login. Sliding renewal retains the validated account identity. Managed HTTP operations also lock and recheck the authenticated account ID inside their transaction, retaining that lock until the read or write finishes. A username alone or an in-flight identity whose account was replaced cannot access these endpoints.
|
||||
|
||||
After Web approval, Plan execution restores the original plan and approved call, retaining the requester and managed acceptance requirements. Approval itself does not replace a JSON check or complete the goal.
|
||||
After interactive Web approval, Plan execution restores the original plan and approved call, retaining the requester and managed acceptance requirements. Approval itself does not replace a JSON check or complete the goal.
|
||||
|
||||
When a background Goal settles into awaiting approval, its original attempt lease is released. Replaying that persisted identity cannot access managed artifacts or complete the Goal. A fresh attempt can reuse still-eligible evidence, but automatic transfer of a settled approval to a new lease is not yet provided; interactive approval verification does not cover this background path.
|
||||
|
||||
@ -67,4 +67,6 @@ Web排队消息从V199起保存入队时已认证账户的内部ID,普通Web
|
||||
|
||||
JWT请求同时核对签名令牌的userId与当前启用账户ID。同名账户重新创建后,旧账户令牌不能修改托管要求或取得新账户的运行身份;缺失或格式错误的ID需要重新登录。滑动续期沿用已验证的账户身份。 托管HTTP操作还会在事务内按认证账户ID加锁复查,并持锁至读取或修改结束;仅有用户名或已被替换的在途身份不能访问这些接口。
|
||||
|
||||
Web审批后的Plan执行会恢复原计划和已批准调用,并保留请求者及受管验收要求;审批通过本身不能替代JSON检查或完成目标。
|
||||
交互式Web审批后的Plan执行会恢复原计划和已批准调用,并保留请求者及受管验收要求;审批通过本身不能替代JSON检查或完成目标。
|
||||
|
||||
后台 Goal 进入待审批并结算后,原 attempt 的租约已经释放;其持久化审批身份不能再读写托管产物或完成 Goal。新 attempt 可复用仍合格的已有版本,但当前尚未提供已结算审批到新租约的自动接续,不能将交互式审批验证视为这一后台路径已通过。
|
||||
|
||||
@ -746,6 +746,49 @@ class GoalJsonAcceptanceIntegrationTest {
|
||||
: goals.markRuntimeCompleted(goal.getId(), evaluation, origin);
|
||||
}
|
||||
|
||||
@ParameterizedTest @ValueSource(booleans = {false, true})
|
||||
void approvalAfterSettledAttemptCannotReviveFenceButFreshAttemptCanReuseEvidence(boolean automatic) {
|
||||
GoalEntity goal = goal(true);
|
||||
goals.appendCriterion(goal.getId(), "report", alice);
|
||||
var evaluation = new GoalEvaluationResult(1, "offline fixture", "completed", true, "fixture", 1, 0,
|
||||
List.of(new GoalChecklistVerdict.CriterionVerdict("C1", true, "fixture only")), null);
|
||||
goals.recordEvaluation(goal.getId(), evaluation, 1, 1);
|
||||
acceptance.configure(goal.getId(), "r", request(0, "summary"), alice);
|
||||
var run = claimed(goal);
|
||||
var origin = attemptOrigin(goal, run);
|
||||
var version = artifacts.publishForRuntime(origin, "report", publication(0, "{\"summary\":false}"));
|
||||
bindings.checkForRuntime(origin, "r", checkRequest(1, version));
|
||||
String pending;
|
||||
vip.mate.agent.context.ChatOriginHolder.set(origin);
|
||||
try {
|
||||
pending = approvals.createPending(goal.getConversationId(), alice, "getManagedGoalJsonSlots", "{}",
|
||||
"offline settled approval fixture", "[]", null, "1");
|
||||
} finally { vip.mate.agent.context.ChatOriginHolder.clear(); }
|
||||
assertTrue(coordinator.settle(run, new SegmentOutcome.AwaitApproval("awaiting_approval"), java.time.LocalDateTime.now()));
|
||||
assertEquals("waiting_approval", continuations.get(goal.getId()).state());
|
||||
assertNull(continuations.get(goal.getId()).currentAttemptId());
|
||||
var consumed = approvals.resolveAndConsume(pending, alice).consumedSnapshot();
|
||||
assertNotNull(consumed);
|
||||
var replay = approvals.restoreChatOrigin(consumed.getChatOrigin()).withApprovalId(pending);
|
||||
assertEquals(run.attempt().id(), replay.executionAttribution().goalAttemptId());
|
||||
assertFalse(coordinator.renew(run, java.time.LocalDateTime.now()));
|
||||
assertThrows(MateClawException.class, () -> bindings.snapshotForRuntime(replay));
|
||||
assertThrows(MateClawException.class, () -> artifacts.publishForRuntime(replay, "report", publication(1, "{\"summary\":true}")));
|
||||
assertThrows(MateClawException.class, () -> bindings.checkForRuntime(replay, "r", checkRequest(1, version)));
|
||||
assertThrows(MateClawException.class, () -> runtimeComplete(goal, evaluation, replay, automatic));
|
||||
assertEquals(GoalStatus.ACTIVE, goals.getById(goal.getId()).getStatus());
|
||||
assertEquals(1, artifacts.list(goal.getId(), alice).getFirst().generation());
|
||||
// Simulate the existing replay turn's completion signal, then use a newly claimed owner.
|
||||
continuations.turnFinished(goal.getConversationId(), java.time.LocalDateTime.now());
|
||||
var fresh = claimed(goal);
|
||||
assertNotEquals(run.attempt().id(), fresh.attempt().id());
|
||||
assertNotEquals(run.attempt().leaseToken(), fresh.attempt().leaseToken());
|
||||
var freshOrigin = attemptOrigin(goal, fresh);
|
||||
assertTrue(bindings.snapshotForRuntime(freshOrigin).checks().getFirst().acceptanceEligible());
|
||||
assertEquals(GoalStatus.COMPLETED, runtimeComplete(goal, evaluation, freshOrigin, automatic).getStatus());
|
||||
assertEquals(1, jdbc.queryForObject("SELECT COUNT(*) FROM mate_goal_json_artifact WHERE goal_id=?", Integer.class, goal.getId()));
|
||||
}
|
||||
|
||||
@ParameterizedTest @ValueSource(strings = {"account", "scheduled", "legacy"})
|
||||
void persistedApprovalOriginRetainsIdentityButCannotOverrideCurrentAuthorization(String kind) {
|
||||
GoalEntity goal = goal(kind.equals("scheduled"));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user