mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +08:00
fix(goal): guide managed JSON retries to reuse current versions
This commit is contained in:
parent
ab1f5069ad
commit
2ce7348c04
@ -5,9 +5,12 @@ public final class GoalJsonProtocolHints {
|
||||
private GoalJsonProtocolHints() { }
|
||||
public static final String INSTRUCTIONS = """
|
||||
This goal has user-selected managed JSON acceptance requirements. Before claiming completion,
|
||||
call getManagedGoalJsonSlots to read current requirements and generations. Produce the requested
|
||||
JSON using publishManagedGoalJson, then call checkManagedGoalJson for every requirement using
|
||||
its exact current revision, artifact ID and generation. Publishing a version alone is not a check.
|
||||
call getManagedGoalJsonSlots to read current requirements and generations. Reuse a current version
|
||||
when it satisfies the request; call checkManagedGoalJson on that version if its binding is missing
|
||||
or outdated. Publish with publishManagedGoalJson only when content needs changing or the version
|
||||
is unusable. Each publication consumes one of 32 versions; a retry does not require a new version,
|
||||
and existing versions can still be checked at the limit. Every requirement needs a current binding
|
||||
using its exact revision, artifact ID and generation. Publishing a version alone is not a check.
|
||||
A new version, an edited requirement or goal definition, or expiry invalidates earlier bindings.
|
||||
Reload after conflicts and check current versions; do not invent PASS results, overwrite blindly,
|
||||
or substitute ordinary file checks or textual claims. Existing semantic criteria still apply.
|
||||
|
||||
@ -16,7 +16,7 @@ Prefix: `/api/v1/goals/{goalId}/json-acceptance`. An enabled account with conver
|
||||
- `POST /artifacts/{slot}`: send `expectedGeneration` and `jsonContent` (a string containing the original JSON body) to append a version and atomically advance the slot.
|
||||
- `GET /artifacts/versions/{artifactId}`: read metadata and exact content of a version belonging to this goal, including historical versions. Read access does not imply current acceptance eligibility.
|
||||
|
||||
Publication requires an active or paused goal and a slot referenced by a current requirement. Content must be a strict JSON object: duplicate keys, trailing documents, nesting beyond 32 levels and UTF-8 content over 1 MiB are rejected. Each goal can retain at most 32 versions; the limit rejects new publication instead of overwriting history. Each version expires after 24 hours. Republishing identical bytes still creates a new version. Reload after a generation conflict rather than automatically overwriting another publication.
|
||||
Publication requires an active or paused goal and a slot referenced by a current requirement. Content must be a strict JSON object: duplicate keys, trailing documents, nesting beyond 32 levels and UTF-8 content over 1 MiB are rejected. Each goal can retain at most 32 versions; the limit rejects new publication instead of overwriting history. Each version expires after 24 hours. Republishing identical bytes still creates a new version. Reload after a generation conflict rather than automatically overwriting another publication. Retries can reuse a suitable current version and refresh its check binding; reaching the quota still permits checking and completing with that version. If the version is expired or its content must change and all 32 versions are used, further publication remains unavailable.
|
||||
|
||||
Managed bodies live independently in the database. Ordinary workspace files, cache paths and hashes in text are not substitutes. No publication API edits historical bodies; bodies and pointers commit together. SHA-256 identifies content and supports integrity checks; it does not isolate an attacker with database credentials or host privileges. The database and service host are trusted foundations of this limited protocol. The JSON service contract has been exercised on H2, MySQL 8.0.46 and PostgreSQL 16.14. The MySQL run isolated an existing V192 migration failure using a test-only migration copy; PostgreSQL used the original Kingbase migration tree while skipping an unrelated bundled-skill import failure. These are protocol tests, not confirmation that an unmodified full installation succeeds. The proprietary Kingbase engine has not been tested.
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
- `POST /artifacts/{slot}`:提交 `expectedGeneration` 和 `jsonContent`(包含原始 JSON 正文的字符串),原子追加新版本并推进槽。
|
||||
- `GET /artifacts/versions/{artifactId}`:读取本 Goal 指定版本的元数据及原始正文,包括历史版本;读取历史版本不表示它仍可用于验收。
|
||||
|
||||
仅当前要求引用的槽可发布,Goal 必须 active 或 paused。正文必须是严格 JSON 对象,拒绝重复键、尾随文档、超过 32 层的嵌套及超过 1 MiB 的 UTF-8 内容。每个 Goal 最多保存 32 个版本;达到配额拒绝继续发布,不覆盖旧版本。每版有效期 24 小时,重复发布同样正文也产生新版本。客户端遇到 generation 冲突应重新读取,不自动覆盖他人发布。
|
||||
仅当前要求引用的槽可发布,Goal 必须 active 或 paused。正文必须是严格 JSON 对象,拒绝重复键、尾随文档、超过 32 层的嵌套及超过 1 MiB 的 UTF-8 内容。每个 Goal 最多保存 32 个版本;达到配额拒绝继续发布,不覆盖旧版本。每版有效期 24 小时,重复发布同样正文也产生新版本。客户端遇到 generation 冲突应重新读取,不自动覆盖他人发布。重试可以复用适用的当前版本并更新检查绑定;达到配额后仍可检查当前版本并在合格时完成。如果版本已过期或正文必须修改且32个版本均已使用,则不能继续发布。
|
||||
|
||||
这些版本独立存储在数据库,不能用普通工作区文件、缓存路径或文字中的 hash 替代。发布接口不支持更新历史正文;所有版本与槽指针同事务保存。SHA-256 用于标识及完整性核对,不能隔离拥有数据库凭据或宿主权限的攻击者;数据库和服务宿主是此有限协议的可信基础。JSON 服务契约已在 H2、MySQL 8.0.46 和 PostgreSQL 16.14 上实测。MySQL 使用仅修正既有 V192 失败的临时迁移副本;PostgreSQL 使用原始 Kingbase 迁移树,跳过无关的内置技能导入失败。这是协议验证,不能代表未修改的完整安装成功;尚未实测 Kingbase 专有引擎。
|
||||
|
||||
|
||||
@ -67,9 +67,10 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
@org.junit.jupiter.params.provider.CsvSource({"false,sync,true", "true,sync,true", "false,stream,true", "true,stream,true",
|
||||
"false,scheduled,true", "true,scheduled,true", "false,recovered,true", "true,recovered,true",
|
||||
"false,scheduled,false", "true,scheduled,false", "false,recovered,false", "true,recovered,false",
|
||||
"false,queued,true"})
|
||||
"false,queued,true", "false,reuse,true", "true,reuse,true"})
|
||||
void authenticatedGoalCompletesThroughHttpOrScheduledProductionRuntime(boolean plan, String entry, boolean accepted) throws Exception {
|
||||
boolean scheduled = entry.equals("scheduled") || entry.equals("recovered");
|
||||
boolean reuse = entry.equals("reuse");
|
||||
boolean queued = entry.equals("queued");
|
||||
boolean recovered = entry.equals("recovered");
|
||||
String username = "http-json-" + UUID.randomUUID();
|
||||
@ -102,6 +103,11 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
JsonNode configured = request("PUT", "/api/v1/goals/" + goal.getId() + "/json-acceptance/requirements/r", token,
|
||||
Map.of("expectedRevision", "0", "artifactSlot", "report", "requiredFields", List.of("summary")));
|
||||
assertEquals(200, configured.path("code").asInt(), configured.toString());
|
||||
if (reuse) {
|
||||
for (long generation = 0; generation < 32; generation++) {
|
||||
artifacts.publish(goal.getId(), "report", new ManagedGoalJsonService.PublishRequest(generation, "{\"summary\":false}"), username);
|
||||
}
|
||||
}
|
||||
GoalRunCoordinator.ClaimedRun run = null;
|
||||
if (scheduled) {
|
||||
jdbc.update("UPDATE mate_agent_goal SET auto_followup_enabled=TRUE WHERE id=?", goal.getId());
|
||||
@ -161,17 +167,30 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
case 2 -> {
|
||||
assertTrue(last.path("required").asBoolean(), String.valueOf(last));
|
||||
revision.set(last.path("requirements").get(0).path("revision").asText());
|
||||
name = "publishManagedGoalJson";
|
||||
arguments = json.writeValueAsString(Map.of("artifactSlot", "report", "expectedGeneration", recovered ? "1" : "0", "jsonContent", "{\"summary\":false}"));
|
||||
if (reuse) {
|
||||
assertEquals(32, last.path("versionCount").asInt());
|
||||
JsonNode current = last.path("slots").get(0).path("current");
|
||||
name = "checkManagedGoalJson";
|
||||
arguments = json.writeValueAsString(Map.of("criterionKey", "r", "expectedRequirementRevision", revision.get(),
|
||||
"artifactId", current.path("artifactId").asText(), "expectedGeneration", current.path("generation").asText()));
|
||||
} else {
|
||||
name = "publishManagedGoalJson";
|
||||
arguments = json.writeValueAsString(Map.of("artifactSlot", "report", "expectedGeneration", recovered ? "1" : "0", "jsonContent", "{\"summary\":false}"));
|
||||
}
|
||||
}
|
||||
case 3 -> {
|
||||
assertEquals(scheduled ? "goal-attempt" : "account-runtime", last.path("producerKind").asText(), String.valueOf(last));
|
||||
name = "checkManagedGoalJson";
|
||||
arguments = json.writeValueAsString(Map.of("criterionKey", "r", "expectedRequirementRevision", revision.get(),
|
||||
"artifactId", last.path("artifactId").asText(), "expectedGeneration", last.path("generation").asText()));
|
||||
if (reuse) {
|
||||
assertTrue(last.path("acceptanceEligible").asBoolean(), String.valueOf(last));
|
||||
name = "getManagedGoalJsonSlots";
|
||||
} else {
|
||||
assertEquals(scheduled ? "goal-attempt" : "account-runtime", last.path("producerKind").asText(), String.valueOf(last));
|
||||
name = "checkManagedGoalJson";
|
||||
arguments = json.writeValueAsString(Map.of("criterionKey", "r", "expectedRequirementRevision", revision.get(),
|
||||
"artifactId", last.path("artifactId").asText(), "expectedGeneration", last.path("generation").asText()));
|
||||
}
|
||||
}
|
||||
case 4 -> {
|
||||
assertTrue(last.path("acceptanceEligible").asBoolean(), String.valueOf(last));
|
||||
assertTrue((reuse ? last.path("checks").get(0) : last).path("acceptanceEligible").asBoolean(), String.valueOf(last));
|
||||
name = "completeGoal";
|
||||
}
|
||||
default -> {
|
||||
@ -248,6 +267,8 @@ class GoalJsonHttpRuntimeIntegrationTest {
|
||||
assertTrue(calls.get() >= (accepted ? 6 : 2) && calls.get() <= (accepted ? 10 : 4), "Bounded offline model calls: " + calls.get());
|
||||
if (!accepted) assertEquals(recovered ? 1 : 0,
|
||||
jdbc.queryForObject("SELECT COUNT(*) FROM mate_goal_json_artifact WHERE goal_id=?", Integer.class, goal.getId()));
|
||||
if (reuse) assertEquals(32, jdbc.queryForObject("SELECT COUNT(*) FROM mate_goal_json_artifact WHERE goal_id=?", Integer.class, goal.getId()),
|
||||
"Checking and completing a current version must not consume another publication");
|
||||
JsonNode currentRequirements = request("GET", "/api/v1/goals/" + goal.getId() + "/json-acceptance", token, null);
|
||||
assertEquals(accepted ? "completed" : "active", currentRequirements.path("data").path("status").asText());
|
||||
verify(modelFactory, atLeastOnce()).buildFor(any(), any());
|
||||
|
||||
Loading…
Reference in New Issue
Block a user