@tea-agent/loop-agent 0.33.7-beta.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +67 -16
  2. package/dist/application/task-lifecycle/advance.js +309 -9
  3. package/dist/application/task-lifecycle/gates.js +50 -0
  4. package/dist/application/task-lifecycle/observe.js +11 -2
  5. package/dist/application/task-lifecycle/plan-transitions.js +13 -21
  6. package/dist/commands/init-upgrade.js +32 -1
  7. package/dist/commands/init.js +94 -3
  8. package/dist/executors/dag-pi-executor.js +18 -3
  9. package/dist/executors/shell-executor.js +4 -91
  10. package/dist/executors/shell-write-guard.js +26 -8
  11. package/dist/shared/operator/capabilities.js +98 -44
  12. package/dist/shared/resilient-git.js +133 -0
  13. package/dist/task/source-prepare/artifact-meta.js +137 -0
  14. package/dist/task/source-prepare/index.js +2 -0
  15. package/dist/task/source-prepare/parse-intent.js +58 -10
  16. package/dist/task/source-prepare/prepare.js +229 -18
  17. package/dist/task/source-prepare/reference-integrity.js +18 -2
  18. package/dist/task/source-prepare/semantic-intake.js +404 -0
  19. package/dist/worker/console/app-data.js +2 -0
  20. package/dist/worker/console/chat/chat-event-store.js +190 -25
  21. package/dist/worker/console/chat/model-resolver.js +17 -0
  22. package/dist/worker/console/chat/pi-console-config.js +250 -32
  23. package/dist/worker/console/chat/pi-runtime.js +1007 -188
  24. package/dist/worker/console/chat/resource-loader.js +5 -4
  25. package/dist/worker/console/chat/routes.js +495 -157
  26. package/dist/worker/console/chat/runtime-context.js +48 -12
  27. package/dist/worker/console/chat/runtime-selection.js +59 -0
  28. package/dist/worker/console/chat/session-store.js +39 -0
  29. package/dist/worker/console/chat/shortcuts.js +1 -0
  30. package/dist/worker/console/chat/tool-adapter.js +9 -3
  31. package/dist/worker/console/chat/tools.js +5 -1
  32. package/dist/worker/console/dag-execution-receipt.js +380 -0
  33. package/dist/worker/console/interview/grill-me.js +7 -6
  34. package/dist/worker/console/operator-actions.js +785 -85
  35. package/dist/worker/console/prd-intake-bridge.js +393 -0
  36. package/dist/worker/console/recovery-cta.js +35 -6
  37. package/dist/worker/console/server.js +8 -15
  38. package/dist/worker/console/static/assets/index-BQkhJpV8.css +1 -0
  39. package/dist/worker/console/static/assets/index-BpuHmlSP.js +29 -0
  40. package/dist/worker/console/static/index.html +2 -2
  41. package/dist/worker/console/static-src/app/console-types.js +1 -0
  42. package/dist/worker/console/static-src/app/usePrdImport.js +2 -1
  43. package/dist/worker/console/static-src/app/useRecoveryActions.js +24 -1
  44. package/dist/worker/console/static-src/app/useRecoveryConsole.js +19 -1
  45. package/dist/worker/console/static-src/app/useTaskWizard.js +57 -2
  46. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +45 -8
  47. package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
  48. package/dist/worker/console/static-src/operator-chat/refs.js +9 -0
  49. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
  50. package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
  51. package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
  52. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +37 -0
  53. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +125 -22
  54. package/dist/worker/console/static-src/operator-chat/useChatStream.js +215 -184
  55. package/dist/worker/console/static-src/operator-chat/useChatThread.js +49 -5
  56. package/dist/worker/console/static-src/operator-chat/useComposer.js +17 -0
  57. package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
  58. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +225 -74
  59. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
  60. package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
  61. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +31 -0
  62. package/dist/worker/delivery/final-verification.js +13 -5
  63. package/dist/worker/delivery/package.js +31 -19
  64. package/dist/worker/delivery/verification-bundle.js +6 -4
  65. package/dist/worker/observability/read-model.js +3 -0
  66. package/dist/worker/observe/static/operator-chrome.css +5 -2
  67. package/dist/worker/observe/static/operator-chrome.js +6 -1
  68. package/dist/worker/observe/static/styles.css +39 -9
  69. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +33 -462
  70. package/dist/workflows/dag/backend-test-case-manifest.js +0 -4
  71. package/dist/workflows/dag/backend-test-markdown-workflow.js +1 -25
  72. package/dist/workflows/dag/backend-test-module-stem.js +0 -5
  73. package/dist/workflows/dag/backend-test-pytest-collection.js +24 -345
  74. package/dist/workflows/dag/backend-test-scenario-param.js +82 -269
  75. package/dist/workflows/dag/backend-test-writer-completeness.js +16 -47
  76. package/dist/workflows/dag/dynamic-runtime/map.js +8 -24
  77. package/dist/workflows/dag/failure-category.js +3 -0
  78. package/dist/workflows/dag/frontend-worktree-diff.js +12 -27
  79. package/dist/workflows/dag/init-hybrid.js +49 -55
  80. package/dist/workflows/dag/node-execution.js +3 -2
  81. package/dist/workflows/dag/retry-policy.js +44 -11
  82. package/dist/workflows/dag/runner.js +6 -0
  83. package/dist/workflows/dag/scheduler.js +49 -1
  84. package/dist/workflows/dag/types.js +0 -7
  85. package/dist/workflows/dag/validate.js +16 -2
  86. package/dist/workflows/dag/workspace-checkpoint.js +8 -27
  87. package/docs/templates/agent-dag.schema.json +4 -4
  88. package/docs/templates/backend-test-dag.json +29 -32
  89. package/harness.json +1 -1
  90. package/package.json +1 -1
  91. package/skills/local-jacoco-coverage/SKILL.md +281 -0
  92. package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
  93. package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
  94. package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
  95. package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
  96. package/skills/loop-agent/references/command-reference.md +3 -1
  97. package/skills/loop-agent/references/source-and-plan-practice.md +13 -0
  98. package/skills/loop-agent/references/task-workflow.md +4 -0
  99. package/dist/worker/console/chat/instruction-skills.js +0 -217
  100. package/dist/worker/console/static/assets/index-CnUXAqxG.css +0 -1
  101. package/dist/worker/console/static/assets/index-CteJFFL2.js +0 -29
@@ -104,9 +104,35 @@ async function mapConcurrent(items, limit, fn) {
104
104
  }
105
105
  await Promise.all(executing);
106
106
  }
107
+ /** Fail-closed: leave no PENDING nodes that look "still scheduled" after abort. */
108
+ export function markPendingNodesControllerInterrupted(state, reason = "run aborted by controller (abortSignal)") {
109
+ const affected = [];
110
+ const finishedAt = new Date().toISOString();
111
+ for (const [nodeId, node] of Object.entries(state.nodes)) {
112
+ if (node.status !== "PENDING")
113
+ continue;
114
+ node.status = "ERROR";
115
+ node.failureCategory = "controller-interrupted";
116
+ node.stderr = reason;
117
+ node.finishedAt = finishedAt;
118
+ affected.push(nodeId);
119
+ }
120
+ if (affected.length > 0 && !state.failureCategory) {
121
+ state.failureCategory = "controller-interrupted";
122
+ }
123
+ return affected;
124
+ }
107
125
  export async function executeDagRanksOnce(input) {
108
126
  let pausedByNodeId;
109
127
  for (const rank of input.ranks) {
128
+ if (input.abortSignal?.aborted) {
129
+ const marked = markPendingNodesControllerInterrupted(input.state, input.abortSignal.reason
130
+ ? `run aborted by controller: ${String(input.abortSignal.reason)}`
131
+ : undefined);
132
+ if (marked.length > 0)
133
+ await input.persistState();
134
+ break;
135
+ }
110
136
  const pending = rank.filter((id) => {
111
137
  const node = input.state.nodes[id];
112
138
  return node?.status === "PENDING";
@@ -169,6 +195,12 @@ export async function executeDagRanksOnce(input) {
169
195
  });
170
196
  const executeNode = input.createExecuteNodeForRank(rankWriterNodeIds);
171
197
  for (const nodeId of pauseGateRunnable) {
198
+ if (input.abortSignal?.aborted) {
199
+ const marked = markPendingNodesControllerInterrupted(input.state);
200
+ if (marked.length > 0)
201
+ await input.persistState();
202
+ break;
203
+ }
172
204
  await input.executeScheduledNode(nodeId, executeNode, (id, pausedAt, pauseReason) => {
173
205
  pausedByNodeId = id;
174
206
  input.state.pausedAt = pausedAt;
@@ -178,9 +210,19 @@ export async function executeDagRanksOnce(input) {
178
210
  if (pausedByNodeId)
179
211
  break;
180
212
  }
181
- if (pausedByNodeId)
213
+ if (pausedByNodeId || input.abortSignal?.aborted) {
214
+ if (input.abortSignal?.aborted && !pausedByNodeId) {
215
+ const marked = markPendingNodesControllerInterrupted(input.state);
216
+ if (marked.length > 0)
217
+ await input.persistState();
218
+ }
182
219
  break;
220
+ }
183
221
  await mapConcurrent(regularRunnable, input.maxConcurrent, async (nodeId) => {
222
+ if (input.abortSignal?.aborted) {
223
+ // Sibling may still be PENDING; outer loop finalizes remaining.
224
+ return;
225
+ }
184
226
  await input.executeScheduledNode(nodeId, executeNode, (id, pausedAt, pauseReason) => {
185
227
  pausedByNodeId = id;
186
228
  input.state.pausedAt = pausedAt;
@@ -190,6 +232,12 @@ export async function executeDagRanksOnce(input) {
190
232
  });
191
233
  if (pausedByNodeId)
192
234
  break;
235
+ if (input.abortSignal?.aborted) {
236
+ const marked = markPendingNodesControllerInterrupted(input.state);
237
+ if (marked.length > 0)
238
+ await input.persistState();
239
+ break;
240
+ }
193
241
  }
194
242
  return pausedByNodeId;
195
243
  }
@@ -555,13 +555,6 @@ export const dagDynamicExpansionSchema = z.object({
555
555
  maxTotalTokens: z.number().int().positive().optional(),
556
556
  })
557
557
  .optional(),
558
- /**
559
- * When true, any child blocked by the aggregate token budget fails the map
560
- * barrier instead of being retained as a case-level blocked outcome.
561
- * Backend-test enables this because partial Markdown/pytest module sets are
562
- * invalid generation assets; frontend-test keeps the default false.
563
- */
564
- failOnTokenBudgetExhaustion: z.boolean().optional(),
565
558
  /**
566
559
  * When true, map child ERROR/auth/timeout is recorded as case-level
567
560
  * failed/blocked evidence and the map barrier still succeeds (frontend-test).
@@ -3,7 +3,7 @@ import { resolveShellCommands } from "../../executors/shell-executor.js";
3
3
  import { pathMatchesPattern } from "../../shared/git-progress.js";
4
4
  import { resolveRepairTaskForGate } from "./repair-artifact.js";
5
5
  import { topoSortToRanks } from "./topo.js";
6
- import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "./retry-policy.js";
6
+ import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "./retry-policy.js";
7
7
  import { isBackendTestCompletenessRetryCandidate } from "./backend-test-writer-completeness.js";
8
8
  const GOVERNANCE_WARNING_TYPES = new Set([
9
9
  "read-only-missing-artifacts-forbidden",
@@ -598,13 +598,15 @@ function validateRetryPolicyTaskConfig(task, issues) {
598
598
  return;
599
599
  const isReadOnlyCandidate = isSafeReadOnlyPiRetryCandidate(task);
600
600
  const isWriterEmptyDiffCandidate = isWriterEmptyDiffRetryCandidate(task);
601
+ const isWriterTransportCandidate = isWriterTransportRetryCandidate(task);
601
602
  const isBackendCompletenessCandidate = isBackendTestCompletenessRetryCandidate(task);
602
603
  if (!isReadOnlyCandidate &&
603
604
  !isWriterEmptyDiffCandidate &&
605
+ !isWriterTransportCandidate &&
604
606
  !isBackendCompletenessCandidate) {
605
607
  issues.push({
606
608
  type: "invalid-retry-policy",
607
- message: `task ${task.id} declares retryPolicy but is neither a safe read-only non-dynamic Pi node nor an exclusive bounded Pi implementer with writerOutcomePolicy.requireChangedFiles=true`,
609
+ message: `task ${task.id} declares retryPolicy but is neither a safe read-only non-dynamic Pi node, an exclusive bounded Pi implementer with writerOutcomePolicy.requireChangedFiles=true, nor a standard exclusive implementer transport-retry candidate`,
608
610
  });
609
611
  return;
610
612
  }
@@ -635,6 +637,18 @@ function validateRetryPolicyTaskConfig(task, issues) {
635
637
  type: "invalid-retry-policy",
636
638
  message: `task ${task.id} writer retryPolicy must use exactly two total attempts and only ${WRITER_EMPTY_DIFF_RETRY_CATEGORY}`,
637
639
  });
640
+ return;
641
+ }
642
+ if (isWriterTransportCandidate) {
643
+ const categories = new Set(task.retryPolicy.retryCategories);
644
+ if (task.retryPolicy.maxAttempts !== 2 ||
645
+ categories.size !== 1 ||
646
+ !categories.has(WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY)) {
647
+ issues.push({
648
+ type: "invalid-retry-policy",
649
+ message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only ${WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY}`,
650
+ });
651
+ }
638
652
  }
639
653
  }
640
654
  function validateOutputProtocolTaskConfig(task, issues) {
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
- import { spawn } from "node:child_process";
3
2
  import { lstat, readFile, realpath } from "node:fs/promises";
4
3
  import path from "node:path";
4
+ import { runResilientGitCommand } from "../../shared/resilient-git.js";
5
5
  import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
6
6
  /**
7
7
  * Workspace checkpoint for DAG continuation safety.
@@ -13,30 +13,10 @@ function sha256Hex(input) {
13
13
  return createHash("sha256").update(input).digest("hex");
14
14
  }
15
15
  async function runGit(cwd, args) {
16
- return new Promise((resolve, reject) => {
17
- const child = spawn("git", args, {
18
- cwd,
19
- stdio: ["ignore", "pipe", "pipe"],
20
- });
21
- let stdout = "";
22
- let stderr = "";
23
- child.stdout.setEncoding("utf-8");
24
- child.stderr.setEncoding("utf-8");
25
- child.stdout.on("data", (chunk) => {
26
- stdout += chunk;
27
- });
28
- child.stderr.on("data", (chunk) => {
29
- stderr += chunk;
30
- });
31
- child.on("error", reject);
32
- child.on("close", (code) => {
33
- if (code === 0) {
34
- resolve(stdout);
35
- return;
36
- }
37
- reject(new Error(`git ${args.join(" ")} failed: ${stderr.trim() || stdout.trim()}`));
38
- });
39
- });
16
+ const result = await runResilientGitCommand({ cwd, args, readOnly: true });
17
+ if (result.code === 0)
18
+ return result.stdout;
19
+ throw new Error(`git ${args.join(" ")} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`}`);
40
20
  }
41
21
  function normalizeGitPath(value) {
42
22
  const target = value.includes(" -> ")
@@ -137,9 +117,10 @@ async function hashStableGovernanceInputs(repoRoot, taskId) {
137
117
  * When `taskId` is provided, fingerprint includes stable ignored Task Contract inputs.
138
118
  */
139
119
  export async function captureWorkspaceCheckpoint(repoRoot, options) {
120
+ const run = options?.dependencies?.runGit ?? runGit;
140
121
  const capturedAt = (options?.now ?? new Date()).toISOString();
141
- const gitHeadSha = (await runGit(repoRoot, ["rev-parse", "HEAD"])).trim();
142
- const porcelain = await runGit(repoRoot, [
122
+ const gitHeadSha = (await run(repoRoot, ["rev-parse", "HEAD"])).trim();
123
+ const porcelain = await run(repoRoot, [
143
124
  "status",
144
125
  "--porcelain=v1",
145
126
  "--untracked-files=all",
@@ -401,7 +401,7 @@
401
401
  "type": "object",
402
402
  "additionalProperties": false,
403
403
  "required": ["maxAttempts"],
404
- "description": "Retry policy for safe read-only Pi nodes, plus the sole explicit writer exception: an exclusive bounded Pi implementer with writerOutcomePolicy.requireChangedFiles=true may use exactly two attempts with only writer-empty-diff. Total attempts include the first try and are capped at five. Only the categories listed in retryCategories are retried; quota/auth/invalid-output/write-guard are never retried.",
404
+ "description": "Retry policy for safe read-only Pi nodes and validation-gated bounded writer exceptions. Total attempts include the first try and are capped at five. Standard implementation-outcome writers may use exactly two attempts with only writer-clean-timeout after the executor proves zero attributed writes and zero write-tool calls; requireChangedFiles writers retain their dedicated writer-empty-diff/completeness policies.",
405
405
  "properties": {
406
406
  "maxAttempts": {
407
407
  "type": "integer",
@@ -423,10 +423,10 @@
423
423
  "retryCategories": {
424
424
  "type": "array",
425
425
  "items": {
426
- "enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-too-large", "protocol-invalid", "writer-empty-diff"]
426
+ "enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout"]
427
427
  },
428
- "default": ["timeout", "network", "rate-limit", "unavailable"],
429
- "description": "Failure categories eligible for retry. quota is never eligible. empty-output is safe only because retryPolicy is restricted to read-only Pi nodes (apart from the exact writer-empty-diff exception). output-too-large is reserved for explicit structured-required nodes. protocol-invalid is for nodes that declare outputProtocol with retryOnInvalid. writer-empty-diff is reserved for the exact bounded writer policy (two total attempts, requireChangedFiles=true) after executor-attributed empty diff evidence."
428
+ "default": ["timeout", "network", "rate-limit", "unavailable", "empty-output"],
429
+ "description": "Failure categories eligible for retry. quota/auth/write-guard are never eligible. invalid-output is allowed only on explicit structured/protocol policies; writer-empty-diff and incomplete-write-set are reserved for requireChangedFiles generation/completeness writers; writer-clean-timeout is reserved for standard bounded writers after explicit zero-write-tool-call and empty-diff evidence."
430
430
  }
431
431
  }
432
432
  },
@@ -140,7 +140,7 @@
140
140
  ]
141
141
  },
142
142
  "outputContract": "Write a Chinese, human-readable testcase/md/README.md as the single Markdown-first entry page with Coverage Scope, Coverage Matrix and a machine-parseable module index. Do not write module case cards here; do not execute pytest or modify production code/config.",
143
- "subtask_prompt": "This is a required file-generation node. After reading the bounded inputs, immediately use write tools to create testcase/md/README.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Write ONLY testcase/md/README.md in this node; module case cards are written by downstream sharded nodes.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, case bodies, or source text into assistant chat. README holds only Scope+Matrix+module index; never inline full case bodies. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nThe first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the README has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.\n\nRead the upstream environment report. Generate the Markdown-first backend test README under testcase/md/README.md.\n\nWrite human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.\n\nCreate testcase/md/README.md as the concise entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in README.\n\nBefore the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in README using exactly `| Field | Value |`, immediately followed by the separator row `|---|---|`, and these six unique rows: `Change Classification`, `Coverage Policy`, `Affected Operations`, `Affected Rule Keys`, `Regression Floor`, `Scope Evidence`. Always set `Change Classification` to `new-operation` and `Coverage Policy` to `full-contract`; do NOT reason about whether operations are new or existing. Cover all in-scope rules from the requirement document at full depth; treat the product requirement as the coverage baseline and use API contract evidence (fields/status/enum/boundary/format) to supplement scenario dimensions. Scope is limited to operations/rules the requirement document (or its referenced API contract) explicitly describes; do not expand to unrelated operations that the requirement does not mention. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.\n\nCoverage depth is full over the in-scope rules: fully cover every documented status, request/response field rule, requiredness, enum, boundary, format, auth and business state of each affected operation the requirement describes, but do not re-test unrelated operations the requirement does not mention. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT.\n\nBefore writing cases, build the mandatory machine-readable Coverage Matrix inside `testcase/md/README.md` itself. Its section heading line must be exactly `## Coverage Matrix` with no numeric prefix/suffix; never place the canonical Matrix only in a module file. Use this exact header: `| Rule Key | Priority | Source | Endpoint/Field | Dimension | Rule | Required Test Points | Case IDs | Status |`. Every data row must contain exactly 9 pipe-delimited cells and must never omit `Dimension`; use concise dimensions such as requirement, operation, response-status, requiredness, enum, boundary, format, business-state or error. Use only P0/P1/P2 and COVERED/PARTIAL/GAP/CONFLICT. Use stable `TP-<UPPERCASE-HYPHENATED-ID>` test points separated by semicolons.\n\nEach Rule Key must appear in exactly one Matrix row. Preserve each AC/REQ/BR Rule Key as one row; if one product rule spans multiple dimensions, use a concise composite Dimension in that single row instead of duplicating the key. Derive OpenAPI Rule Keys exactly as the deterministic analyzer does: operation token is `<HTTP-METHOD>-<PATH>` with braces removed and every non-alphanumeric run replaced by a hyphen, uppercase (for example POST `/api/resource-notes` → `POST-API-RESOURCE-NOTES`); response statuses use `API-<OPERATION>-RESPONSE-STATUS`; body/parameter fields use `API-<OPERATION>-<FIELD>-REQUIRED|ENUM|MIN-LENGTH|MAX-LENGTH|MINIMUM|MAXIMUM|PATTERN|FORMAT`. Do not invent aliases such as API-CREATE-FIELDS when a deterministic key applies.\n\nCoverage priority is strict inside the declared scope: P0 product requirements/task hard constraints always remain in scope; P1 exhaustively supplements documented operations, fields, business rules, statuses and errors only for Affected Operations; P2 adds bounded protocol robustness only when it is relevant to the change and does not invent product behavior. Coverage percentages describe the declared affected scope, never whole-API completeness unless every operation is explicitly listed. Conflicts or undefined expectations must stay visible as GAP/CONFLICT with precise source pointers, never guessed.\n\nFor uniqueness/lifecycle rules cover absent, active-existing, deleted-existing, create-delete-recreate, restore-then-recreate and documented scope/case-normalization states. For every enum cover every valid value plus bounded invalid equivalence classes (unknown, case variant, whitespace, empty, null/missing and wrong types as applicable). For every length/number rule cover min-1, min, nominal, max and max+1. For format rules cover each allowed class separately plus a valid mixed value, and representative forbidden classes including uppercase, internal/leading/trailing whitespace, tab/newline, unsupported punctuation, slash, emoji or control characters when the source contract supports that expectation.\n\nMandatory module index: include a `## Module Index` table in README that lists every planned module as a canonical relative link of the exact form `[label](./<stem>.md)` plus a `testcase/md/<stem>.md` path cell, so a downstream deterministic manifest can parse the module list. Group by stable business resource/domain, not by CRUD operation: one resource's list/detail/create/update/delete cases belong in one module such as `resource_notes`; split only when a single module would exceed the per-child 16K output protocol, keep the total module count at the smallest safe value, and never exceed 8 modules. Name each module file with a stable lowercase business stem such as `health` or `resource_notes`. Pure hexadecimal/hash-like opaque stems such as `a401606` or `deadbeef` are forbidden. Do not use priority-only stems `p0`, `p1` or `p2`; Priority belongs only in the Coverage Matrix and never defines module files. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. The relative link target MUST equal the on-disk filename stem the sharded writer will create. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.\n\nBefore finalizing README, calculate the predicted collected-item count as `sum(max(1, number of variant Test Points in each Case))`. If the task declares an item budget, the prediction must not exceed it. Reduce excess only by removing duplicate execution and converting same-request checkpoints to assertions; never drop required rules, boundaries, enums, operation-specific inputs, or business states. Record the prediction in README. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text."
143
+ "subtask_prompt": "This is a required file-generation node. After reading the bounded inputs, immediately use write tools to create testcase/md/README.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Write ONLY testcase/md/README.md in this node; module case cards are written by downstream sharded nodes.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, case bodies, or source text into assistant chat. README holds only Scope+Matrix+module index; never inline full case bodies. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nThe first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the README has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.\n\nRead the upstream environment report. Generate the Markdown-first backend test README under testcase/md/README.md.\n\nWrite human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.\n\nCreate testcase/md/README.md as the concise entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in README.\n\nBefore the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in README using exactly `| Field | Value |`, immediately followed by the separator row `|---|---|`, and these six unique rows: `Change Classification`, `Coverage Policy`, `Affected Operations`, `Affected Rule Keys`, `Regression Floor`, `Scope Evidence`. Always set `Change Classification` to `new-operation` and `Coverage Policy` to `full-contract`; do NOT reason about whether operations are new or existing. Cover all in-scope rules from the requirement document at full depth; treat the product requirement as the coverage baseline and use API contract evidence (fields/status/enum/boundary/format) to supplement scenario dimensions. Scope is limited to operations/rules the requirement document (or its referenced API contract) explicitly describes; do not expand to unrelated operations that the requirement does not mention. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.\n\nCoverage depth is full over the in-scope rules: fully cover every documented status, request/response field rule, requiredness, enum, boundary, format, auth and business state of each affected operation the requirement describes, but do not re-test unrelated operations the requirement does not mention. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT.\n\nBefore writing cases, build the mandatory machine-readable Coverage Matrix inside `testcase/md/README.md` itself. Its section heading line must be exactly `## Coverage Matrix` with no numeric prefix/suffix; never place the canonical Matrix only in a module file. Use this exact header: `| Rule Key | Priority | Source | Endpoint/Field | Dimension | Rule | Required Test Points | Case IDs | Status |`. Every data row must contain exactly 9 pipe-delimited cells and must never omit `Dimension`; use concise dimensions such as requirement, operation, response-status, requiredness, enum, boundary, format, business-state or error. Use only P0/P1/P2 and COVERED/PARTIAL/GAP/CONFLICT. Use stable `TP-<UPPERCASE-HYPHENATED-ID>` test points separated by semicolons.\n\nEach Rule Key must appear in exactly one Matrix row. Preserve each AC/REQ/BR Rule Key as one row; if one product rule spans multiple dimensions, use a concise composite Dimension in that single row instead of duplicating the key. Derive OpenAPI Rule Keys exactly as the deterministic analyzer does: operation token is `<HTTP-METHOD>-<PATH>` with braces removed and every non-alphanumeric run replaced by a hyphen, uppercase (for example POST `/api/resource-notes` → `POST-API-RESOURCE-NOTES`); response statuses use `API-<OPERATION>-RESPONSE-STATUS`; body/parameter fields use `API-<OPERATION>-<FIELD>-REQUIRED|ENUM|MIN-LENGTH|MAX-LENGTH|MINIMUM|MAXIMUM|PATTERN|FORMAT`. Do not invent aliases such as API-CREATE-FIELDS when a deterministic key applies.\n\nCoverage priority is strict inside the declared scope: P0 product requirements/task hard constraints always remain in scope; P1 exhaustively supplements documented operations, fields, business rules, statuses and errors only for Affected Operations; P2 adds bounded protocol robustness only when it is relevant to the change and does not invent product behavior. Coverage percentages describe the declared affected scope, never whole-API completeness unless every operation is explicitly listed. Conflicts or undefined expectations must stay visible as GAP/CONFLICT with precise source pointers, never guessed.\n\nFor uniqueness/lifecycle rules cover absent, active-existing, deleted-existing, create-delete-recreate, restore-then-recreate and documented scope/case-normalization states. For every enum cover every valid value plus bounded invalid equivalence classes (unknown, case variant, whitespace, empty, null/missing and wrong types as applicable). For every length/number rule cover min-1, min, nominal, max and max+1. For format rules cover each allowed class separately plus a valid mixed value, and representative forbidden classes including uppercase, internal/leading/trailing whitespace, tab/newline, unsupported punctuation, slash, emoji or control characters when the source contract supports that expectation.\n\nMandatory module index: include a `## Module Index` table in README that lists every planned module as a canonical relative link of the exact form `[label](./<stem>.md)` plus a `testcase/md/<stem>.md` path cell, so a downstream deterministic manifest can parse the module list. Group by stable business resource/domain, not by CRUD operation: one resource's list/detail/create/update/delete cases belong in one module such as `resource_notes`; split only when a single module would exceed the per-child 16K output budget, and keep the total module count at the smallest safe value. Name each module file with a stable lowercase business stem such as `health` or `resource_notes`. Do not use priority-only stems `p0`, `p1` or `p2`; Priority belongs only in the Coverage Matrix and never defines module files. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. The relative link target MUST equal the on-disk filename stem the sharded writer will create. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.\n\nBefore finalizing README, calculate the predicted collected-item count as `sum(max(1, number of variant Test Points in each Case))`. If the task declares an item budget, the prediction must not exceed it. Reduce excess only by removing duplicate execution and converting same-request checkpoints to assertions; never drop required rules, boundaries, enums, operation-specific inputs, or business states. Record the prediction in README. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text."
144
144
  },
145
145
  {
146
146
  "id": "materialize-backend-md-module-manifest-shell",
@@ -164,7 +164,7 @@
164
164
  "subtask_prompt": "Parse testcase/md/README.md and emit exactly one trailing JSON line {modules:[{stem}]} listing every trusted module stem (table-row testcase/md/<stem>.md mentions and canonical [label](./<stem>.md) relative links only). No file writes.",
165
165
  "shell": {
166
166
  "commands": [
167
- "node -e \"eval(Buffer.from('Y29uc3QgZnM9cmVxdWlyZSgnZnMnKTsKY29uc3QgcmVhZG1lPWZzLmV4aXN0c1N5bmMoJ3Rlc3RjYXNlL21kL1JFQURNRS5tZCcpP2ZzLnJlYWRGaWxlU3luYygndGVzdGNhc2UvbWQvUkVBRE1FLm1kJywndXRmOCcpOicnOwpjb25zdCBub3JtPXM9PlN0cmluZyhzKS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL1teYS16MC05XSsvZywnXycpLnJlcGxhY2UoL15fK3xfKyQvZywnJykucmVwbGFjZSgvXysvZywnXycpOwpjb25zdCBidD1TdHJpbmcuZnJvbUNoYXJDb2RlKDk2KTsKY29uc3Qgc3RyaXBCYWNrdGlja3M9cz0+cy5zcGxpdChidCkuam9pbignJyk7CmNvbnN0IGludmFsaWRSZWFzb249cmF3PT57Y29uc3Qgc3Q9bm9ybShyYXcpO2lmKC9ecFswLTJdJC8udGVzdChzdCkpcmV0dXJuICdwcmlvcml0eS1vbmx5LW1vZHVsZS1zdGVtJztpZigvXlthLWZdW2EtZjAtOV17Niw2M30kLy50ZXN0KHN0KSlyZXR1cm4gJ29wYXF1ZS1oYXNoLW1vZHVsZS1zdGVtJztpZihzdD09PSdyZWFkbWUnKXJldHVybiAncmVzZXJ2ZWQtbW9kdWxlLXN0ZW0nO2lmKCEvXlthLXpdW2EtejAtOV9dKiQvLnRlc3Qoc3QpKXJldHVybiAnaW52YWxpZC1zeW50YXgnO2lmKC9eKD86YmV8dHB8YWN8cmVxfGJyKVtfLV0vaS50ZXN0KHN0KSlyZXR1cm4gJ2Nhc2UtbGlrZS1tb2R1bGUtc3RlbSc7cmV0dXJuIG51bGw7fTsKY29uc3QgdmFsaWQ9cmF3PT5pbnZhbGlkUmVhc29uKHJhdyk9PT1udWxsOwpjb25zdCByeE1kUGF0aD0vdGVzdGNhc2VcL21kXC8oW0EtWmEtejAtOV8uLV0rKVwubWQvZzsKY29uc3QgcnhUYWJsZVJvdz0vXHxccyooW0EtWmEtejAtOV8uLV0rKVxzKlx8XHMqdGVzdGNhc2VcL3Rlc3RfL2c7CmNvbnN0IHJ4UmVsTGluaz0vXFtbXlxdXStcXVwoXC5cLyhbQS1aYS16MC05Xy4tXSspXC5tZFwpL2c7CmNvbnN0IGFsbExpbmVzPXJlYWRtZS5yZXBsYWNlKC9cclxuL2csJ1xuJykucmVwbGFjZSgvXHIvZywnXG4nKS5zcGxpdCgnXG4nKTsKY29uc3QgaGVhZGluZ3M9W107Zm9yKGxldCBpPTA7aTxhbGxMaW5lcy5sZW5ndGg7aSsrKXtpZihhbGxMaW5lc1tpXS50cmltKCk9PT0nIyMgTW9kdWxlIEluZGV4JyloZWFkaW5ncy5wdXNoKGkpO30KaWYoaGVhZGluZ3MubGVuZ3RoIT09MSl7cHJvY2Vzcy5zdGRlcnIud3JpdGUoKGhlYWRpbmdzLmxlbmd0aD09PTA/J21pc3NpbmctbW9kdWxlLWluZGV4JzonZHVwbGljYXRlLW1vZHVsZS1pbmRleCcpKyc7IHJlcXVpcmUgZXhhY3RseSBvbmUgZXhhY3QgIyMgTW9kdWxlIEluZGV4IHNlY3Rpb25cbicpO3Byb2Nlc3MuZXhpdCgyKTt9CmNvbnN0IHN0YXJ0PWhlYWRpbmdzWzBdKzE7bGV0IGVuZD1hbGxMaW5lcy5sZW5ndGg7Zm9yKGxldCBpPXN0YXJ0O2k8YWxsTGluZXMubGVuZ3RoO2krKyl7aWYoL14jI1xzK1xTLy50ZXN0KGFsbExpbmVzW2ldLnRyaW0oKSkpe2VuZD1pO2JyZWFrO319CmNvbnN0IHNlY3Rpb249YWxsTGluZXMuc2xpY2Uoc3RhcnQsZW5kKS5qb2luKCdcbicpOwpjb25zdCByYXc9W107CmNvbnN0IGxpbmVzPXNlY3Rpb24uc3BsaXQoJ1xuJykuZmlsdGVyKGw9PmwuaW5jbHVkZXMoJ3wnKSk7CmZvcihjb25zdCBsaW5lIG9mIGxpbmVzKXsKICBjb25zdCBiYXJlPXN0cmlwQmFja3RpY2tzKGxpbmUpOwogIGZvcihjb25zdCBtIG9mIGJhcmUubWF0Y2hBbGwocnhNZFBhdGgpKXtyYXcucHVzaChtWzFdKTt9CiAgZm9yKGNvbnN0IG0gb2YgYmFyZS5tYXRjaEFsbChyeFRhYmxlUm93KSl7aWYodmFsaWQobVsxXSl8fGludmFsaWRSZWFzb24obVsxXSkhPT0naW52YWxpZC1zeW50YXgnKXJhdy5wdXNoKG1bMV0pO30KfQpmb3IoY29uc3QgbSBvZiBzZWN0aW9uLm1hdGNoQWxsKHJ4UmVsTGluaykpe3Jhdy5wdXNoKG1bMV0pO30KY29uc3QgaW52YWxpZD1bXTtmb3IoY29uc3QgciBvZiByYXcpe2NvbnN0IHJlYXNvbj1pbnZhbGlkUmVhc29uKHIpO2lmKHJlYXNvbilpbnZhbGlkLnB1c2goe3N0ZW06bm9ybShyKSxyZWFzb259KTt9CmlmKGludmFsaWQubGVuZ3RoKXtmb3IoY29uc3QgaXRlbSBvZiBpbnZhbGlkKXByb2Nlc3Muc3RkZXJyLndyaXRlKGl0ZW0ucmVhc29uKyc6ICcraXRlbS5zdGVtKyc7IHVzZSBhIHN0YWJsZSBidXNpbmVzcyByZXNvdXJjZS9kb21haW4gc3RlbVxuJyk7cHJvY2Vzcy5leGl0KDIpO30KY29uc3Qgc2Vlbj1uZXcgU2V0KCk7Y29uc3QgbW9kdWxlcz1bXTsKZm9yKGNvbnN0IHIgb2YgcmF3KXtjb25zdCBzdD1ub3JtKHIpO2lmKHZhbGlkKHIpJiYhc2Vlbi5oYXMoc3QpKXtzZWVuLmFkZChzdCk7bW9kdWxlcy5wdXNoKHtzdGVtOnN0fSk7fX0KaWYobW9kdWxlcy5sZW5ndGg+OCl7cHJvY2Vzcy5zdGRlcnIud3JpdGUoJ2V4Y2Vzc2l2ZS1tb2R1bGUtY291bnQ6ICcrbW9kdWxlcy5sZW5ndGgrJyA+IDg7IG1lcmdlIGJ5IHRoZSBzbWFsbGVzdCBzdGFibGUgYnVzaW5lc3MgcmVzb3VyY2UvZG9tYWluIHNldFxuJyk7cHJvY2Vzcy5leGl0KDIpO30KcHJvY2Vzcy5zdGRvdXQud3JpdGUoSlNPTi5zdHJpbmdpZnkoe21vZHVsZXN9KSk7Cg==','base64').toString('utf8'))\""
167
+ "node -e \"eval(Buffer.from('Y29uc3QgZnM9cmVxdWlyZSgnZnMnKTsKY29uc3QgcmVhZG1lPWZzLmV4aXN0c1N5bmMoJ3Rlc3RjYXNlL21kL1JFQURNRS5tZCcpP2ZzLnJlYWRGaWxlU3luYygndGVzdGNhc2UvbWQvUkVBRE1FLm1kJywndXRmOCcpOicnOwpjb25zdCBub3JtPXM9PlN0cmluZyhzKS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL1teYS16MC05XSsvZywnXycpLnJlcGxhY2UoL15fK3xfKyQvZywnJykucmVwbGFjZSgvXysvZywnXycpOwpjb25zdCBidD1TdHJpbmcuZnJvbUNoYXJDb2RlKDk2KTsKY29uc3Qgc3RyaXBCYWNrdGlja3M9cz0+cy5zcGxpdChidCkuam9pbignJyk7CmNvbnN0IGludmFsaWRSZWFzb249cmF3PT57Y29uc3Qgc3Q9bm9ybShyYXcpO2lmKC9ecFswLTJdJC8udGVzdChzdCkpcmV0dXJuICdwcmlvcml0eS1vbmx5LW1vZHVsZS1zdGVtJztpZihzdD09PSdyZWFkbWUnKXJldHVybiAncmVzZXJ2ZWQtbW9kdWxlLXN0ZW0nO2lmKCEvXlthLXpdW2EtejAtOV9dKiQvLnRlc3Qoc3QpKXJldHVybiAnaW52YWxpZC1zeW50YXgnO2lmKC9eKD86YmV8dHB8YWN8cmVxfGJyKVtfLV0vaS50ZXN0KHN0KSlyZXR1cm4gJ2Nhc2UtbGlrZS1tb2R1bGUtc3RlbSc7cmV0dXJuIG51bGw7fTsKY29uc3QgdmFsaWQ9cmF3PT5pbnZhbGlkUmVhc29uKHJhdyk9PT1udWxsOwpjb25zdCByeE1kUGF0aD0vdGVzdGNhc2VcL21kXC8oW0EtWmEtejAtOV8uLV0rKVwubWQvZzsKY29uc3QgcnhUYWJsZVJvdz0vXHxccyooW0EtWmEtejAtOV8uLV0rKVxzKlx8XHMqdGVzdGNhc2VcL3Rlc3RfL2c7CmNvbnN0IHJ4UmVsTGluaz0vXFtbXlxdXStcXVwoXC5cLyhbQS1aYS16MC05Xy4tXSspXC5tZFwpL2c7CmNvbnN0IHJhdz1bXTsKY29uc3QgbGluZXM9cmVhZG1lLnJlcGxhY2UoL1xyXG4vZywnXG4nKS5yZXBsYWNlKC9cci9nLCdcbicpLnNwbGl0KCdcbicpLmZpbHRlcihsPT5sLmluY2x1ZGVzKCd8JykpOwpmb3IoY29uc3QgbGluZSBvZiBsaW5lcyl7CiAgY29uc3QgYmFyZT1zdHJpcEJhY2t0aWNrcyhsaW5lKTsKICBmb3IoY29uc3QgbSBvZiBiYXJlLm1hdGNoQWxsKHJ4TWRQYXRoKSl7cmF3LnB1c2gobVsxXSk7fQogIGZvcihjb25zdCBtIG9mIGJhcmUubWF0Y2hBbGwocnhUYWJsZVJvdykpe2lmKHZhbGlkKG1bMV0pfHxpbnZhbGlkUmVhc29uKG1bMV0pPT09J3ByaW9yaXR5LW9ubHktbW9kdWxlLXN0ZW0nKXJhdy5wdXNoKG1bMV0pO30KfQpmb3IoY29uc3QgbSBvZiByZWFkbWUubWF0Y2hBbGwocnhSZWxMaW5rKSl7cmF3LnB1c2gobVsxXSk7fQpjb25zdCBpbnZhbGlkPVtdO2Zvcihjb25zdCByIG9mIHJhdyl7Y29uc3QgcmVhc29uPWludmFsaWRSZWFzb24ocik7aWYocmVhc29uKWludmFsaWQucHVzaCh7c3RlbTpub3JtKHIpLHJlYXNvbn0pO30KaWYoaW52YWxpZC5sZW5ndGgpe2Zvcihjb25zdCBpdGVtIG9mIGludmFsaWQpcHJvY2Vzcy5zdGRlcnIud3JpdGUoaXRlbS5yZWFzb24rJzogJytpdGVtLnN0ZW0rJzsgdXNlIGEgc3RhYmxlIGJ1c2luZXNzIHJlc291cmNlL2RvbWFpbiBzdGVtXG4nKTtwcm9jZXNzLmV4aXQoMik7fQpjb25zdCBzZWVuPW5ldyBTZXQoKTtjb25zdCBtb2R1bGVzPVtdOwpmb3IoY29uc3QgciBvZiByYXcpe2NvbnN0IHN0PW5vcm0ocik7aWYodmFsaWQocikmJiFzZWVuLmhhcyhzdCkpe3NlZW4uYWRkKHN0KTttb2R1bGVzLnB1c2goe3N0ZW06c3R9KTt9fQpwcm9jZXNzLnN0ZG91dC53cml0ZShKU09OLnN0cmluZ2lmeSh7bW9kdWxlc30pKTsK','base64').toString('utf8'))\""
168
168
  ],
169
169
  "cwd": ".",
170
170
  "timeoutMs": 60000
@@ -195,13 +195,13 @@
195
195
  "workflowNodeId": "generate-backend-md-cases-map",
196
196
  "itemsFrom": "$.nodes['materialize-backend-md-module-manifest-shell'].output.modules",
197
197
  "itemName": "item",
198
- "maxItems": 8,
199
- "maxExpandedNodes": 8,
198
+ "maxItems": 64,
199
+ "maxExpandedNodes": 64,
200
200
  "childIdPrefix": "generate-backend-md-case",
201
201
  "tokenBudget": {
202
- "maxTotalTokens": 3000000
202
+ "maxTokensPerCase": 16384,
203
+ "maxTotalTokens": 600000
203
204
  },
204
- "failOnTokenBudgetExhaustion": true,
205
205
  "childTask": {
206
206
  "executor": "pi",
207
207
  "role": "implementer",
@@ -238,7 +238,7 @@
238
238
  ]
239
239
  },
240
240
  "outputContract": "Write exactly one Chinese module Markdown case-card file testcase/md/<stem>.md with BE-<MODULE>-<NNN> cases and the seven required h3 sections; keep machine IDs/literals exact and do not execute pytest or modify production code/config or the README.",
241
- "subtaskPromptTemplate": "This is a required file-generation node for exactly one Markdown module. After reading testcase/md/README.md (Coverage Scope + Coverage Matrix + module index) and the bounded references, immediately use write tools to create the single file testcase/md/{{item.stem}}.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Do not modify README.md or any other module file.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, other modules' case bodies, or source text into assistant chat. Each write/edit tool call touches at most one file (this module). Compact tables/lists are required; omitting required sections or in-scope variants is forbidden. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nThe first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the module file has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.\n\nWrite human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.\n\nWrite the module {{item.stem}} as readable case cards covering every in-scope rule/Test Point the README Coverage Matrix assigns to this module. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. `<NNN>` is exactly three zero-padded digits (`001`, `002`, ...), never two digits (`01`), a bare number, or an alphabetic suffix such as `011A`. Every case must include `### 覆盖规则`, `### 测试点`, `### 场景类型`, `### 前置条件`, `### 操作步骤`, `### 预期结果`, and `### 自动化映射` Do not group cases under \"## 测试类 ...\" (or any h2 grouping) headings that force Cases down to h3; each Case must be a direct h2 (`##`), and its seven sections must be h3 (`###`) children of that Case. If you need to convey a pytest class, state it inside the Case's `### 自动化映射` instead. Forbidden: `## 测试类 X` then `### BE-PD-001` and `### 覆盖规则` at the same h3 level. Required: `## BE-PD-001` then `### 覆盖规则`.; `覆盖规则` and `测试点` must reference exact Matrix Rule Keys/Test Points. Add `测试目的`, `验收标准`, `需求依据`, and `测试数据` for readable evidence. The `验收标准` section must list the exact applicable `AC-...` IDs, and every explicit task AC must appear in at least one Case. Every automatable case explicitly names its target pytest script and exactly one primary symbol so traceability scans only that script/symbol. Evidence-only meta cases that exist solely for non-executable assertion/cross-cutting process evidence may declare `脚本:无` and `primary symbol:无` with empty `变体测试点`, and must not invent a business pytest item.\n\nName this module file with the stable lowercase business stem `{{item.stem}}` (filename `testcase/md/{{item.stem}}.md`). Priority-only stems `p0`, `p1` and `p2` are forbidden and must never produce `p0.md` or `test_p0.py`. Pure hexadecimal/hash-like opaque stems such as `a401606` and `deadbeef` are also forbidden. Do not use Case-ID-like module filenames. For every automatable case, `自动化映射` must name exactly `testcase/test_{{item.stem}}.py`, where the module stem is this Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `health` → `testcase/test_health.py`; `resource_notes` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.\n\nEvery Case must keep at least one numbered executable line under `### 操作步骤`; a compact variant/result table may follow but must not replace the numbered action anchor. Keep numbered/bulleted independently assertable results under `### 预期结果`. The exact `### 操作步骤` and `### 预期结果` headings must remain present for every Case, including compact/table-based Cases; never compress later Cases by dropping required headings. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.\n\nIn every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`, plus a deterministic payload contract. For operations without a request body write `Payload Contract: none`. Otherwise write `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum` (write `none` when there is no enum); nested fields use dot paths such as `approver.name`. Values must come only from bound API/DTO evidence, never guesses. Each Test Point from `### 测试点` must appear in exactly one binding list, and every Test Point named in any binding list must also be declared in that Case's `### 测试点`; write `无` for an empty list. A variant Test Point is atomic: one exact endpoint/input/precondition/outcome row equals one exact pytest item and one exact TP ID. If a parameter table has five rows, declare five distinct variant TP IDs in Markdown; never declare one family TP and append row suffixes only in pytest. Classify as `variant` only when endpoint, request input, precondition business state, or expected outcome genuinely changes and therefore needs an independent pytest parameter item. Classify CRUD checkpoints, status/body/header/schema assertions and multiple checks over the same response/journey as `assertion`; classify shared HTTP logging/redaction/truncation evidence as `cross-cutting`. Never create a Test Point merely to parameterize a checkpoint. Every non-cross-cutting TP ID is owned by exactly one Case; when the same response/schema/error assertion is needed in different Cases, use distinct Case-specific TP IDs instead of reusing one assertion TP across Cases. Keep the script path identical to the module one-to-one path and declare exactly one primary symbol named with the canonical Case prefix, for example `BE-RN-003` → `test_BE_RN_003_<description>`; non-Case-prefixed primary symbols are forbidden because parameterized item association must remain deterministic. For evidence-only meta Cases with no executable business journey, write `脚本:无` and `primary symbol:无`, keep `变体测试点:无`, and place process evidence only in assertion/cross-cutting lists. For redaction scenarios, list sensitive header/field key names only. Never write any header-name-and-value pair, credential placeholder, fake token, anti-example, or other secret-shaped literal in Markdown; state only that a test-only value is supplied at runtime and omitted. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text."
241
+ "subtaskPromptTemplate": "This is a required file-generation node for exactly one Markdown module. After reading testcase/md/README.md (Coverage Scope + Coverage Matrix + module index) and the bounded references, immediately use write tools to create the single file testcase/md/{{item.stem}}.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Do not modify README.md or any other module file.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, other modules' case bodies, or source text into assistant chat. Each write/edit tool call touches at most one file (this module). Compact tables/lists are required; omitting required sections or in-scope variants is forbidden. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nThe first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the module file has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.\n\nWrite human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.\n\nWrite the module {{item.stem}} as readable case cards covering every in-scope rule/Test Point the README Coverage Matrix assigns to this module. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. `<NNN>` is exactly three zero-padded digits (`001`, `002`, ...), never two digits (`01`), a bare number, or an alphabetic suffix such as `011A`. Every case must include `### 覆盖规则`, `### 测试点`, `### 场景类型`, `### 前置条件`, `### 操作步骤`, `### 预期结果`, and `### 自动化映射` Do not group cases under \"## 测试类 ...\" (or any h2 grouping) headings that force Cases down to h3; each Case must be a direct h2 (`##`), and its seven sections must be h3 (`###`) children of that Case. If you need to convey a pytest class, state it inside the Case's `### 自动化映射` instead. Forbidden: `## 测试类 X` then `### BE-PD-001` and `### 覆盖规则` at the same h3 level. Required: `## BE-PD-001` then `### 覆盖规则`.; `覆盖规则` and `测试点` must reference exact Matrix Rule Keys/Test Points. Add `测试目的`, `验收标准`, `需求依据`, and `测试数据` for readable evidence. The `验收标准` section must list the exact applicable `AC-...` IDs, and every explicit task AC must appear in at least one Case. Every automatable case explicitly names its target pytest script and exactly one primary symbol so traceability scans only that script/symbol.\n\nName this module file with the stable lowercase business stem `{{item.stem}}` (filename `testcase/md/{{item.stem}}.md`). Priority-only stems `p0`, `p1` and `p2` are forbidden and must never produce `p0.md` or `test_p0.py`. Do not use Case-ID-like module filenames. For every automatable case, `自动化映射` must name exactly `testcase/test_{{item.stem}}.py`, where the module stem is this Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `health` → `testcase/test_health.py`; `resource_notes` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.\n\nEvery Case must keep at least one numbered executable line under `### 操作步骤`; a compact variant/result table may follow but must not replace the numbered action anchor. Keep numbered/bulleted independently assertable results under `### 预期结果`. The exact `### 操作步骤` and `### 预期结果` headings must remain present for every Case, including compact/table-based Cases; never compress later Cases by dropping required headings. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.\n\nIn every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`. Each Test Point from `### 测试点` must appear in exactly one binding list, and every Test Point named in any binding list must also be declared in that Case's `### 测试点`; write `无` for an empty list. A variant Test Point is atomic: one exact endpoint/input/precondition/outcome row equals one exact pytest item and one exact TP ID. If a parameter table has five rows, declare five distinct variant TP IDs in Markdown; never declare one family TP and append row suffixes only in pytest. Classify as `variant` only when endpoint, request input, precondition business state, or expected outcome genuinely changes and therefore needs an independent pytest parameter item. Classify CRUD checkpoints, status/body/header/schema assertions and multiple checks over the same response/journey as `assertion`; classify shared HTTP logging/redaction/truncation evidence as `cross-cutting`. Never create a Test Point merely to parameterize a checkpoint. Every non-cross-cutting TP ID is owned by exactly one Case; when the same response/schema/error assertion is needed in different Cases, use distinct Case-specific TP IDs instead of reusing one assertion TP across Cases. Keep the script path identical to the module one-to-one path and declare exactly one primary symbol named with the canonical Case prefix, for example `BE-RN-003` → `test_BE_RN_003_<description>`; non-Case-prefixed primary symbols are forbidden because parameterized item association must remain deterministic. For redaction scenarios, list sensitive header/field key names only. Never write any header-name-and-value pair, credential placeholder, fake token, anti-example, or other secret-shaped literal in Markdown; state only that a test-only value is supplied at runtime and omitted. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text."
242
242
  }
243
243
  }
244
244
  },
@@ -247,17 +247,14 @@
247
247
  "depends_on": [
248
248
  "generate-backend-md-cases-map"
249
249
  ],
250
- "role": "implementer",
251
- "executor": "pi",
252
- "toolProfile": "write",
253
- "complexity": "MED",
250
+ "role": "reviewer",
251
+ "executor": "static",
252
+ "complexity": "LOW",
254
253
  "writePolicy": "exclusive",
255
254
  "writeSet": [
256
255
  "testcase/md/**"
257
256
  ],
258
257
  "allowedPaths": [
259
- "testcase/**",
260
- "docs/test-reports/**",
261
258
  "testcase/md/**"
262
259
  ],
263
260
  "forbiddenPaths": [
@@ -265,11 +262,11 @@
265
262
  ".harness/dag-runs/**",
266
263
  "artifacts/**"
267
264
  ],
268
- "writerOutcomePolicy": {
269
- "type": "implementation-outcome-v1"
270
- },
271
- "outputContract": "First non-empty line is IMPLEMENTATION_OUTCOME: changed|already-satisfied|blocked. Perform exactly one bounded incremental synchronization of testcase/md/** against all bound source references; preserve valid Cases and report a concise summary.",
272
- "subtask_prompt": "Perform one gap-targeted synchronization, not a full-suite rewrite or stylistic review. Start from explicit bound source IDs/error codes/DTO fields/normative quoted rules and the README Matrix; open and edit only modules that own a missing or conflicting rule. Preserve unrelated valid modules byte-for-byte and avoid optional wording cleanup.\n\nOutput budget protocol: never dump full Matrix/case bodies into assistant chat. Inspect README first, build a concise target list, then read/write only target modules one file per tool call. Do not traverse every module when the Matrix and source token inventory show no gap; return `already-satisfied`. When adding omitted in-scope cases, keep every required section. Do not bulk-delete in-scope cases to save tokens.\n\nFor every variant Test Point, ensure the Markdown scenario intent is machine-checkable with an exact transport target: `场景意图: <TP-ID>; operation=<METHOD /path>; target=<body.field|query.field|path.field|header.field|request>; intent=<empty|missing|null|min-1|min|max|max+1|pattern-invalid|enum-invalid|wrong-type|nominal-operation|custom-literal:V>; bound=<n optional>; example=<optional>; expectedCode=<optional>`. Never use vague targets such as field=resource/health. Keep pytest params aligned to the exact target.\n\nTreat the requirement document as the coverage baseline; scope is limited to operations/rules it (or its referenced API contract) describes, and API contract evidence supplements scenario dimensions. For every in-scope operation, check applicable lifecycle/uniqueness states (including deleted-existing when in scope), valid enum values, bounded invalid classes, min-1/min/nominal/max/max+1, allowed/forbidden format classes, required/null/missing/wrong-type semantics, status/error codes, auth and state transitions. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT. Directly add in-scope omissions; reject scope expansion to operations absent from the requirement document; undefined impact remains GAP/CONFLICT rather than invented behavior.\n\nCheck AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Require the exact `## Coverage Scope` Field/Value table with the `|---|---|` separator row, a valid classification-policy pair, non-empty Affected Operations/Rule Keys/Scope Evidence, and the classification-specific Regression Floor. Require the exact unnumbered `## Coverage Matrix` heading in `testcase/md/README.md`, exact headers, exactly 9 cells in every data row (including a non-empty Dimension), deterministic OpenAPI Rule Keys for every in-scope affected operation, exactly one Matrix row per Rule Key (merge multi-dimension product rows), and bidirectional Matrix Rule/Test Point ↔ Case bindings. Never describe affected-scope coverage as whole-API completeness. Every explicit AC ID must appear in at least one Case `验收标准`; every explicit in-scope AC/REQ/BR Rule Key cited by a Case must have exactly one Coverage Matrix row, and no Case may cite a source Rule Key omitted from the Matrix. Every Matrix Case ID must share at least one of that row's Required Test Points and the Case must cite that Rule Key. Perform an explicit execution-redundancy review: merge checkpoint-only parameter rows, repeated default/read-back assertions, DELETE status/body/follow-up-read checks, response schema/Content-Type checks, PUT full-update/timestamp checks, repeated list setup and identical null/empty inputs when endpoint, input partition, precondition state and expected outcome are the same. Preserve separate POST/PUT, boundary, enum, wrong-type, role/tenant and distinct business-state variants. Directly repair malformed headings/rows/keys and binding modes rather than merely commenting on them. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, a `### 操作步骤` section that contains only a table without any numbered executable line, vague results such as ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), dropped exact `### 操作步骤`/`### 预期结果` headings, and missing or drifted script/function mapping where it can be derived.\n\nCorrect testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (`BE-RESOURCE_NOTES-01` → `BE-RESOURCE-NOTES-001`; `BE-RN-011A` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each automatable case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol (evidence-only meta cases may keep `脚本/primary symbol=无` with empty variants), assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, ensure every binding-list Test Point is also present in that Case's `### 测试点`, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case `验收标准`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as `sum(max(1, variant count per Case))`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.\n\nThis is the single Markdown incremental synchronization round. Read every authoritative reference index entry whose role hints include acceptance-criteria, api-contract, data-contract or business-rule; do not rely on the derived PRD as a complete inventory. Preserve every explicit AC/REQ/BR ID, every documented HTTP/business error code, every DTO/JSON field, enum value, boundary, format, nested shape, transaction/state/idempotency/uniqueness/auth/tenant/cross-field rule. For each natural-language normative business rule preserved as required scope, include its exact source sentence without paraphrase together with source path and line/heading anchor so the deterministic ledger can verify quote/hash provenance. Ensure every Case declares exactly `Payload Contract: none` or the three labels `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; never infer missing keys or enum values. Add only missing Matrix rows/Test Points/Cases/assertions or repair exact drift; do not rewrite already-valid unrelated modules. Work gap-targeted: inspect source anchors and affected modules first, leave unrelated valid modules byte-stable, and return `already-satisfied` without restating the full suite when no gap exists.\n\nFor affected API fields, use one valid nominal payload plus atomic required/missing/null/empty/wrong-type, every documented enum value plus bounded invalid classes, documented min-1/min/nominal/max/max+1, formats and nested object/array constraints. Do not generate a Cartesian product or invent undocumented constraints.\n\nBefore returning, verify that every explicit source AC/REQ/BR, error code and strong DTO field token appears in README or an applicable module Case. If a fact cannot be safely automated, retain it as GAP/CONFLICT with its exact source pointer instead of dropping it. Return already-satisfied only when no target file needs an incremental edit.\n\nRead only indexed source paths. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails."
265
+ "outputContract": "Deterministic advisory handoff that reserves testcase/md/** in the approved DAG write set for upstream map children; no model invocation or writes.",
266
+ "subtask_prompt": "Independently review generated Markdown cases against the task requirements and environment evidence. Treat the files as human-facing test documentation: require clear preconditions, executable steps and assertable expected results; improve names, purpose, metadata and automation mapping where useful while preserving exact machine IDs and technical literals.\n\nOutput budget protocol: default to local edit per file; never dump full Matrix/case bodies into assistant chat. Review order is README (Scope/Matrix) then one module file per turn. When adding omitted in-scope cases, write one file per tool call and keep every required section. Do not bulk-delete in-scope cases to save tokens.\n\nFor every variant Test Point, ensure the Markdown scenario intent is machine-checkable with an exact transport target: `场景意图: <TP-ID>; operation=<METHOD /path>; target=<body.field|query.field|path.field|header.field|request>; intent=<empty|missing|null|min-1|min|max|max+1|pattern-invalid|enum-invalid|wrong-type|nominal-operation|custom-literal:V>; bound=<n optional>; example=<optional>; expectedCode=<optional>`. Never use vague targets such as field=resource/health. Keep pytest params aligned to the exact target.\n\nTreat the requirement document as the coverage baseline; scope is limited to operations/rules it (or its referenced API contract) describes, and API contract evidence supplements scenario dimensions. For every in-scope operation, check applicable lifecycle/uniqueness states (including deleted-existing when in scope), valid enum values, bounded invalid classes, min-1/min/nominal/max/max+1, allowed/forbidden format classes, required/null/missing/wrong-type semantics, status/error codes, auth and state transitions. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT. Directly add in-scope omissions; reject scope expansion to operations absent from the requirement document; undefined impact remains GAP/CONFLICT rather than invented behavior.\n\nCheck AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Require the exact `## Coverage Scope` Field/Value table with the `|---|---|` separator row, a valid classification-policy pair, non-empty Affected Operations/Rule Keys/Scope Evidence, and the classification-specific Regression Floor. Require the exact unnumbered `## Coverage Matrix` heading in `testcase/md/README.md`, exact headers, exactly 9 cells in every data row (including a non-empty Dimension), deterministic OpenAPI Rule Keys for every in-scope affected operation, exactly one Matrix row per Rule Key (merge multi-dimension product rows), and bidirectional Matrix Rule/Test Point ↔ Case bindings. Never describe affected-scope coverage as whole-API completeness. Every explicit AC ID must appear in at least one Case `验收标准`; every explicit in-scope AC/REQ/BR Rule Key cited by a Case must have exactly one Coverage Matrix row, and no Case may cite a source Rule Key omitted from the Matrix. Every Matrix Case ID must share at least one of that row's Required Test Points and the Case must cite that Rule Key. Perform an explicit execution-redundancy review: merge checkpoint-only parameter rows, repeated default/read-back assertions, DELETE status/body/follow-up-read checks, response schema/Content-Type checks, PUT full-update/timestamp checks, repeated list setup and identical null/empty inputs when endpoint, input partition, precondition state and expected outcome are the same. Preserve separate POST/PUT, boundary, enum, wrong-type, role/tenant and distinct business-state variants. Directly repair malformed headings/rows/keys and binding modes rather than merely commenting on them. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, a `### 操作步骤` section that contains only a table without any numbered executable line, vague results such as ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), dropped exact `### 操作步骤`/`### 预期结果` headings, and missing or drifted script/function mapping where it can be derived.\n\nCorrect testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (`BE-RESOURCE_NOTES-01` → `BE-RESOURCE-NOTES-001`; `BE-RN-011A` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol, assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, ensure every binding-list Test Point is also present in that Case's `### 测试点`, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case `验收标准`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as `sum(max(1, variant count per Case))`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.\n\nRead only precise referenced source paths plus requirement sections needed for uncovered ACs. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
267
+ "static": {
268
+ "resultMarkdown": "Markdown module writers completed. Deterministic node 6 validation owns advisory structure/coverage findings; no model review or rewrite was invoked."
269
+ }
273
270
  },
274
271
  {
275
272
  "id": "validate-backend-md-cases-shell",
@@ -289,7 +286,7 @@
289
286
  ".harness/dag-runs/**",
290
287
  "artifacts/**"
291
288
  ],
292
- "outputContract": "Run-owned reports/backend-md-case-validation.md, reports/backend-test-case-coverage-analysis.md and contracts/backend-test-case-coverage-facts.json v4 with Coverage Scope plus PASS/FAIL/UNAVAILABLE advisory facts; downstream execution continues.",
289
+ "outputContract": "Run-owned reports/backend-md-case-validation.md, reports/backend-test-case-coverage-analysis.md and contracts/backend-test-case-coverage-facts.json v3 with Coverage Scope plus PASS/FAIL/UNAVAILABLE advisory facts; downstream execution continues.",
293
290
  "subtask_prompt": "Record advisory findings for Markdown structure and deterministically analyze the final README Coverage Scope and Coverage Matrix against final Case rule/test-point bindings. Validate the classification-policy pair, affected operations/rules, scope evidence and regression floor; require documented OpenAPI completeness only for declared affected operations, while all explicit AC/REQ/BR remain in scope. Detect missing in-scope product/API rules, enum values, invalid equivalence classes, boundaries, format classes, business lifecycle states, GAP/CONFLICT, bidirectional Matrix/Case drift, non-canonical Case IDs, unclassified Test Points, duplicate binding modes and non-cross-cutting Test Points bound by multiple Cases. Do not validate source-reference existence. Write human and machine evidence from the same facts. Keep quality findings advisory, but fail closed after writing the report when secret-shaped values are detected. Coverage FAIL stays advisory.",
294
291
  "shell": {
295
292
  "commands": [],
@@ -341,7 +338,7 @@
341
338
  "subtask_prompt": "Parse testcase/md/README.md and emit exactly one trailing JSON line {modules:[{stem}]} listing every trusted module stem (table-row testcase/md/<stem>.md mentions and canonical [label](./<stem>.md) relative links only). No file writes.",
342
339
  "shell": {
343
340
  "commands": [
344
- "node -e \"eval(Buffer.from('Y29uc3QgZnM9cmVxdWlyZSgnZnMnKTsKY29uc3QgcmVhZG1lPWZzLmV4aXN0c1N5bmMoJ3Rlc3RjYXNlL21kL1JFQURNRS5tZCcpP2ZzLnJlYWRGaWxlU3luYygndGVzdGNhc2UvbWQvUkVBRE1FLm1kJywndXRmOCcpOicnOwpjb25zdCBub3JtPXM9PlN0cmluZyhzKS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL1teYS16MC05XSsvZywnXycpLnJlcGxhY2UoL15fK3xfKyQvZywnJykucmVwbGFjZSgvXysvZywnXycpOwpjb25zdCBidD1TdHJpbmcuZnJvbUNoYXJDb2RlKDk2KTsKY29uc3Qgc3RyaXBCYWNrdGlja3M9cz0+cy5zcGxpdChidCkuam9pbignJyk7CmNvbnN0IGludmFsaWRSZWFzb249cmF3PT57Y29uc3Qgc3Q9bm9ybShyYXcpO2lmKC9ecFswLTJdJC8udGVzdChzdCkpcmV0dXJuICdwcmlvcml0eS1vbmx5LW1vZHVsZS1zdGVtJztpZigvXlthLWZdW2EtZjAtOV17Niw2M30kLy50ZXN0KHN0KSlyZXR1cm4gJ29wYXF1ZS1oYXNoLW1vZHVsZS1zdGVtJztpZihzdD09PSdyZWFkbWUnKXJldHVybiAncmVzZXJ2ZWQtbW9kdWxlLXN0ZW0nO2lmKCEvXlthLXpdW2EtejAtOV9dKiQvLnRlc3Qoc3QpKXJldHVybiAnaW52YWxpZC1zeW50YXgnO2lmKC9eKD86YmV8dHB8YWN8cmVxfGJyKVtfLV0vaS50ZXN0KHN0KSlyZXR1cm4gJ2Nhc2UtbGlrZS1tb2R1bGUtc3RlbSc7cmV0dXJuIG51bGw7fTsKY29uc3QgdmFsaWQ9cmF3PT5pbnZhbGlkUmVhc29uKHJhdyk9PT1udWxsOwpjb25zdCByeE1kUGF0aD0vdGVzdGNhc2VcL21kXC8oW0EtWmEtejAtOV8uLV0rKVwubWQvZzsKY29uc3QgcnhUYWJsZVJvdz0vXHxccyooW0EtWmEtejAtOV8uLV0rKVxzKlx8XHMqdGVzdGNhc2VcL3Rlc3RfL2c7CmNvbnN0IHJ4UmVsTGluaz0vXFtbXlxdXStcXVwoXC5cLyhbQS1aYS16MC05Xy4tXSspXC5tZFwpL2c7CmNvbnN0IGFsbExpbmVzPXJlYWRtZS5yZXBsYWNlKC9cclxuL2csJ1xuJykucmVwbGFjZSgvXHIvZywnXG4nKS5zcGxpdCgnXG4nKTsKY29uc3QgaGVhZGluZ3M9W107Zm9yKGxldCBpPTA7aTxhbGxMaW5lcy5sZW5ndGg7aSsrKXtpZihhbGxMaW5lc1tpXS50cmltKCk9PT0nIyMgTW9kdWxlIEluZGV4JyloZWFkaW5ncy5wdXNoKGkpO30KaWYoaGVhZGluZ3MubGVuZ3RoIT09MSl7cHJvY2Vzcy5zdGRlcnIud3JpdGUoKGhlYWRpbmdzLmxlbmd0aD09PTA/J21pc3NpbmctbW9kdWxlLWluZGV4JzonZHVwbGljYXRlLW1vZHVsZS1pbmRleCcpKyc7IHJlcXVpcmUgZXhhY3RseSBvbmUgZXhhY3QgIyMgTW9kdWxlIEluZGV4IHNlY3Rpb25cbicpO3Byb2Nlc3MuZXhpdCgyKTt9CmNvbnN0IHN0YXJ0PWhlYWRpbmdzWzBdKzE7bGV0IGVuZD1hbGxMaW5lcy5sZW5ndGg7Zm9yKGxldCBpPXN0YXJ0O2k8YWxsTGluZXMubGVuZ3RoO2krKyl7aWYoL14jI1xzK1xTLy50ZXN0KGFsbExpbmVzW2ldLnRyaW0oKSkpe2VuZD1pO2JyZWFrO319CmNvbnN0IHNlY3Rpb249YWxsTGluZXMuc2xpY2Uoc3RhcnQsZW5kKS5qb2luKCdcbicpOwpjb25zdCByYXc9W107CmNvbnN0IGxpbmVzPXNlY3Rpb24uc3BsaXQoJ1xuJykuZmlsdGVyKGw9PmwuaW5jbHVkZXMoJ3wnKSk7CmZvcihjb25zdCBsaW5lIG9mIGxpbmVzKXsKICBjb25zdCBiYXJlPXN0cmlwQmFja3RpY2tzKGxpbmUpOwogIGZvcihjb25zdCBtIG9mIGJhcmUubWF0Y2hBbGwocnhNZFBhdGgpKXtyYXcucHVzaChtWzFdKTt9CiAgZm9yKGNvbnN0IG0gb2YgYmFyZS5tYXRjaEFsbChyeFRhYmxlUm93KSl7aWYodmFsaWQobVsxXSl8fGludmFsaWRSZWFzb24obVsxXSkhPT0naW52YWxpZC1zeW50YXgnKXJhdy5wdXNoKG1bMV0pO30KfQpmb3IoY29uc3QgbSBvZiBzZWN0aW9uLm1hdGNoQWxsKHJ4UmVsTGluaykpe3Jhdy5wdXNoKG1bMV0pO30KY29uc3QgaW52YWxpZD1bXTtmb3IoY29uc3QgciBvZiByYXcpe2NvbnN0IHJlYXNvbj1pbnZhbGlkUmVhc29uKHIpO2lmKHJlYXNvbilpbnZhbGlkLnB1c2goe3N0ZW06bm9ybShyKSxyZWFzb259KTt9CmlmKGludmFsaWQubGVuZ3RoKXtmb3IoY29uc3QgaXRlbSBvZiBpbnZhbGlkKXByb2Nlc3Muc3RkZXJyLndyaXRlKGl0ZW0ucmVhc29uKyc6ICcraXRlbS5zdGVtKyc7IHVzZSBhIHN0YWJsZSBidXNpbmVzcyByZXNvdXJjZS9kb21haW4gc3RlbVxuJyk7cHJvY2Vzcy5leGl0KDIpO30KY29uc3Qgc2Vlbj1uZXcgU2V0KCk7Y29uc3QgbW9kdWxlcz1bXTsKZm9yKGNvbnN0IHIgb2YgcmF3KXtjb25zdCBzdD1ub3JtKHIpO2lmKHZhbGlkKHIpJiYhc2Vlbi5oYXMoc3QpKXtzZWVuLmFkZChzdCk7bW9kdWxlcy5wdXNoKHtzdGVtOnN0fSk7fX0KaWYobW9kdWxlcy5sZW5ndGg+OCl7cHJvY2Vzcy5zdGRlcnIud3JpdGUoJ2V4Y2Vzc2l2ZS1tb2R1bGUtY291bnQ6ICcrbW9kdWxlcy5sZW5ndGgrJyA+IDg7IG1lcmdlIGJ5IHRoZSBzbWFsbGVzdCBzdGFibGUgYnVzaW5lc3MgcmVzb3VyY2UvZG9tYWluIHNldFxuJyk7cHJvY2Vzcy5leGl0KDIpO30KcHJvY2Vzcy5zdGRvdXQud3JpdGUoSlNPTi5zdHJpbmdpZnkoe21vZHVsZXN9KSk7Cg==','base64').toString('utf8'))\""
341
+ "node -e \"eval(Buffer.from('Y29uc3QgZnM9cmVxdWlyZSgnZnMnKTsKY29uc3QgcmVhZG1lPWZzLmV4aXN0c1N5bmMoJ3Rlc3RjYXNlL21kL1JFQURNRS5tZCcpP2ZzLnJlYWRGaWxlU3luYygndGVzdGNhc2UvbWQvUkVBRE1FLm1kJywndXRmOCcpOicnOwpjb25zdCBub3JtPXM9PlN0cmluZyhzKS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL1teYS16MC05XSsvZywnXycpLnJlcGxhY2UoL15fK3xfKyQvZywnJykucmVwbGFjZSgvXysvZywnXycpOwpjb25zdCBidD1TdHJpbmcuZnJvbUNoYXJDb2RlKDk2KTsKY29uc3Qgc3RyaXBCYWNrdGlja3M9cz0+cy5zcGxpdChidCkuam9pbignJyk7CmNvbnN0IHZhbGlkPXJhdz0+e2NvbnN0IHN0PW5vcm0ocmF3KTtpZihzdD09PSdyZWFkbWUnKXJldHVybiBmYWxzZTtpZighL15bYS16XVthLXowLTlfXSokLy50ZXN0KHN0KSlyZXR1cm4gZmFsc2U7aWYoL14oPzpiZXx0cHxhY3xyZXF8YnIpW18tXS9pLnRlc3Qoc3QpKXJldHVybiBmYWxzZTtyZXR1cm4gdHJ1ZTt9Owpjb25zdCByeE1kUGF0aD0vdGVzdGNhc2VcL21kXC8oW0EtWmEtejAtOV8uLV0rKVwubWQvZzsKY29uc3QgcnhUYWJsZVJvdz0vXHxccyooW0EtWmEtejAtOV8uLV0rKVxzKlx8XHMqdGVzdGNhc2VcL3Rlc3RfL2c7CmNvbnN0IHJ4UmVsTGluaz0vXFtbXlxdXStcXVwoXC5cLyhbQS1aYS16MC05Xy4tXSspXC5tZFwpL2c7CmNvbnN0IHJhdz1bXTsKY29uc3QgbGluZXM9cmVhZG1lLnJlcGxhY2UoL1xyXG4vZywnXG4nKS5yZXBsYWNlKC9cci9nLCdcbicpLnNwbGl0KCdcbicpLmZpbHRlcihsPT5sLmluY2x1ZGVzKCd8JykpOwpmb3IoY29uc3QgbGluZSBvZiBsaW5lcyl7CiAgY29uc3QgYmFyZT1zdHJpcEJhY2t0aWNrcyhsaW5lKTsKICBmb3IoY29uc3QgbSBvZiBiYXJlLm1hdGNoQWxsKHJ4TWRQYXRoKSl7aWYodmFsaWQobVsxXSkpcmF3LnB1c2gobVsxXSk7fQogIGZvcihjb25zdCBtIG9mIGJhcmUubWF0Y2hBbGwocnhUYWJsZVJvdykpe2lmKHZhbGlkKG1bMV0pKXJhdy5wdXNoKG1bMV0pO30KfQpmb3IoY29uc3QgbSBvZiByZWFkbWUubWF0Y2hBbGwocnhSZWxMaW5rKSl7aWYodmFsaWQobVsxXSkpcmF3LnB1c2gobVsxXSk7fQpjb25zdCBzZWVuPW5ldyBTZXQoKTtjb25zdCBtb2R1bGVzPVtdOwpmb3IoY29uc3QgciBvZiByYXcpe2NvbnN0IHN0PW5vcm0ocik7aWYoIXNlZW4uaGFzKHN0KSl7c2Vlbi5hZGQoc3QpO21vZHVsZXMucHVzaCh7c3RlbTpzdH0pO319CnByb2Nlc3Muc3Rkb3V0LndyaXRlKEpTT04uc3RyaW5naWZ5KHttb2R1bGVzfSkpOwo=','base64').toString('utf8'))\""
345
342
  ],
346
343
  "cwd": ".",
347
344
  "timeoutMs": 60000
@@ -372,13 +369,13 @@
372
369
  "workflowNodeId": "generate-backend-pytest-cases-map",
373
370
  "itemsFrom": "$.nodes['materialize-backend-pytest-module-manifest-shell'].output.modules",
374
371
  "itemName": "item",
375
- "maxItems": 8,
376
- "maxExpandedNodes": 8,
372
+ "maxItems": 64,
373
+ "maxExpandedNodes": 64,
377
374
  "childIdPrefix": "generate-backend-pytest-case",
378
375
  "tokenBudget": {
379
- "maxTotalTokens": 3000000
376
+ "maxTokensPerCase": 16384,
377
+ "maxTotalTokens": 600000
380
378
  },
381
- "failOnTokenBudgetExhaustion": true,
382
379
  "childTask": {
383
380
  "executor": "pi",
384
381
  "role": "implementer",
@@ -420,7 +417,7 @@
420
417
  ]
421
418
  },
422
419
  "outputContract": "Write exactly one pytest module file testcase/test_<stem>.py whose actual test function region contains the exact Case ID, preferably in the function name or docstring. testcase/md/<module>.md (excluding README.md) maps one-to-one to testcase/test_<module>.py; never merge or split modules. No JSON and no pytest execution.",
423
- "subtaskPromptTemplate": "Convert the single Markdown module testcase/md/{{item.stem}}.md into one self-contained pytest module. After reading the module Markdown and the bounded pytest config/conftest, immediately use write tools to create the single file testcase/test_{{item.stem}}.py. Define any bounded HTTP client fixture, request logging/redaction/truncation helper and payload builders needed by this module inside that same file; do not import generated testcase/**/helpers/** or testcase/**/factories/** assets. Do not end after analysis or planning. Do not modify Markdown, conftest, helpers/factories, or any other module's pytest script.\n\nOutput budget protocol (hard, max output <=16K per turn): Write exactly one test_{{item.stem}}.py. Never paste full Python modules into assistant chat. Do not merge or split modules. Do not reduce params/assertions/skips to fit. If OUTPUT_LIMIT_RECOVERY is injected, continue only listed missing/broken scripts.\n\nAlign every variant pytest.param payload with the Markdown scenario intent (empty/missing/null/length/pattern/enum/wrong-type/nominal). Prefer literal payloads over Faker for intent-critical fields so pre-execution scenario-param checks can verify them.\n\nEnsure every automatable final Markdown Case ID in this module appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. Skip evidence-only meta Cases that declare `脚本/primary symbol=无` with empty variants; do not invent a business pytest symbol for them. The symbol must start with `test_BE_<MODULE>_<NNN>_` so every parameterized collected item remains associated with its Case. Module-level functions and class-based pytest methods are both supported. Only `变体测试点` may use stable `pytest.param(..., id=\"TP-...\")` IDs, and every atomic variant ID must appear exactly once with a genuine input/state/outcome change. Use `pytest.param(..., id=...)` for every row; do not use decorator-level `ids=[...]`, generated suffixes, or IDs that extend/shorten the exact Markdown TP. Do not parameterize `场景断言测试点` or `横切证据测试点`; execute all assertion checkpoints within the same business journey/item and use shared helpers for cross-cutting evidence. The primary symbol docstring must contain exact metadata lines `Case-ID: BE-...`, `Assertion-Test-Points: TP-...;TP-...` and `Cross-Cutting-Test-Points: TP-...;TP-...` (use `none` when empty). Implement request dictionaries so their direct and nested key paths and enum literals exactly satisfy the Case `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; for `Payload Contract: none`, do not invent a JSON/body DTO. No Test Point may be invented, renamed, omitted or bound in two modes. The generated pytest collection shape must equal the Markdown prediction `sum(max(1, variant count per Case))`; keep it at or below the task's explicit budget by removing duplicate execution, never by collapsing multiple parameter rows under a coarse family TP. Assertions come only from 预期结果 and setup comes only from 前置条件/测试数据/自动化映射.\n\nName the generated pytest file so it corresponds one-to-one with its source Markdown module file: this module stem `{{item.stem}}` maps to exactly one `testcase/test_{{item.stem}}.py`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `resource_notes` → `testcase/test_resource_notes.py`, `health` → `testcase/test_health.py`. If Markdown automation mapping names a different path than this module stem path, still write the module stem path and do not invent prefixes. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.\n\nKeep this module self-contained: define module-local fixtures and helpers directly in testcase/test_{{item.stem}}.py, so pytest discovers every fixture dependency without external plugin registration. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions. Recursively redact sensitive values and apply bounded truncation before logging.\n\nMaterialize every automatable Markdown Case exactly once as one canonical primary pytest symbol. Preserve every explicit variant Test Point as a stable pytest.param id and every assertion/cross-cutting binding as declared. Build request payloads from the effective Markdown test data literally: keep all declared DTO keys, nested shapes, enum values, missing/null/boundary variants and business-state preconditions; never substitute guessed convenience fields or rename contract fields.\n\nDo not read source/**, add cases, reassign ACs, modify conftest/config/production code, use skip/xfail, swallow assertions, execute pytest, or emit JSON. For best-effort cleanup, catch only the narrow transport exception actually raised by the selected HTTP client (for example `requests.RequestException` or `urllib.error.URLError`); never use bare `except`, `Exception`, or `BaseException` with `pass`."
420
+ "subtaskPromptTemplate": "Convert the single Markdown module testcase/md/{{item.stem}}.md into one self-contained pytest module. After reading the module Markdown and the bounded pytest config/conftest, immediately use write tools to create the single file testcase/test_{{item.stem}}.py. Define any bounded HTTP client fixture, request logging/redaction/truncation helper and payload builders needed by this module inside that same file; do not import generated testcase/**/helpers/** or testcase/**/factories/** assets. Do not end after analysis or planning. Do not modify Markdown, conftest, helpers/factories, or any other module's pytest script.\n\nOutput budget protocol (hard, max output <=16K per turn): Write exactly one test_{{item.stem}}.py. Never paste full Python modules into assistant chat. Do not merge or split modules. Do not reduce params/assertions/skips to fit. If OUTPUT_LIMIT_RECOVERY is injected, continue only listed missing/broken scripts.\n\nAlign every variant pytest.param payload with the Markdown scenario intent (empty/missing/null/length/pattern/enum/wrong-type/nominal). Prefer literal payloads over Faker for intent-critical fields so pre-execution scenario-param checks can verify them.\n\nEnsure every final Markdown Case ID in this module appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. The symbol must start with `test_BE_<MODULE>_<NNN>_` so every parameterized collected item remains associated with its Case. Module-level functions and class-based pytest methods are both supported. Only `变体测试点` may use stable `pytest.param(..., id=\"TP-...\")` IDs, and every atomic variant ID must appear exactly once with a genuine input/state/outcome change. Use `pytest.param(..., id=...)` for every row; do not use decorator-level `ids=[...]`, generated suffixes, or IDs that extend/shorten the exact Markdown TP. Do not parameterize `场景断言测试点` or `横切证据测试点`; execute all assertion checkpoints within the same business journey/item and use shared helpers for cross-cutting evidence. The primary symbol docstring must contain exact metadata lines `Case-ID: BE-...`, `Assertion-Test-Points: TP-...;TP-...` and `Cross-Cutting-Test-Points: TP-...;TP-...` (use `none` when empty). No Test Point may be invented, renamed, omitted or bound in two modes. The generated pytest collection shape must equal the Markdown prediction `sum(max(1, variant count per Case))`; keep it at or below the task's explicit budget by removing duplicate execution, never by collapsing multiple parameter rows under a coarse family TP. Assertions come only from 预期结果 and setup comes only from 前置条件/测试数据/自动化映射.\n\nName the generated pytest file so it corresponds one-to-one with its source Markdown module file: this module stem `{{item.stem}}` maps to exactly one `testcase/test_{{item.stem}}.py`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `resource_notes` → `testcase/test_resource_notes.py`, `health` → `testcase/test_health.py`. If Markdown automation mapping names a different path than this module stem path, still write the module stem path and do not invent prefixes. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.\n\nKeep this module self-contained: define module-local fixtures and helpers directly in testcase/test_{{item.stem}}.py, so pytest discovers every fixture dependency without external plugin registration. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions. Recursively redact sensitive values and apply bounded truncation before logging.\n\nDo not read source/**, add cases, reassign ACs, modify conftest/config/production code, use skip/xfail, swallow assertions, execute pytest, or emit JSON. For best-effort cleanup, catch only the narrow transport exception actually raised by the selected HTTP client (for example `requests.RequestException` or `urllib.error.URLError`); never use bare `except`, `Exception`, or `BaseException` with `pass`."
424
421
  }
425
422
  }
426
423
  },
@@ -485,7 +482,7 @@
485
482
  "type": "implementation-outcome-v1"
486
483
  },
487
484
  "outputContract": "First non-empty line is IMPLEMENTATION_OUTCOME: changed|already-satisfied|blocked, followed by a concise repair summary. Modify only generated pytest scripts/helpers/factories and preserve every Markdown Case, Test Point, primary symbol and assertion meaning.",
488
- "subtask_prompt": "Repair the generated backend pytest asset as one bounded program using the direct upstream collection assessment. This is the only repair attempt and happens before any business test body execution.\n\nFix only readiness-proven generated testcase-local defects on initial facts repairPaths: create exact safe missing mapped test_*.py paths, repair syntax/import/symbol/decorator/parameterization, close generated fixture dependencies/plugin registration, and repair initial Markdown-to-pytest correspondence findings (missing/multiple primary symbol, script mismatch, parameter ID or assertion binding). Never invent a business pytest symbol for evidence-only Markdown Cases that declare `脚本/primary symbol=无` with empty variants. For fixture defects inspect both provider and importer; fix ScopeMismatch by aligning fixture scopes or inlining request-scoped values so module fixtures never depend on function fixtures; when a shared fixture depends on sibling fixtures, register the whole provider module through an exact pytest_plugins declaration rather than importing only the outer fixture. Do not create unrelated pytest scripts.\n\nThis is the single pytest incremental synchronization round. For every assessment-listed path, compare the effective Markdown Case/Test Points/test data and its `Payload Contract`/`Payload Required Paths`/`Payload Allowed Paths`/`Payload Enum` labels with the generated module. Incrementally add or repair only missing symbols, params, assertions and payload builders. Repair every assessment-listed missing nested path, unexpected key and enum mismatch; preserve exact DTO keys, nested shapes, enum/boundary literals, operation transport and business preconditions; remove guessed replacement keys only when the effective Markdown proves the exact contract.\n\nPreserve final testcase/md/** semantics, every Case ID, Rule/Test Point binding, primary symbol, parameter ID, expected status/body/schema assertion, HTTP logging, redaction and truncation behavior.\n\nUse local edit only on assessment-listed paths; keep summaries short; never rewrite unrelated modules.\n\nDo not reinterpret requirements beyond the effective Markdown and bounded assessment diagnostics. Do not modify Markdown, conftest, pytest config, production code or dependencies.\n\nDo not add skip/skipif/xfail, remove tests, reduce collected items, loosen assertions, swallow exceptions, use try/except ImportError fallback, mutate sys.path/PYTHONPATH, or replace the real API with mocks.\n\nDo not execute pytest; the deterministic effective collection gate owns the final collection attempt."
485
+ "subtask_prompt": "Repair the generated backend pytest asset as one bounded program using the direct upstream collection assessment. This is the only repair attempt and happens before any business test body execution.\n\nFix only readiness-proven generated testcase-local defects on initial facts repairPaths: create exact safe missing mapped test_*.py paths, repair syntax/import/symbol/decorator/parameterization, or close generated fixture dependencies and plugin registration. For fixture defects inspect both provider and importer; when a shared fixture depends on sibling fixtures, register the whole provider module through an exact pytest_plugins declaration rather than importing only the outer fixture. Do not create unrelated pytest scripts.\n\nPreserve final testcase/md/** semantics, every Case ID, Rule/Test Point binding, primary symbol, parameter ID, expected status/body/schema assertion, HTTP logging, redaction and truncation behavior.\n\nUse local edit only on assessment-listed paths; keep summaries short; never rewrite unrelated modules.\n\nDo not read task source/** or reinterpret requirements. Do not modify Markdown, conftest, pytest config, production code or dependencies.\n\nDo not add skip/skipif/xfail, remove tests, reduce collected items, loosen assertions, swallow exceptions, use try/except ImportError fallback, mutate sys.path/PYTHONPATH, or replace the real API with mocks.\n\nDo not execute pytest; the deterministic effective collection gate owns the final collection attempt."
489
486
  },
490
487
  {
491
488
  "id": "effective-backend-pytest-collection-gate-shell",
@@ -506,8 +503,8 @@
506
503
  ".harness/dag-runs/**",
507
504
  "artifacts/**"
508
505
  ],
509
- "outputContract": "Run-owned effective collection-v3 facts, reports/backend-test-execution-eligibility.md, contracts/backend-test-execution-eligibility.json and contracts/backend-test-execution-readiness.json v2 proving exact final assets are collectable, fixture-resolvable, payload-safe at item level and hash-bound; initial PASS is reused, repair path records attempt=1.",
510
- "subtask_prompt": "If initial collection+fixture readiness passed, verify unchanged asset hashes and reuse it. If the single repair ran, rerun scenario-param preflight, final collection and no-business-body fixture resolution once. Recompute effective correspondence/payload shape and materialize Case/symbol/item `ELIGIBLE|NOT_ELIGIBLE` facts; Payload-contract-bound field-target MISMATCH/UNDETERMINED scenario items, non-exact mappings and payload UNSAFE/UNAVAILABLE items are excluded. Generic request-level nominal/health observations remain advisory when payload shape is SAFE and correspondence exact. BLOCKED facts, repair failure, residual fixture failure, zero eligible items or hash drift prevent business pytest execution. Materialize canonical backend-test-execution-readiness.json v2.",
506
+ "outputContract": "Run-owned effective collection-v3 facts plus contracts/backend-test-execution-readiness.json proving exact final assets are collectable, fixture-resolvable and hash-bound; initial PASS is reused, repair path records attempt=1.",
507
+ "subtask_prompt": "If initial collection+fixture readiness passed, verify unchanged asset hashes and reuse it. If the single repair ran, rerun scenario-param preflight, final collection and no-business-body fixture resolution once. BLOCKED facts, repair failure, residual fixture failure or hash drift prevent business pytest execution. Materialize canonical backend-test-execution-readiness.json.",
511
508
  "shell": {
512
509
  "commands": [],
513
510
  "backendTestPipeline": "markdown-collection-effective",
@@ -535,7 +532,7 @@
535
532
  "artifacts/**"
536
533
  ],
537
534
  "outputContract": "Run-owned reports/backend-test-traceability.md, reports/backend-test-markdown-pytest-correspondence.md, contracts/backend-test-markdown-pytest-correspondence-facts.json, reports/backend-test-scenario-param-consistency.md and contracts/backend-test-scenario-param-consistency-facts.json (initial+final) with optional repair audit; PASS/FAIL/UNAVAILABLE correspondence facts bound after effective collection.",
538
- "subtask_prompt": "Deterministically scan only final readiness-authorized Markdown-mapped pytest scripts. Produce bidirectional Markdown module/Case/Test Point ↔ pytest file/primary symbol correspondence, payload-shape safety, per-item execution eligibility and logging findings. scenario-param assessment/repair already ran before collection; consume and display its final PASS/PARTIAL/FAIL/UNAVAILABLE facts without modifying pytest assets after readiness was frozen. Correspondence findings remain advisory; Never block pytest solely on correspondence FAIL.",
535
+ "subtask_prompt": "Deterministically scan only final readiness-authorized Markdown-mapped pytest scripts. Produce bidirectional Markdown module/Case/Test Point ↔ pytest file/primary symbol correspondence and logging findings. scenario-param assessment/repair already ran before collection; consume and display its final PASS/PARTIAL/FAIL/UNAVAILABLE facts without modifying pytest assets after readiness was frozen. Correspondence findings remain advisory; Never block pytest solely on correspondence FAIL.",
539
536
  "shell": {
540
537
  "commands": [],
541
538
  "backendTestPipeline": "markdown-traceability",
@@ -588,11 +585,11 @@
588
585
  ".harness/dag-runs/**",
589
586
  "artifacts/**"
590
587
  ],
591
- "outputContract": "One scoped pytest execution over readiness-authorized eligible pytest items producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html, reports/backend-test.md, reports/backend-test-facts.md, a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON), and an optional contracts/code-coverage-v1.json when jacocoCoverage is configured (JaCoCo TCP dump → jacoco.xml → parsed; failure-safe); exit 0/1 with valid evidence continues.",
592
- "subtask_prompt": "Read canonical contracts/backend-test-execution-readiness.json v2, verify final asset and eligibility-input hashes, then execute exactly its `eligibleItemIds` pytest node IDs once. Never execute `excludedItems`; retain each exclusion reason as residual TestBug/automation evidence rather than ProductBug. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论 and quality status; make node 6 Markdown validation + case coverage and node 13 traceability + Markdown-to-pytest correspondence expandable to their full escaped details; show each failure overview item with its original pytest message plus deterministic evidence-based reason analysis; list failure/error case cards before the remaining cases while preserving stable order. Each polished per-case result card includes concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.",
588
+ "outputContract": "One scoped pytest execution over Markdown-mapped scripts producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html, reports/backend-test.md, reports/backend-test-facts.md, a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON), and an optional contracts/code-coverage-v1.json when jacocoCoverage is configured (JaCoCo TCP dump → jacoco.xml → parsed; failure-safe); exit 0/1 with valid evidence continues.",
589
+ "subtask_prompt": "Read canonical contracts/backend-test-execution-readiness.json, verify final asset hashes, then resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论 and quality status; make node 6 Markdown validation + case coverage and node 13 traceability + Markdown-to-pytest correspondence expandable to their full escaped details; show each failure overview item with its original pytest message plus deterministic evidence-based reason analysis; list failure/error case cards before the remaining cases while preserving stable order. Each polished per-case result card includes concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.",
593
590
  "shell": {
594
591
  "commands": [
595
- "mkdir -p \"${HARNESS_DAG_RUN_DIR}/reports\"; echo \"pytest targets are resolved at runtime from execution-readiness v2 eligibleItemIds\""
592
+ "mkdir -p \"${HARNESS_DAG_RUN_DIR}/reports\"; echo \"pytest targets are resolved at runtime from final Markdown 自动化映射\""
596
593
  ],
597
594
  "backendTestPipeline": "markdown-execute-html",
598
595
  "cwd": ".",
package/harness.json CHANGED
@@ -81,7 +81,7 @@
81
81
  "pi": {
82
82
  "description": "Pi 负责规划、评审、诊断;当 DAG toolProfile=write 时也可做有界写入。模型按复杂度三档配置,格式为 provider/model 字符串(例:wizard-local/grok-4.5);斜杠前为 Pi provider,后为 modelId,勿只写裸 modelId。",
83
83
  "LOW": "wizard-local/minimax-m3",
84
- "MED": "wizard-local/gpt-5.6-sol",
84
+ "MED": "wizard-local/grok-4.5",
85
85
  "HIGH": "wizard-local/gpt-5.6-sol"
86
86
  }
87
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.33.7-beta.0",
3
+ "version": "0.34.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",