@tea-agent/loop-agent 0.16.1 → 0.16.2

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 (31) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/executors/dag-pi-executor.js +4 -2
  3. package/dist/executors/pi-sdk-executor.js +66 -3
  4. package/dist/executors/shell-executor.js +212 -29
  5. package/dist/executors/shell-presets.js +12 -2
  6. package/dist/executors/shell-write-guard.js +20 -1
  7. package/dist/shared/git-progress.js +9 -2
  8. package/dist/worker/observability/read-model.js +56 -0
  9. package/dist/worker/observe/server.js +6 -3
  10. package/dist/workflows/dag/backend-test-analysis-contract.js +87 -30
  11. package/dist/workflows/dag/backend-test-case-manifest.js +71 -8
  12. package/dist/workflows/dag/backend-test-execution-contract.js +63 -11
  13. package/dist/workflows/dag/backend-test-repair-contract.js +94 -0
  14. package/dist/workflows/dag/backend-test-result-contract.js +6 -4
  15. package/dist/workflows/dag/backend-test-semantic-review-contract.js +36 -0
  16. package/dist/workflows/dag/dynamic-runtime/condition.js +1 -1
  17. package/dist/workflows/dag/dynamic-runtime/shared.js +42 -0
  18. package/dist/workflows/dag/failure-routing.js +1 -1
  19. package/dist/workflows/dag/frontend-implementation-contract.js +32 -16
  20. package/dist/workflows/dag/init-hybrid.js +591 -119
  21. package/dist/workflows/dag/lifecycle.js +33 -2
  22. package/dist/workflows/dag/scheduler.js +87 -17
  23. package/dist/workflows/dag/types.js +31 -0
  24. package/dist/workflows/dag/validate.js +20 -14
  25. package/docs/templates/agent-dag.schema.json +25 -2
  26. package/docs/templates/backend-test-analysis.schema.json +9 -16
  27. package/docs/templates/backend-test-dag.json +493 -197
  28. package/docs/templates/backend-test-dag.review-cases.prompt.md +10 -4
  29. package/docs/templates/backend-test-execution.schema.json +6 -1
  30. package/package.json +1 -1
  31. package/skills/loop-agent/references/hybrid-dag.md +4 -1
@@ -1,60 +1,42 @@
1
1
  {
2
- "$schema": "./agent-dag.schema.json",
3
2
  "version": 3,
4
- "title": "Backend test DAG template",
3
+ "title": "Backend test DAG: Backend test template",
5
4
  "runtimeContract": {
6
5
  "schemaVersion": 1,
7
6
  "agentRuntime": "pi-only",
8
7
  "repairWriterProtocol": "explicit-node-v1"
9
8
  },
10
- "objective": "End-to-end backend functional testing pipeline: analyze requirements as Backend Test Analysis v1 JSON → validate analysis contract → environment scout + execution contract → generate functional test cases → emit+validate Case Manifest v1 → review cases → generate pytest automation → traceability gate → preflight + execute pytest → parse Result v1 → classify failures → retrospective → outcome gate. Covers the full chain from requirement analysis to test maturity assessment with always-run retrospect on assertion failures and deterministic AC→case→symbol traceability.",
9
+ "outputLanguage": "zh-CN",
10
+ "objective": "- AC-001 proof",
11
11
  "successCriteria": [
12
- "analyze-inputs-pi returns pure Backend Test Analysis v1 JSON (schema docs/templates/backend-test-analysis.schema.json) with no Markdown prose",
13
- "backend-test-analysis-contract-shell validates schemaId backend-test-analysis-v1 and materializes run-owned contracts/backend-test-analysis.json",
14
- "backend-test-environment-scout-pi returns pure Backend Test Execution Contract v1 JSON (schema docs/templates/backend-test-execution.schema.json)",
15
- "backend-test-execution-contract-shell validates schemaId backend-test-execution-v1 and materializes run-owned contracts/backend-test-execution.json",
16
- "generate-backend-functional-cases-pi consumes validated contracts and produces structured test cases with BE-<MODULE>-<NNN> IDs under testcase/md/",
17
- "emit-backend-case-manifest-pi returns pure Backend Test Case Manifest v1 JSON (schema docs/templates/backend-test-case-manifest.schema.json) with no Markdown prose",
18
- "backend-test-case-manifest-shell validates schemaId backend-test-case-manifest-v1 and materializes run-owned contracts/backend-test-case-manifest.json with deterministic AC coverage",
19
- "backend-test-traceability-gate-shell verifies generated file/symbol existence after pytest generation and before execute",
20
- "test-retrospect-pi uses Case Manifest coverageSummary + Result v1 only for coverage/pass rate inputs (model must not invent percentages)",
21
- "review-backend-cases-pi returns VERDICT: pass or request-revision with coverage assessment",
22
- "review-backend-cases-gate-shell blocks pytest generation unless the review verdict is VERDICT: pass",
23
- "generate-backend-pytest-pi converts reviewed cases into pytest code using verified analysis + execution contracts under testcase/",
24
- "execute-backend-pytest-shell runs deterministic preflight against the execution contract, then pytest with frozen Adapter testRoot (default testcase/), writing JUnit XML under the current $HARNESS_DAG_RUN_DIR/reports/** only",
25
- "execute-backend-pytest-shell maps pytest exit 0/1 to node success only when non-empty JUnit exists; raw pytestExitCode is side-channeled under reports/",
26
- "parse-backend-test-result-shell materializes run-owned contracts/backend-test-result.json (Backend Test Result v1) from JUnit + pytestExitCode",
27
- "classify-backend-test-result-pi returns structured failure classification JSON (ProductBug|TestBug|EnvFailure|ContractMismatch|FlakyTest|Unknown) without writing files",
28
- "test-retrospect-pi runs after parse+classify on both pass and assertion-fail paths; stats come from Result v1 only",
29
- "backend-test-outcome-gate-shell exits non-zero unless result.outcome=passed (shell facts only; retrospective cannot override)",
30
- "test-retrospect-pi generates retrospective report with A/B/C/D maturity rating under docs/test-reports/",
31
- "Full traceability from acceptance criteria → functional test case ID → pytest function name"
12
+ "contract-pi produces an auditable read-only implementation contract for backend-test-template",
13
+ "scout-src and scout-tests complete parallel read-only reconnaissance without file writes",
14
+ "plan-pi returns a read-only implementation plan aligned with constraints",
15
+ "the implementation writer changes only paths declared in writeSet",
16
+ "verify-pi returns read-only verification strategy and residual risks",
17
+ "closeout-pi returns read-only handoff summary without writing artifacts/docs"
32
18
  ],
33
19
  "globalConstraints": [
34
- "Replace every REPLACE/WITH/... placeholder with concrete repo paths before execution; do not leave template placeholders in production DAG JSON.",
35
- "Do not commit runtime traces under .harness/dag-runs/.",
36
- "Use only existing executors: pi, shell. Pi writer nodes must set toolProfile=write.",
37
- "Read-only nodes must not write repository files, including root artifacts/**.",
38
- "Exclusive writer nodes must stay within declared writeSet.",
39
- "Every task must explicitly declare executor; defaults.executor is schema-only and not a runtime fallback.",
20
+ "Do not commit runtime traces under .harness/dag-runs/",
21
+ "Do not enable cross-node Pi runtime reuse by default",
22
+ "Preserve CODE_AGENT_PI_BACKEND=cli-only rollback for Pi nodes",
23
+ "Same-rank exclusive writeSet entries must be disjoint",
24
+ "Every task must explicitly declare executor; defaults.executor is schema-only and not a runtime fallback",
25
+ "Prefer same-rank parallel read-only scouts over serial chains when outputs are independent",
26
+ "If the DAG is a single linear chain, challenge whether read-only work can run in parallel ranks",
27
+ "Read-only DAG nodes must not write repository files, including root artifacts/**; return findings in node output only",
28
+ "Root artifacts/ is reserved for explicit exclusive write nodes, not read-only scout/reviewer output",
29
+ "exclusive implementer nodes must use narrow, concrete writeSet paths; never keep ** or repo root",
30
+ "Replace REPLACE/WITH/NARROW/IMPLEMENT/PATHS/** with concrete paths before executing the implementation writer",
31
+ "backend-test-dag uses exactly 24 real top-level tasks; bounded revision/repair branches are controlled by fail-closed runIf expressions.",
32
+ "Analysis, execution, manifest, semantic review, initial/final/effective results, classification, eligibility, repair safety, traceability and outcome evidence remain run-owned and fail-closed.",
40
33
  "Functional test case IDs must use BE-<MODULE>-<NNN> format.",
41
- "pytest execution must keep the target worktree read-only and write machine-readable results only under the current HARNESS_DAG_RUN_DIR/reports/** (e.g. JUnit XML).",
42
- "Backend-test-dag nodes must maintain traceability from requirements to functional cases to pytest automation.",
43
- "pytest automation scripts must use test_ filename prefix for pytest discovery.",
44
- "generate-backend-pytest-pi may create only new files under testcase/**/test_*.py, testcase/**/helpers/**, and testcase/**/factories/**; modifying conftest.py, pytest.ini, pyproject.toml, or production code is forbidden.",
45
- "review-backend-cases-gate-shell must block pytest generation unless the review verdict is exactly VERDICT: pass.",
46
- "If a target test filename already exists under testcase/, add a numeric suffix (_01, _02, ...); never overwrite or append to existing files.",
47
- "execute-backend-pytest-shell must not modify test assertions or production code to make tests pass; test failures indicate potential implementation issues and must be reported honestly.",
48
- "Prompt templates must not ask main session to write artifacts; node output is the artifact and runner archives it under .harness/dag-runs/.",
49
- "Actual file operation paths must be macOS/Windows compatible; use / only for stable repo refs, JSON/Markdown evidence refs, and glob conventions.",
50
- "Same-rank exclusive writeSet entries must be disjoint.",
51
- "parse-backend-test-result-shell materializes Backend Test Result v1 from JUnit + pytestExitCode; classify/retrospect run on pass and assertion-fail; backend-test-outcome-gate-shell uses result.outcome only.",
52
- "backend-test-case-manifest-shell validates schemaId backend-test-case-manifest-v1 and materializes contracts/backend-test-case-manifest.json; AC coverage is fail-closed and deterministic.",
53
- "backend-test-traceability-gate-shell verifies generated file/symbol existence after pytest generation and before execute; models must not invent coverage percentages."
34
+ "pytest writers may only change declared testcase assets; production code, config, skip/xfail, swallowed failures and mock substitution are forbidden."
54
35
  ],
55
36
  "defaults": {
56
37
  "executor": "pi",
57
- "contextProfile": "slim",
38
+ "piBackend": "sdk-first",
39
+ "contextProfile": "full",
58
40
  "skills": [
59
41
  "ai-engineering-context"
60
42
  ],
@@ -91,20 +73,23 @@
91
73
  },
92
74
  "tasks": [
93
75
  {
94
- "id": "analyze-inputs-pi",
76
+ "id": "analyze-and-discover-backend-test-pi",
95
77
  "depends_on": [],
96
- "complexity": "MED",
97
- "executor": "pi",
98
78
  "role": "planner",
79
+ "executor": "pi",
80
+ "complexity": "MED",
99
81
  "writePolicy": "read-only",
100
82
  "allowedPaths": [
101
- "REPLACE/WITH/SOURCE/PATH/**"
83
+ "testcase/**",
84
+ "docs/test-reports/**"
102
85
  ],
103
86
  "forbiddenPaths": [
104
87
  ".harness/**",
88
+ ".harness/dag-runs/**",
105
89
  "artifacts/**"
106
90
  ],
107
- "outputContract": "Pure Backend Test Analysis v1 JSON object matching docs/templates/backend-test-analysis.schema.json. No Markdown prose and no file writes.",
91
+ "outputContract": "Pure JSON envelope {analysis: Backend Test Analysis v2, execution: Backend Test Execution Contract v1}; no prose or writes.",
92
+ "subtask_prompt": "Read the task source materials and return exactly one JSON object matching Backend Test Analysis v2.\n\nDo not wrap it in explanatory prose. A single fenced json block is tolerated, but pure JSON is preferred.\n\nCopy the sourceBinding object exactly from the JSON block below; do not infer, add, remove, or reclassify source paths.\n\nOnly kind=reference sources belong in referencePaths; kind=constraint sources MUST NOT be included in referencePaths.\n\n## Exact Backend Test Analysis sourceBinding JSON\n\n{\n \"taskId\": \"backend-test-template\",\n \"requirementPath\": \"source/需求.md\",\n \"requirementSha256\": \"bdecb1206ebb38957da644736300813f6e59a919a10be1b48126fbc1282bc9f6\",\n \"referencePaths\": [],\n \"requirementIds\": [\n \"AC-001\"\n ]\n}\n\nFor every endpoint, explicitly set responseBody.kind=array|object|scalar|empty|unknown and ordering=specified|unspecified|not-applicable. Add itemSchemaRef for arrays when documented.\n\nFor response fields, use comparison=exact|parseable-only|semantic when the source defines assertion semantics; date-time fields whose precision is unspecified should use parseable-only, not string equality.\n\nEndpoint sourceRefs and field sourceRefs must cite only requirement/reference evidence actually read. Empty sourceRefs are allowed only when normalizing legacy v1 input; newly generated v2 should cite evidence.\n\nUse empty arrays for categories not documented. Never include credentials, tokens, private keys, or secret values.\n\nRequired top-level keys: schemaVersion=2, sourceBinding, acceptanceCriteria, endpoints, dataModels, businessRules, stateTransitions, boundaryConstraints, externalDependencies, risks, evidenceGaps.\n\nRead-only: do not modify code, docs, artifacts, or repository files.\n\n## Task source: 需求.md\n\n# Backend test template\n\n- AC-001 proof\n\n## Task config summary\n\n- taskId: backend-test-template\n\n- flow: auto\n\n- complexity: medium\n\n- contextProfile: full\n\n- allowedPaths: testcase/**, docs/test-reports/**\n\n- forbiddenPaths: (none)\n\n- Pi DAG nodes are read-only unless toolProfile=\"write\" is explicitly selected for a bounded writer node.\n\n- Agent DAG read-only nodes must not write root artifacts/**; root artifacts/ is not a per-node scratchpad.\n\n- source/references/* are immutable user/source facts; source/需求.md is the derived execution contract.\n\nAlso perform the read-only environment discovery described by Backend Test Execution Contract v1. Return exactly one JSON envelope with top-level keys analysis and execution; analysis must satisfy v2 and execution must satisfy v1.",
108
93
  "retryPolicy": {
109
94
  "maxAttempts": 3,
110
95
  "backoff": "exponential",
@@ -116,94 +101,85 @@
116
101
  "rate-limit",
117
102
  "unavailable"
118
103
  ]
119
- },
120
- "subtask_prompt": "Read the task source materials and return exactly one JSON object matching Backend Test Analysis v1.\n\nDo not wrap it in explanatory prose. A single fenced json block is tolerated, but pure JSON is preferred.\n\nCopy taskId, requirementPath, requirementSha256, referencePaths, and requirementIds exactly from the DAG source binding shown below.\n\nPreserve existing AC IDs. Do not invent endpoint methods, paths, fields, errors, boundaries, or business rules; record unknowns in evidenceGaps.\n\nUse empty arrays for categories not documented. Never include credentials, tokens, private keys, or secret values.\n\nRequired top-level keys: schemaVersion, sourceBinding, acceptanceCriteria, endpoints, dataModels, businessRules, stateTransitions, boundaryConstraints, externalDependencies, risks, evidenceGaps.\n\nRead-only: do not modify code, docs, artifacts, or repository files."
104
+ }
121
105
  },
122
106
  {
123
- "id": "backend-test-analysis-contract-shell",
107
+ "id": "validate-backend-test-contracts-shell",
124
108
  "depends_on": [
125
- "analyze-inputs-pi"
109
+ "analyze-and-discover-backend-test-pi"
126
110
  ],
127
- "complexity": "LOW",
128
- "executor": "shell",
129
111
  "role": "verifier",
112
+ "executor": "shell",
113
+ "complexity": "LOW",
130
114
  "writePolicy": "read-only",
131
115
  "allowedPaths": [
132
- "REPLACE/WITH/SOURCE/PATH/**"
116
+ "testcase/**",
117
+ "docs/test-reports/**"
133
118
  ],
134
119
  "forbiddenPaths": [
135
120
  ".harness/**",
121
+ ".harness/dag-runs/**",
136
122
  "artifacts/**"
137
123
  ],
138
- "outputContract": "Validated run-owned Backend Test Analysis v1 artifact pointer, schema ID, and SHA-256.",
139
- "subtask_prompt": "Materialize and validate the backend-test analysis contract under the current DAG run.",
124
+ "outputContract": "Materialize and validate contracts/backend-test-analysis.json and contracts/backend-test-execution.json.",
125
+ "subtask_prompt": "Validate both backend-test intake contracts fail-closed.",
140
126
  "shell": {
141
127
  "commands": [],
142
- "jsonArtifactGate": {
143
- "fromNodeId": "analyze-inputs-pi",
144
- "schemaId": "backend-test-analysis-v1",
145
- "artifactName": "backend-test-analysis.json",
146
- "outputDir": "contracts"
147
- },
128
+ "backendTestPipeline": "contracts",
148
129
  "cwd": ".",
149
130
  "timeoutMs": 60000
150
131
  }
151
132
  },
152
133
  {
153
- "id": "backend-test-environment-scout-pi",
134
+ "id": "generate-backend-cases-and-manifest-pi",
154
135
  "depends_on": [
155
- "backend-test-analysis-contract-shell"
136
+ "validate-backend-test-contracts-shell"
156
137
  ],
157
- "complexity": "MED",
138
+ "role": "implementer",
158
139
  "executor": "pi",
159
- "role": "scout",
160
- "writePolicy": "read-only",
140
+ "toolProfile": "write",
141
+ "complexity": "MED",
142
+ "writePolicy": "exclusive",
143
+ "writeSet": [
144
+ "testcase/md/**"
145
+ ],
161
146
  "allowedPaths": [
162
- "REPLACE/WITH/SOURCE/PATH/**"
147
+ "testcase/md/**"
163
148
  ],
164
149
  "forbiddenPaths": [
165
150
  ".harness/**",
151
+ ".harness/dag-runs/**",
166
152
  "artifacts/**"
167
153
  ],
168
- "outputContract": "Pure Backend Test Execution Contract v1 JSON object matching docs/templates/backend-test-execution.schema.json. No Markdown prose and no file writes.",
169
- "retryPolicy": {
170
- "maxAttempts": 3,
171
- "backoff": "exponential",
172
- "initialDelayMs": 2000,
173
- "maxDelayMs": 30000,
174
- "retryCategories": [
175
- "timeout",
176
- "network",
177
- "rate-limit",
178
- "unavailable"
179
- ]
180
- },
181
- "subtask_prompt": "Read-only environment scout for backend-test pytest MVP.\n\nReturn exactly one JSON object matching Backend Test Execution Contract v1 (schema docs/templates/backend-test-execution.schema.json).\n\nPrefer pure JSON; a single fenced json block is tolerated; no trailing prose.\n\nDiscover only non-secret evidence: pytest config files (pytest.ini / pyproject.toml / setup.cfg test paths), candidate test roots, existing fixtures/clients, documented run commands, and env *names* (not values).\n\nDo NOT search the whole repo for secrets, .env values, tokens, private keys, or production credentials.\n\nframework must be \"pytest\". Default targetMode to \"in-process\" unless evidence clearly shows an external service base URL env name or documented managed start/stop with sourceRef.\n\nDo NOT select targetMode \"managed-command\" unless task source documents a safe start/stop command with an explicit sourceRef; otherwise leave managedCommand absent and record the gap in evidenceGaps.\n\ntestRoot and workingDirectory must be repo-relative posix paths without .. or absolute form. Adapter default testRoot is testcase when evidence is incomplete.\n\nrunner must not include secret values. report.format must be junit with a relativeHint under the run (e.g. reports/backend-test-junit.xml).\n\nrequiredEnvNames lists env NAMES only. baseUrlEnvName is required only for external-running-service and must match ^[A-Z_][A-Z0-9_]*$.\n\nRecord incomplete discovery in evidenceGaps. Populate evidenceRefs with repo-relative paths actually read.\n\nRequired top-level keys: schemaVersion, framework, runner, testRoot, workingDirectory, report, targetMode, existingFixtures, authenticationMode, requiredEnvNames, dataIsolation, evidenceGaps, evidenceRefs.\n\nRead-only: do not modify code, docs, artifacts, or repository files."
154
+ "subtask_prompt": "Read both validated run-owned contracts before generating functional cases:\n\n- contracts/backend-test-analysis.json: authoritative requirements, AC IDs, endpoints, fields, rules, boundaries, risks, and evidence gaps.\n\n- contracts/backend-test-execution.json: pytest target mode, base URL env name, readiness, fixtures, and data-isolation constraints.\n\nGenerate cases from the analysis contract; use the execution contract only to keep preconditions and automation feasibility realistic.\n\nDo not proceed from the execution contract alone. Do not re-read source documents or fall back to free-form analysis.\n\n\n\n## Output Steps (do in order):\n\n1. First, output a brief summary: how many modules, how many cases planned per module\n\n2. Then write each test case file under testcase/md/\n\n\n\n## Format Rules:\n\n- Each test case ID: BE-<MODULE>-<NNN> (e.g. BE-ORDER-001) — always write the FULL id; never abbreviate as 002, 003 in matrices\n\n- Each file covers one module\n\n- Case structure: ID, Title, Acceptance Criteria, Business Rules, Precondition, Steps, Expected Result\n\n- Every emitted case MUST declare at least one semantically applicable explicit AC-* under Acceptance Criteria; list BR-* separately under Business Rules\n\n- If a BR-only scenario has no semantically valid in-scope AC, do not create a standalone case for it; record the limitation in the summary for the manifest evidenceGaps instead\n\n- Never relabel a negative/boundary/BR-only behavior as AC-002 or another unrelated AC merely to make acIds non-empty\n\n- Map each case to acceptance criteria (AC-xxx)\n\n\n\n## AC ↔ case consistency (CRITICAL — prevents review request-revision):\n\n- Every AC-xxx listed on a case body MUST appear only on cases that truly exercise that AC\n\n- Any AC-coverage matrix / summary table MUST list the same full BE-* case IDs that the case bodies claim — never 'all cases' / '全部用例' unless every case body maps that AC\n\n- Prefer one primary BE-* case for suite-level ACs (e.g. AC-008 pytest exit 0) rather than tagging every case\n\n- Out-of-scope ACs (Flyway, frontend e2e, mvn test, etc.) must NOT be claimed in MD; leave them for manifest evidenceGaps\n\n\n\n## Coverage Requirements:\n\n- Positive paths: happy path for each acceptance criterion\n\n- Negative paths: error scenarios (invalid input, not found, state violations)\n\n\n\n## Conditional Coverage (include ONLY if mentioned in upstream analysis):\n\n- Boundary conditions: include ONLY if upstream analyze-inputs-pi mentions value ranges, length limits, numeric bounds, or format constraints\n\n- State transitions: include ONLY if upstream analyze-inputs-pi mentions state machine\n\n- Authentication scenarios: include ONLY if upstream analyze-inputs-pi mentions auth mechanism\n\n- Timeout scenarios: include ONLY if upstream analyze-inputs-pi mentions timeout handling\n\n- Concurrency scenarios: include ONLY if upstream analyze-inputs-pi mentions concurrency/idempotency rules\n\n- If not mentioned, do NOT generate these test cases\n\n\n\n## Constraints:\n\n- Stay within writeSet: testcase/md/**\n\n- Do NOT re-read source documents or fall back to free-form analysis; use the two validated run-owned contracts only\n\n- Do not write root artifacts/**\n\nAfter writing Markdown, end assistant output with exactly one fenced json block containing Backend Test Case Manifest v1 derived from the written cases.\n\nThe final fenced JSON block is authoritative and MUST conform exactly to Backend Test Case Manifest v1.\n\nTop-level keys MUST be exactly: schemaVersion, sourceBinding, cases, evidenceGaps, and optional coverageSummary. Set schemaVersion to numeric 1. Do NOT emit schemaId, manifestType, taskId, modules, acCoverage, brCoverage, dataIsolation, readiness, or other custom top-level keys.\n\nCopy sourceBinding exactly from contracts/backend-test-analysis.json: taskId, requirementPath, requirementSha256, referencePaths, requirementIds. Preserve Unicode paths exactly; never replace characters in source/需求.md or other paths.\n\nEach cases[] item MUST use exactly: caseId, non-empty acIds, title, category, automationStatus; optional endpointRef, ruleRefs, file, symbol, gapReason, evidenceRef. Do NOT use id, module, brIds, endpoint, or priority.\n\ncategory MUST be exactly one of: positive, negative, boundary, state-transition, auth, timeout, concurrency, other.\n\nBefore pytest generation, set automationStatus=planned. Use generated only with both file and symbol. Use skipped or unsupported only with gapReason.\n\nEach evidenceGaps[] item MUST use exactly: optional acId, optional caseId, required description, optional evidenceRef. Every gap requires at least acId or caseId. Do NOT use requirementId, relatedBrIds, or sourceRef.\n\nEvery case must map to at least one semantically applicable explicit AC-* in acIds. If no AC applies, omit that case and bind an evidence gap to the nearest applicable acId or caseId; never emit an unbound informational gap.\n\nacIds MUST exactly match the explicit AC-* values in the written case body; do not infer ACs from Business Rules or summary matrices.\n\nUse full BE-<MODULE>-<NNN> caseId strings. Do not invent coverage percentages; omit coverageSummary unless all deterministic counts are exact.\n\nMinimal shape example: {\"schemaVersion\":1,\"sourceBinding\":{\"taskId\":\"...\",\"requirementPath\":\"source/需求.md\",\"requirementSha256\":\"<64 lowercase hex>\",\"referencePaths\":[],\"requirementIds\":[\"AC-001\"]},\"cases\":[{\"caseId\":\"BE-MODULE-001\",\"acIds\":[\"AC-001\"],\"title\":\"...\",\"category\":\"positive\",\"automationStatus\":\"planned\",\"evidenceRef\":\"testcase/md/module.md\"}],\"evidenceGaps\":[]}",
155
+ "outputContract": "Write testcase/md/** and end with one fenced json Backend Test Case Manifest v1 block matching the strict field contract."
182
156
  },
183
157
  {
184
- "id": "backend-test-execution-contract-shell",
158
+ "id": "backend-test-case-manifest-shell",
185
159
  "depends_on": [
186
- "backend-test-environment-scout-pi"
160
+ "generate-backend-cases-and-manifest-pi"
187
161
  ],
188
- "complexity": "LOW",
189
- "executor": "shell",
190
162
  "role": "verifier",
163
+ "executor": "shell",
164
+ "complexity": "LOW",
191
165
  "writePolicy": "read-only",
192
166
  "allowedPaths": [
193
- "REPLACE/WITH/SOURCE/PATH/**"
167
+ "testcase/**",
168
+ "docs/test-reports/**"
194
169
  ],
195
170
  "forbiddenPaths": [
196
171
  ".harness/**",
172
+ ".harness/dag-runs/**",
197
173
  "artifacts/**"
198
174
  ],
199
- "outputContract": "Validated run-owned Backend Test Execution Contract v1 artifact pointer, schema ID, and SHA-256.",
200
- "subtask_prompt": "Materialize and validate the backend-test execution contract under the current DAG run.",
175
+ "outputContract": "Validated run-owned Backend Test Case Manifest v1 at contracts/backend-test-case-manifest.json (schemaId backend-test-case-manifest-v1) with deterministic AC coverage.",
176
+ "subtask_prompt": "Materialize and validate Backend Test Case Manifest v1; fail closed on duplicate IDs, unknown AC, missing AC coverage without gap, or skipped without gapReason.",
201
177
  "shell": {
202
178
  "commands": [],
203
179
  "jsonArtifactGate": {
204
- "fromNodeId": "backend-test-environment-scout-pi",
205
- "schemaId": "backend-test-execution-v1",
206
- "artifactName": "backend-test-execution.json",
180
+ "fromNodeId": "generate-backend-cases-and-manifest-pi",
181
+ "schemaId": "backend-test-case-manifest-v1",
182
+ "artifactName": "backend-test-case-manifest.json",
207
183
  "outputDir": "contracts"
208
184
  },
209
185
  "cwd": ".",
@@ -211,15 +187,49 @@
211
187
  }
212
188
  },
213
189
  {
214
- "id": "generate-backend-functional-cases-pi",
190
+ "id": "review-backend-cases-pi",
215
191
  "depends_on": [
216
- "backend-test-execution-contract-shell"
192
+ "backend-test-case-manifest-shell",
193
+ "validate-backend-test-contracts-shell"
217
194
  ],
218
- "complexity": "MED",
195
+ "role": "reviewer",
219
196
  "executor": "pi",
197
+ "complexity": "HIGH",
198
+ "writePolicy": "read-only",
199
+ "allowedPaths": [
200
+ "testcase/**",
201
+ "docs/test-reports/**"
202
+ ],
203
+ "forbiddenPaths": [
204
+ ".harness/**",
205
+ ".harness/dag-runs/**",
206
+ "artifacts/**"
207
+ ],
208
+ "outputContract": "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision; followed by Findings, Coverage Assessment, and Required revisions when requesting revision. No file writes.",
209
+ "subtask_prompt": "Review the generated backend functional test cases under testcase/md/ and the validated Case Manifest v1.\n\n## Mandatory First Line:\n\nFirst non-empty line must be exactly: VERDICT: pass or VERDICT: request-revision\n\n## Review Checklist:\n\n- ID format: every case uses BE-<MODULE>-<NNN> (full ids only in bodies and matrices)\n\n- Positive coverage: each in-scope acceptance criterion (AC-xxx) has happy-path case\n\n- Negative coverage: error scenarios (invalid input, not found, state violations)\n\n- Traceability: each explicit AC maps to a case ID or an evidenceGap in contracts/backend-test-case-manifest.json\n\n- Case structure: ID, Title, Precondition, Steps, Expected Result\n\n- No duplicate IDs across files\n\n- Manifest consistency (Critical): every AC claimed in MD case bodies/matrices must match manifest caseId→acIds; never accept 'all cases cover AC-xxx' unless every case maps that AC\n\n## Conditional Coverage (check ONLY if mentioned in upstream analysis):\n\n- Boundary coverage: check ONLY if analyze-inputs-pi mentions value ranges, length limits, numeric bounds, or format constraints\n\n- State transition coverage: check ONLY if analyze-inputs-pi mentions state machine\n\n- Authentication coverage: check ONLY if analyze-inputs-pi mentions auth mechanism\n\n- Timeout coverage: check ONLY if analyze-inputs-pi mentions timeout handling\n\n- Concurrency coverage: check ONLY if analyze-inputs-pi mentions concurrency/idempotency rules\n\n- If not mentioned, do NOT flag as missing\n\n## Do NOT treat as Critical alone:\n\n- Missing test_*.py / automation still planned (expected before generate-backend-pytest-pi)\n\n- Out-of-scope ACs already listed in manifest evidenceGaps (Flyway, frontend e2e, mvn test)\n\n## Verdict Rules:\n\n- All Critical checks pass + Important findings ≤ 2 → VERDICT: pass\n\n- Any Critical fails OR Important > 2 → VERDICT: request-revision\n\n- When requesting revision, list numbered Required revisions concrete enough for revise-backend-cases-pi to edit testcase/md/**.\n\n## Output After Verdict:\n\n1. Coverage Assessment table (AC → full BE-* case IDs) using manifest + MD\n\n2. Findings list (Critical/Important/Informational)\n\n3. Statistics (total cases, positive/negative/boundary breakdown)\n\n4. Required revisions (only when request-revision)\n\n## Constraints:\n\n- Read-only: do not modify files\n\n- Read validated analysis + case manifest artifacts; do not recompute coverage percentages\n\n- Use testcase/md/ files for case review",
210
+ "retryPolicy": {
211
+ "maxAttempts": 3,
212
+ "backoff": "exponential",
213
+ "initialDelayMs": 2000,
214
+ "maxDelayMs": 30000,
215
+ "retryCategories": [
216
+ "timeout",
217
+ "network",
218
+ "rate-limit",
219
+ "unavailable"
220
+ ]
221
+ }
222
+ },
223
+ {
224
+ "id": "revise-backend-cases-pi",
225
+ "depends_on": [
226
+ "review-backend-cases-pi"
227
+ ],
220
228
  "role": "implementer",
221
- "writePolicy": "exclusive",
229
+ "executor": "pi",
222
230
  "toolProfile": "write",
231
+ "complexity": "MED",
232
+ "writePolicy": "exclusive",
223
233
  "writeSet": [
224
234
  "testcase/md/**"
225
235
  ],
@@ -228,28 +238,68 @@
228
238
  ],
229
239
  "forbiddenPaths": [
230
240
  ".harness/**",
241
+ ".harness/dag-runs/**",
231
242
  "artifacts/**"
232
243
  ],
233
- "subtask_prompt": "Read the validated structured artifact pointer from backend-test-analysis-contract-shell and generate cases only from that JSON contract.\n\n\n\n\n\n## Output Steps (do in order):\n\n1. First, output a brief summary: how many modules, how many cases planned per module\n\n2. Then write each test case file under testcase/md/\n\n\n\n## Format Rules:\n\n- Each test case ID: BE-<MODULE>-<NNN> (e.g. BE-ORDER-001)\n\n- Each file covers one module\n\n- Case structure: ID, Title, Precondition, Steps, Expected Result\n\n- Map each case to acceptance criteria (AC-xxx)\n\n\n\n## Coverage Requirements:\n\n- Positive paths: happy path for each acceptance criterion\n\n- Negative paths: error scenarios (invalid input, not found, state violations)\n\n\n\n## Conditional Coverage (include ONLY if mentioned in upstream analysis):\n\n- Boundary conditions: include ONLY if upstream analyze-inputs-pi mentions value ranges, length limits, numeric bounds, or format constraints\n\n- State transitions: include ONLY if upstream analyze-inputs-pi mentions state machine\n\n- Authentication scenarios: include ONLY if upstream analyze-inputs-pi mentions auth mechanism\n\n- Timeout scenarios: include ONLY if upstream analyze-inputs-pi mentions timeout handling\n\n- Concurrency scenarios: include ONLY if upstream analyze-inputs-pi mentions concurrency/idempotency rules\n\n- If not mentioned, do NOT generate these test cases\n\n\n\n## Constraints:\n\n- Stay within writeSet: testcase/md/**\n\n- Do NOT re-read source documents or fall back to free-form analysis — use the validated structured artifact only\n\n\n\n- Do not write root artifacts/**"
244
+ "outputContract": "Apply one case revision and end with one fenced json final Case Manifest v1 block matching the strict field contract.",
245
+ "subtask_prompt": "You are the single backend case revision pass (max one per DAG run).\n\nThis node is only scheduled when review-backend-cases-pi emitted VERDICT: request-revision.\n\n\n\n## Inputs\n\n- First review: review-backend-cases-pi Markdown (VERDICT + Findings + Required revisions)\n\n- Current cases: testcase/md/**\n\n- Validated analysis + case manifest under the current run contracts/\n\n\n\n## Required work\n\n1. Edit only testcase/md/** to address every Critical finding and Required revision item\n\n2. Fix AC matrices to list FULL BE-* ids matching case bodies; remove false 'all cases' AC claims\n\n3. Keep BE-<MODULE>-<NNN> ids stable when possible; do not invent out-of-scope AC coverage\n\n4. Stay within writeSet; do not write pytest or production code\n\n5. End with a short summary of files touched\n\n\n\nDownstream will re-emit and re-validate Case Manifest v1, then run a FINAL review (pass-only gate).\n\nAfter edits, end assistant output with exactly one fenced json block containing the complete final Backend Test Case Manifest v1.\n\nThe final fenced JSON block is authoritative and MUST conform exactly to Backend Test Case Manifest v1.\n\nTop-level keys MUST be exactly: schemaVersion, sourceBinding, cases, evidenceGaps, and optional coverageSummary. Set schemaVersion to numeric 1. Do NOT emit schemaId, manifestType, taskId, modules, acCoverage, brCoverage, dataIsolation, readiness, or other custom top-level keys.\n\nCopy sourceBinding exactly from contracts/backend-test-analysis.json: taskId, requirementPath, requirementSha256, referencePaths, requirementIds. Preserve Unicode paths exactly; never replace characters in source/需求.md or other paths.\n\nEach cases[] item MUST use exactly: caseId, non-empty acIds, title, category, automationStatus; optional endpointRef, ruleRefs, file, symbol, gapReason, evidenceRef. Do NOT use id, module, brIds, endpoint, or priority.\n\ncategory MUST be exactly one of: positive, negative, boundary, state-transition, auth, timeout, concurrency, other.\n\nBefore pytest generation, set automationStatus=planned. Use generated only with both file and symbol. Use skipped or unsupported only with gapReason.\n\nEach evidenceGaps[] item MUST use exactly: optional acId, optional caseId, required description, optional evidenceRef. Every gap requires at least acId or caseId. Do NOT use requirementId, relatedBrIds, or sourceRef.\n\nEvery case must map to at least one semantically applicable explicit AC-* in acIds. If no AC applies, omit that case and bind an evidence gap to the nearest applicable acId or caseId; never emit an unbound informational gap.\n\nacIds MUST exactly match the explicit AC-* values in the written case body; do not infer ACs from Business Rules or summary matrices.\n\nUse full BE-<MODULE>-<NNN> caseId strings. Do not invent coverage percentages; omit coverageSummary unless all deterministic counts are exact.\n\nMinimal shape example: {\"schemaVersion\":1,\"sourceBinding\":{\"taskId\":\"...\",\"requirementPath\":\"source/需求.md\",\"requirementSha256\":\"<64 lowercase hex>\",\"referencePaths\":[],\"requirementIds\":[\"AC-001\"]},\"cases\":[{\"caseId\":\"BE-MODULE-001\",\"acIds\":[\"AC-001\"],\"title\":\"...\",\"category\":\"positive\",\"automationStatus\":\"planned\",\"evidenceRef\":\"testcase/md/module.md\"}],\"evidenceGaps\":[]}",
246
+ "runIf": "$.nodes['review-backend-cases-pi'].firstVerdictLine == 'VERDICT: request-revision'"
234
247
  },
235
248
  {
236
- "id": "emit-backend-case-manifest-pi",
249
+ "id": "backend-test-case-manifest-final-shell",
237
250
  "depends_on": [
238
- "generate-backend-functional-cases-pi",
239
- "backend-test-analysis-contract-shell"
251
+ "revise-backend-cases-pi"
240
252
  ],
241
- "complexity": "MED",
253
+ "role": "verifier",
254
+ "executor": "shell",
255
+ "complexity": "LOW",
256
+ "writePolicy": "read-only",
257
+ "allowedPaths": [
258
+ "testcase/**",
259
+ "docs/test-reports/**"
260
+ ],
261
+ "forbiddenPaths": [
262
+ ".harness/**",
263
+ ".harness/dag-runs/**",
264
+ "artifacts/**"
265
+ ],
266
+ "outputContract": "Validated run-owned Backend Test Case Manifest v1 at contracts/backend-test-case-manifest.json (schemaId backend-test-case-manifest-v1) with deterministic AC coverage.",
267
+ "subtask_prompt": "Materialize and validate Backend Test Case Manifest v1; fail closed on duplicate IDs, unknown AC, missing AC coverage without gap, or skipped without gapReason.",
268
+ "shell": {
269
+ "commands": [],
270
+ "jsonArtifactGate": {
271
+ "fromNodeId": "revise-backend-cases-pi",
272
+ "schemaId": "backend-test-case-manifest-v1",
273
+ "artifactName": "backend-test-case-manifest.json",
274
+ "outputDir": "contracts"
275
+ },
276
+ "cwd": ".",
277
+ "timeoutMs": 60000
278
+ },
279
+ "runIf": "$.nodes['review-backend-cases-pi'].firstVerdictLine == 'VERDICT: request-revision'"
280
+ },
281
+ {
282
+ "id": "review-backend-cases-final-pi",
283
+ "depends_on": [
284
+ "backend-test-case-manifest-final-shell",
285
+ "validate-backend-test-contracts-shell"
286
+ ],
287
+ "role": "reviewer",
242
288
  "executor": "pi",
243
- "role": "scout",
289
+ "complexity": "HIGH",
244
290
  "writePolicy": "read-only",
245
291
  "allowedPaths": [
246
- "**"
292
+ "testcase/**",
293
+ "docs/test-reports/**"
247
294
  ],
248
295
  "forbiddenPaths": [
249
296
  ".harness/**",
297
+ ".harness/dag-runs/**",
250
298
  "artifacts/**"
251
299
  ],
252
- "outputContract": "Pure Backend Test Case Manifest v1 JSON (schema docs/templates/backend-test-case-manifest.schema.json). No file writes; model must not write .harness/**.",
300
+ "outputContract": "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision after at most one revision; followed by Findings and Coverage Assessment. No file writes. Final gate accepts pass only.",
301
+ "subtask_prompt": "Final review of backend functional cases under testcase/md/ and the re-validated Case Manifest v1 after at most one revision pass.\n\n## Mandatory First Line:\n\nFirst non-empty line must be exactly: VERDICT: pass or VERDICT: request-revision\n\n## Review Checklist:\n\n- ID format: every case uses BE-<MODULE>-<NNN> (full ids only in bodies and matrices)\n\n- Positive coverage: each in-scope acceptance criterion (AC-xxx) has happy-path case\n\n- Negative coverage: error scenarios (invalid input, not found, state violations)\n\n- Traceability: each explicit AC maps to a case ID or an evidenceGap in contracts/backend-test-case-manifest.json\n\n- Case structure: ID, Title, Precondition, Steps, Expected Result\n\n- No duplicate IDs across files\n\n- Manifest consistency (Critical): every AC claimed in MD case bodies/matrices must match manifest caseId→acIds; never accept 'all cases cover AC-xxx' unless every case maps that AC\n\n## Conditional Coverage (check ONLY if mentioned in upstream analysis):\n\n- Boundary coverage: check ONLY if analyze-inputs-pi mentions value ranges, length limits, numeric bounds, or format constraints\n\n- State transition coverage: check ONLY if analyze-inputs-pi mentions state machine\n\n- Authentication coverage: check ONLY if analyze-inputs-pi mentions auth mechanism\n\n- Timeout coverage: check ONLY if analyze-inputs-pi mentions timeout handling\n\n- Concurrency coverage: check ONLY if analyze-inputs-pi mentions concurrency/idempotency rules\n\n- If not mentioned, do NOT flag as missing\n\n## Do NOT treat as Critical alone:\n\n- Missing test_*.py / automation still planned (expected before generate-backend-pytest-pi)\n\n- Out-of-scope ACs already listed in manifest evidenceGaps (Flyway, frontend e2e, mvn test)\n\n## Verdict Rules:\n\n- All Critical checks pass + Important findings ≤ 2 → VERDICT: pass\n\n- Any Critical fails OR Important > 2 → VERDICT: request-revision\n\n- This is the FINAL review after one revision opportunity; remaining Critical issues must still request-revision (final gate will stop the DAG).\n\n## Output After Verdict:\n\n1. Coverage Assessment table (AC → full BE-* case IDs) using manifest + MD\n\n2. Findings list (Critical/Important/Informational)\n\n3. Statistics (total cases, positive/negative/boundary breakdown)\n\n## Constraints:\n\n- Read-only: do not modify files\n\n- Read validated analysis + case manifest artifacts; do not recompute coverage percentages\n\n- Use testcase/md/ files for case review",
302
+ "runIf": "$.nodes['review-backend-cases-pi'].firstVerdictLine == 'VERDICT: request-revision'",
253
303
  "retryPolicy": {
254
304
  "maxAttempts": 3,
255
305
  "backoff": "exponential",
@@ -261,57 +311,97 @@
261
311
  "rate-limit",
262
312
  "unavailable"
263
313
  ]
264
- },
265
- "subtask_prompt": "Emit Backend Test Case Manifest v1 as pure JSON (or one fenced json block with no trailing text).\n\nRead-only: use validated contracts/backend-test-analysis.json pointer + testcase/md/** only. Do not write repository files or .harness/**.\n\nsourceBinding must match the analysis contract / DAG source binding exactly (taskId, requirementPath, requirementSha256, referencePaths, requirementIds).\n\nFor each functional case under testcase/md/: caseId BE-<MODULE>-<NNN>, acIds[], title, category, automationStatus.\n\nAfter case generation (pre-pytest), default automationStatus=planned. Use skipped/unsupported only with gapReason. Use generated only when file+symbol already exist.\n\nevidenceGaps: structured gaps for explicit AC-* that cannot be mapped to a case.\n\nDo NOT invent coverage percentages. Optional coverageSummary must match deterministic counts (gate recomputes/validates).\n\nNo secrets or credential-shaped fields."
314
+ }
266
315
  },
267
316
  {
268
- "id": "backend-test-case-manifest-shell",
317
+ "id": "review-backend-cases-gate-shell",
269
318
  "depends_on": [
270
- "emit-backend-case-manifest-pi"
319
+ "review-backend-cases-pi",
320
+ "review-backend-cases-final-pi"
271
321
  ],
272
- "complexity": "LOW",
273
- "executor": "shell",
322
+ "dependsPolicy": "all-or-condition-skip",
274
323
  "role": "verifier",
324
+ "executor": "shell",
325
+ "complexity": "LOW",
275
326
  "writePolicy": "read-only",
276
327
  "allowedPaths": [
277
- "**"
328
+ "testcase/**",
329
+ "docs/test-reports/**"
278
330
  ],
279
331
  "forbiddenPaths": [
280
332
  ".harness/**",
333
+ ".harness/dag-runs/**",
281
334
  "artifacts/**"
282
335
  ],
283
- "outputContract": "Validated run-owned Backend Test Case Manifest v1 at contracts/backend-test-case-manifest.json (schemaId backend-test-case-manifest-v1) with deterministic AC coverage.",
284
- "subtask_prompt": "Materialize and validate Backend Test Case Manifest v1; fail closed on duplicate IDs, unknown AC, missing AC coverage without gap, or skipped without gapReason.",
336
+ "outputContract": "Deterministic backend case review gate: exit 0 only when the effective review emits VERDICT: pass (sole authorization for generate-backend-pytest-pi). Prefers final review JSON when present (revision path); else first review (pass path).",
337
+ "subtask_prompt": "Deterministic gate: block pytest generation unless the effective backend case review (final after revision, else first) emitted VERDICT: pass.",
285
338
  "shell": {
286
339
  "commands": [],
287
- "jsonArtifactGate": {
288
- "fromNodeId": "emit-backend-case-manifest-pi",
289
- "schemaId": "backend-test-case-manifest-v1",
290
- "artifactName": "backend-test-case-manifest.json",
291
- "outputDir": "contracts"
340
+ "verdictGate": {
341
+ "fromNodeId": "review-backend-cases-final-pi",
342
+ "fallbackFromNodeIds": [
343
+ "review-backend-cases-pi"
344
+ ],
345
+ "accept": [
346
+ "VERDICT: pass"
347
+ ],
348
+ "label": "backend case effective review",
349
+ "lineMode": "first-verdict-line"
292
350
  },
293
351
  "cwd": ".",
294
352
  "timeoutMs": 60000
295
353
  }
296
354
  },
297
355
  {
298
- "id": "review-backend-cases-pi",
356
+ "id": "generate-backend-pytest-pi",
299
357
  "depends_on": [
300
- "backend-test-case-manifest-shell",
301
- "backend-test-analysis-contract-shell"
358
+ "review-backend-cases-gate-shell",
359
+ "validate-backend-test-contracts-shell"
302
360
  ],
303
- "complexity": "HIGH",
361
+ "role": "implementer",
304
362
  "executor": "pi",
363
+ "toolProfile": "write",
364
+ "complexity": "HIGH",
365
+ "writePolicy": "exclusive",
366
+ "writeSet": [
367
+ "testcase/**/test_*.py",
368
+ "testcase/**/helpers/**",
369
+ "testcase/**/factories/**"
370
+ ],
371
+ "allowedPaths": [
372
+ "testcase/**",
373
+ "docs/test-reports/**"
374
+ ],
375
+ "forbiddenPaths": [
376
+ ".harness/**",
377
+ ".harness/dag-runs/**",
378
+ "artifacts/**"
379
+ ],
380
+ "subtask_prompt": "Convert the reviewed test cases under testcase/md/ into pytest automation code.\n\n\n\n## Inputs (MUST use validated contracts):\n\n- Reviewed cases under testcase/md/ (after review-backend-cases-gate-shell).\n\n- Validated Backend Test Analysis v1 under the current run contracts/ (analysis gate).\n\n- Validated Backend Test Execution Contract v1 under contracts/backend-test-execution.json (execution gate).\n\nUse only fixture names, env NAMES, testRoot, targetMode, and field/API facts already present in those contracts or reviewed cases. Do not invent production credentials or secret values.\n\n\n\n## Output Steps (do in order):\n\n1. First, output a brief summary: how many files, how many test functions planned\n\n2. Then write each test file under testcase/\n\n\n\n## Format Rules:\n\n- File prefix: test_<module>.py\n\n- Function name: test_BE_<MODULE>_<NNN>_<description>\n\n- Docstring first line: BE-<MODULE>-<NNN>: <Case Title>\n\n- 1:1 mapping: each functional case → one pytest function\n\n\n\n## Implementation Rules:\n\n- Use assert statements, not unittest assertions\n\n- Use @pytest.mark.parametrize for boundary cases when the case defines edge values\n\n- Use markers: @pytest.mark.positive, @pytest.mark.negative, @pytest.mark.boundary\n\n\n\n## Test Data Preparation Rules (MUST follow):\n\n\n\n### When Setup is Needed\n\nSetup phase is REQUIRED only when test cases need pre-existing data:\n\n- Query/Read APIs: need data to exist before querying\n\n- Update/Delete APIs: need data to exist before modifying\n\n- State transition tests: need data in specific state\n\n\n\nSetup phase is NOT needed for:\n\n- Create APIs: testing the creation itself\n\n- Validation tests: testing input validation with invalid data\n\n\n\n### Data Setup Strategy\n\nWhen setup is needed:\n\n1. Prefer function-scoped fixtures for isolation; use module/session scope only when cases explicitly share immutable fixtures\n\n2. Prefer API-based setup from the upstream analyze-inputs-pi API list and reviewed cases\n\n3. If a required helper/factory is missing, create NEW files only under testcase/**/helpers/** or testcase/**/factories/**\n\n\n\n### Data Construction Priority\n\n1. API-first: construct data via documented APIs from analyze-inputs-pi / reviewed cases\n\n2. Reuse existing conftest fixtures when present (read-only)\n\n3. Direct DB writes are LAST RESORT and only if conftest already exposes a safe test DB fixture with rollback/isolation\n\n4. If neither API nor safe DB fixture exists, skip the case with an explicit gap note — do NOT invent production DB credentials or write live data\n\n\n\n### API Data Construction\n\n- Prefer the analyze-inputs-pi API Endpoints section and reviewed cases for method/path/fields\n\n- Chain API calls only when cases document multi-step preconditions\n\n- Store created resource IDs in fixtures for reuse\n\n- Do NOT broadly search host route/controller trees for secrets, .env, private keys, or production configs\n\n- Read host API definitions only when needed to resolve a field name already referenced by reviewed cases; stay out of credential/config paths\n\n\n\n### Database Data Construction (restricted)\n\n- Allowed only via existing conftest test-DB fixtures with transaction rollback or equivalent isolation\n\n- Never hardcode connection strings, passwords, tokens, or cloud credentials\n\n- Never target production/shared non-test databases\n\n- If isolation is unclear, report the gap instead of writing DB rows\n\n\n\n## Assertion Rules (MUST follow):\n\n\n\n### Positive Path\n\nMUST assert ALL of the following:\n\n1. HTTP status code: as defined in API spec (e.g. 200, 201)\n\n2. Response structure: key fields exist in response body\n\n3. Specific values: each field equals expected value from test case\n\n4. Data type: each field is correct type\n\n\n\n### Negative Path\n\nMUST assert ALL of the following:\n\n1. HTTP status code: as defined in API spec (e.g. 400, 404, 500)\n\n2. Error code field: field name from API spec (e.g. code, error_code, errcode, ret)\n\n3. Error message field: field name from API spec (e.g. message, msg, errmsg, error)\n\n\n\n### Field Name Resolution\n\nField names MUST come from the upstream analyze-inputs-pi output (API Endpoints section) or reviewed cases, NOT guessed. For example:\n\n- If API spec defines {\"ret\": 0, \"msg\": \"success\"}, assert response.json()['ret'] and response.json()['msg']\n\n- If API spec defines {\"code\": 4001, \"message\": \"error\"}, assert response.json()['code'] and response.json()['message']\n\n\n\n## Conditional Implementation (include ONLY if test cases exist):\n\n- Authentication tests: implement ONLY if testcase/md/ contains auth-related cases\n\n- Timeout tests: implement ONLY if testcase/md/ contains timeout-related cases\n\n- Boundary tests: implement ONLY when cases define value ranges, length limits, or format constraints\n\n- Use @pytest.mark.auth for auth tests, @pytest.mark.timeout for timeout tests\n\n- If no such cases exist, do NOT add these tests\n\n\n\n## Constraints:\n\n- Only create NEW files under writeSet: testcase/**/test_*.py, testcase/**/helpers/**, testcase/**/factories/**\n\n- Do NOT modify existing framework files (conftest.py, pytest.ini, pyproject.toml, setup.cfg, __init__.py)\n\n- If a test filename exists, add suffix: test_order.py → test_order_01.py\n\n- Do NOT re-read source documents — use reviewed cases under testcase/md/ and upstream analyze-inputs-pi output only\n\n- Read existing conftest.py/pytest.ini to understand conventions, but do NOT modify them"
381
+ },
382
+ {
383
+ "id": "review-generated-backend-pytest-pi",
384
+ "depends_on": [
385
+ "generate-backend-pytest-pi",
386
+ "validate-backend-test-contracts-shell",
387
+ "backend-test-case-manifest-shell",
388
+ "backend-test-case-manifest-final-shell"
389
+ ],
390
+ "dependsPolicy": "all-or-condition-skip",
305
391
  "role": "reviewer",
392
+ "executor": "pi",
393
+ "complexity": "MED",
306
394
  "writePolicy": "read-only",
307
395
  "allowedPaths": [
308
- "**"
396
+ "testcase/**"
309
397
  ],
310
398
  "forbiddenPaths": [
311
399
  ".harness/**",
400
+ ".harness/dag-runs/**",
312
401
  "artifacts/**"
313
402
  ],
314
- "outputContract": "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision; followed by Findings and Coverage Assessment. No file writes.",
403
+ "outputContract": "Pure Backend Test Semantic Review v1 JSON: verdict, findings[], summary. No file writes.",
404
+ "subtask_prompt": "Review generated pytest semantics before the first execution.\n\nUse only compact authoritative inputs: contracts/backend-test-analysis.json, contracts/backend-test-case-manifest.json, testcase/md/**, and generated testcase/**/test_*.py/helpers/factories.\n\nReturn exactly one pure JSON object with only verdict, findings, summary; no Markdown fence or surrounding prose.\n\nverdict must be pass or request-revision. Each findings[] item must contain exactly severity, caseId, testFile, testSymbol, contractRefs, issue, requiredChange.\n\nseverity must be exactly Critical, Important, or Informational; contractRefs must be a non-empty string array. A request-revision verdict requires at least one finding; pass must not contain Critical findings.\n\nMinimal shape: {\"verdict\":\"pass\",\"findings\":[],\"summary\":\"No contract-backed semantic contradiction found.\"}\n\nCheck responseBody.kind (array vs object/items), ordering, field comparison (especially parseable-only date-time precision), documented status/error fields, and each caseId→symbol assertion meaning.\n\nDo not use aliases such as file, symbol, refs, finding, or requiredFix; the strict contract requires testFile, testSymbol, contractRefs, issue, requiredChange.\n\nrequest-revision only for concrete semantic contradiction with reviewed cases/formal analysis evidence. No style findings.\n\nRead-only; do not edit tests or production code.",
315
405
  "retryPolicy": {
316
406
  "maxAttempts": 3,
317
407
  "backoff": "exponential",
@@ -323,190 +413,378 @@
323
413
  "rate-limit",
324
414
  "unavailable"
325
415
  ]
326
- },
327
- "subtask_prompt_markdown": "./backend-test-dag.review-cases.prompt.md"
416
+ }
328
417
  },
329
418
  {
330
- "id": "review-backend-cases-gate-shell",
419
+ "id": "validate-semantic-review-and-traceability-shell",
331
420
  "depends_on": [
332
- "review-backend-cases-pi"
421
+ "review-generated-backend-pytest-pi",
422
+ "backend-test-case-manifest-shell",
423
+ "backend-test-case-manifest-final-shell"
333
424
  ],
334
- "complexity": "LOW",
335
- "executor": "shell",
425
+ "dependsPolicy": "all-or-condition-skip",
336
426
  "role": "verifier",
427
+ "executor": "shell",
428
+ "complexity": "LOW",
337
429
  "writePolicy": "read-only",
338
430
  "allowedPaths": [
339
- "**"
431
+ "testcase/**",
432
+ "docs/test-reports/**"
340
433
  ],
341
434
  "forbiddenPaths": [
342
435
  ".harness/**",
436
+ ".harness/dag-runs/**",
343
437
  "artifacts/**"
344
438
  ],
345
- "outputContract": "Deterministic backend case review gate: exit 0 only when review-backend-cases-pi emits VERDICT: pass.",
346
- "subtask_prompt": "Deterministic gate: block pytest generation unless backend case review emitted VERDICT: pass.",
439
+ "outputContract": "Materialize initial semantic review and validate pytest traceability.",
440
+ "subtask_prompt": "Validate initial semantic review and traceability fail-closed.",
347
441
  "shell": {
348
442
  "commands": [],
349
- "verdictGate": {
350
- "fromNodeId": "review-backend-cases-pi",
351
- "accept": [
352
- "VERDICT: pass"
353
- ],
354
- "label": "backend case review",
355
- "lineMode": "first-verdict-line"
356
- },
443
+ "backendTestPipeline": "semantic-initial",
357
444
  "cwd": ".",
358
445
  "timeoutMs": 60000
359
446
  }
360
447
  },
361
448
  {
362
- "id": "generate-backend-pytest-pi",
449
+ "id": "revise-generated-backend-pytest-pi",
363
450
  "depends_on": [
364
- "review-backend-cases-gate-shell",
365
- "backend-test-execution-contract-shell"
451
+ "validate-semantic-review-and-traceability-shell"
366
452
  ],
367
- "complexity": "HIGH",
368
- "executor": "pi",
369
453
  "role": "implementer",
370
- "writePolicy": "exclusive",
454
+ "executor": "pi",
371
455
  "toolProfile": "write",
456
+ "complexity": "MED",
457
+ "writePolicy": "exclusive",
372
458
  "writeSet": [
373
459
  "testcase/**/test_*.py",
374
460
  "testcase/**/helpers/**",
375
461
  "testcase/**/factories/**"
376
462
  ],
377
463
  "allowedPaths": [
378
- "testcase/**",
379
- "**"
464
+ "testcase/**/test_*.py",
465
+ "testcase/**/helpers/**",
466
+ "testcase/**/factories/**"
467
+ ],
468
+ "forbiddenPaths": [
469
+ ".harness/**",
470
+ ".harness/dag-runs/**",
471
+ "artifacts/**",
472
+ "apps/**",
473
+ "src/**",
474
+ "testcase/md/**",
475
+ "**/conftest.py",
476
+ "**/pytest.ini"
477
+ ],
478
+ "outputContract": "Apply one bounded semantic correction to generated pytest from validated findings only.",
479
+ "subtask_prompt": "This is the only pre-execution generated-pytest semantic revision (1/1).\n\nRead contracts/backend-test-semantic-review.json and edit only the cited generated test files/helpers/factories.\n\nPreserve case IDs, symbols, test count, target mode, base URL and real-service path.\n\nDo not delete tests, add skip/xfail, swallow failures, substitute mocks, or weaken assertions beyond the formal comparison/shape contract.\n\nDo not modify product code, testcase/md/**, conftest.py, pytest.ini, or .harness/**.",
480
+ "runIf": "$.nodes['validate-semantic-review-and-traceability-shell'].json.verdict == 'request-revision'"
481
+ },
482
+ {
483
+ "id": "review-generated-backend-pytest-final-pi",
484
+ "depends_on": [
485
+ "revise-generated-backend-pytest-pi"
486
+ ],
487
+ "dependsPolicy": "all-or-condition-skip",
488
+ "role": "reviewer",
489
+ "executor": "pi",
490
+ "complexity": "MED",
491
+ "writePolicy": "read-only",
492
+ "allowedPaths": [
493
+ "testcase/**"
380
494
  ],
381
495
  "forbiddenPaths": [
382
496
  ".harness/**",
497
+ ".harness/dag-runs/**",
383
498
  "artifacts/**"
384
499
  ],
385
- "subtask_prompt_markdown": "./backend-test-dag.generate-pytest.prompt.md"
500
+ "outputContract": "Pure Backend Test Semantic Review v1 JSON: verdict, findings[], summary. No file writes.",
501
+ "subtask_prompt": "Final semantic review after the single generated-pytest revision.\n\nUse only compact authoritative inputs: contracts/backend-test-analysis.json, contracts/backend-test-case-manifest.json, testcase/md/**, and generated testcase/**/test_*.py/helpers/factories.\n\nReturn exactly one pure JSON object with only verdict, findings, summary; no Markdown fence or surrounding prose.\n\nverdict must be pass or request-revision. Each findings[] item must contain exactly severity, caseId, testFile, testSymbol, contractRefs, issue, requiredChange.\n\nseverity must be exactly Critical, Important, or Informational; contractRefs must be a non-empty string array. A request-revision verdict requires at least one finding; pass must not contain Critical findings.\n\nMinimal shape: {\"verdict\":\"pass\",\"findings\":[],\"summary\":\"No contract-backed semantic contradiction found.\"}\n\nCheck responseBody.kind (array vs object/items), ordering, field comparison (especially parseable-only date-time precision), documented status/error fields, and each caseId→symbol assertion meaning.\n\nDo not use aliases such as file, symbol, refs, finding, or requiredFix; the strict contract requires testFile, testSymbol, contractRefs, issue, requiredChange.\n\nrequest-revision only for concrete semantic contradiction with reviewed cases/formal analysis evidence. No style findings.\n\nRead-only; do not edit tests or production code.",
502
+ "runIf": "$.nodes['validate-semantic-review-and-traceability-shell'].json.verdict == 'request-revision'",
503
+ "retryPolicy": {
504
+ "maxAttempts": 3,
505
+ "backoff": "exponential",
506
+ "initialDelayMs": 2000,
507
+ "maxDelayMs": 30000,
508
+ "retryCategories": [
509
+ "timeout",
510
+ "network",
511
+ "rate-limit",
512
+ "unavailable"
513
+ ]
514
+ }
386
515
  },
387
516
  {
388
- "id": "backend-test-traceability-gate-shell",
517
+ "id": "materialize-final-semantic-review-shell",
389
518
  "depends_on": [
390
- "generate-backend-pytest-pi",
391
- "backend-test-case-manifest-shell"
519
+ "review-generated-backend-pytest-final-pi"
392
520
  ],
393
- "complexity": "LOW",
521
+ "role": "verifier",
394
522
  "executor": "shell",
523
+ "complexity": "LOW",
524
+ "writePolicy": "read-only",
525
+ "allowedPaths": [
526
+ "testcase/**",
527
+ "docs/test-reports/**"
528
+ ],
529
+ "forbiddenPaths": [
530
+ ".harness/**",
531
+ ".harness/dag-runs/**",
532
+ "artifacts/**"
533
+ ],
534
+ "runIf": "$.nodes['validate-semantic-review-and-traceability-shell'].json.verdict == 'request-revision'",
535
+ "outputContract": "Materialize final semantic review and re-check traceability.",
536
+ "subtask_prompt": "Validate final semantic review and traceability.",
537
+ "shell": {
538
+ "commands": [],
539
+ "backendTestPipeline": "semantic-final",
540
+ "cwd": ".",
541
+ "timeoutMs": 60000
542
+ }
543
+ },
544
+ {
545
+ "id": "backend-test-semantic-final-gate-shell",
546
+ "depends_on": [
547
+ "validate-semantic-review-and-traceability-shell",
548
+ "materialize-final-semantic-review-shell"
549
+ ],
550
+ "dependsPolicy": "all-or-condition-skip",
395
551
  "role": "verifier",
552
+ "executor": "shell",
553
+ "complexity": "LOW",
396
554
  "writePolicy": "read-only",
397
555
  "allowedPaths": [
398
- "**"
556
+ "testcase/**",
557
+ "docs/test-reports/**"
399
558
  ],
400
559
  "forbiddenPaths": [
401
560
  ".harness/**",
561
+ ".harness/dag-runs/**",
402
562
  "artifacts/**"
403
563
  ],
404
- "outputContract": "Deterministic traceability: generated cases have real file/symbol; skipped/unsupported have gapReason; convention symbols scanned under testcase/**/test_*.py.",
405
- "subtask_prompt": "Fail closed when generated automation claims do not resolve to workspace pytest symbols, or skip/unsupported lacks gapReason.",
564
+ "outputContract": "Pass-only effective semantic review gate with final traceability after revision.",
565
+ "subtask_prompt": "Accept initial semantic pass or validate the single final review and traceability.",
406
566
  "shell": {
407
567
  "commands": [
408
- "backend-test-traceability-gate"
568
+ "test -n \"${HARNESS_DAG_RUN_DIR:-}\" || exit 2; node -e 'const fs=require(\"fs\"),path=require(\"path\");const r=process.env.HARNESS_DAG_RUN_DIR;const f=path.join(r,\"contracts\",\"backend-test-semantic-review-final.json\");const i=path.join(r,\"contracts\",\"backend-test-semantic-review.json\");const v=JSON.parse(fs.readFileSync(fs.existsSync(f)?f:i,\"utf8\"));if(v.verdict!==\"pass\")throw new Error(\"backend pytest semantic review did not pass\");'"
409
569
  ],
410
570
  "cwd": ".",
411
571
  "timeoutMs": 60000
412
572
  }
413
573
  },
414
574
  {
415
- "id": "execute-backend-pytest-shell",
575
+ "id": "execute-and-parse-backend-pytest-initial-shell",
416
576
  "depends_on": [
417
- "backend-test-traceability-gate-shell",
418
- "backend-test-execution-contract-shell"
577
+ "backend-test-semantic-final-gate-shell",
578
+ "validate-backend-test-contracts-shell"
419
579
  ],
420
- "complexity": "LOW",
421
- "executor": "shell",
422
580
  "role": "verifier",
581
+ "executor": "shell",
582
+ "complexity": "LOW",
423
583
  "writePolicy": "read-only",
424
584
  "allowedPaths": [
425
- "**"
585
+ "testcase/**",
586
+ "docs/test-reports/**"
426
587
  ],
427
588
  "forbiddenPaths": [
428
589
  ".harness/**",
590
+ ".harness/dag-runs/**",
429
591
  "artifacts/**"
430
592
  ],
431
593
  "outputContract": "Archived pytest stdout/stderr; raw pytestExitCode side-channel + JUnit at $HARNESS_DAG_RUN_DIR/reports/**. Exit 0/1 with non-empty JUnit finishes the node so parse/classify/retrospect can run; assertion failures remain recorded in exit file.",
432
594
  "subtask_prompt": "Run pytest for the backend test suite; write JUnit + pytestExitCode evidence only under the current HARNESS_DAG_RUN_DIR/reports/.",
433
595
  "shell": {
434
596
  "commands": [
435
- "test -n \"${HARNESS_DAG_RUN_DIR:-}\" || { echo \"missing HARNESS_DAG_RUN_DIR for backend pytest preflight\" >&2; exit 2; }; CONTRACT=\"${HARNESS_DAG_RUN_DIR}/contracts/backend-test-execution.json\"; test -f \"${CONTRACT}\" || { echo \"missing backend-test execution contract: ${CONTRACT}\" >&2; exit 2; }; node -e 'const fs=require(\"fs\");const path=require(\"path\");const contractPath=process.argv[1];const contract=JSON.parse(fs.readFileSync(contractPath,\"utf8\"));const expected=\"testcase\";const errors=[];if(contract.framework!==\"pytest\") errors.push(\"framework must be pytest\");const testRoot=String(contract.testRoot||\"\");if(!testRoot||testRoot.includes(\"..\")||path.isAbsolute(testRoot)) errors.push(\"unsafe testRoot\");if(testRoot.replace(/\\/+$/,\"\")!==expected.replace(/\\/+$/,\"\")) errors.push(\"testRoot mismatch vs frozen command: \"+testRoot+\" !== \"+expected);const rootAbs=path.resolve(process.cwd(),testRoot);if(!fs.existsSync(rootAbs)) errors.push(\"testRoot does not exist: \"+testRoot);if(Array.isArray(contract.evidenceGaps)&&contract.evidenceGaps.length) errors.push(\"evidenceGaps present: \"+contract.evidenceGaps.length);if(contract.targetMode===\"in-process\"&&!(Array.isArray(contract.existingFixtures)&&contract.existingFixtures.length)) errors.push(\"in-process requires existingFixtures\");for (const name of (contract.requiredEnvNames||[])) { if(!process.env[name]) errors.push(\"required env missing: \"+name); }if(contract.targetMode===\"external-running-service\"){ const n=contract.baseUrlEnvName; if(!n||!process.env[n]) errors.push(\"external base URL env missing: \"+String(n||\"<empty>\")); }if(contract.targetMode===\"managed-command\" && !(contract.managedCommand&&contract.managedCommand.sourceRef)) errors.push(\"managed-command requires sourceRef evidence\");if(errors.length){ console.error(errors.join(\"; \")); process.exit(2);} console.log(\"backend-test preflight ok: framework=pytest testRoot=\"+testRoot+\" targetMode=\"+contract.targetMode);' \"${CONTRACT}\"; REPORT=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-junit.xml\"; EXIT_FILE=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt\"; mkdir -p \"$(dirname \"${REPORT}\")\"; PYTHONDONTWRITEBYTECODE=1 python -m pytest testcase/ -v -p no:cacheprovider --junitxml=\"${REPORT}\"; STATUS=$?; printf \"%s\" \"${STATUS}\" > \"${EXIT_FILE}\"; printf \"JUnit report: %s\\n\" \"${REPORT}\"; printf \"pytestExitCode=%s\\n\" \"${STATUS}\"; if { [ \"${STATUS}\" -eq 0 ] || [ \"${STATUS}\" -eq 1 ]; } && [ -s \"${REPORT}\" ]; then exit 0; fi; exit \"${STATUS}\""
597
+ "test -n \"${HARNESS_DAG_RUN_DIR:-}\" || { echo \"missing HARNESS_DAG_RUN_DIR for backend pytest preflight\" >&2; exit 2; }; CONTRACT=\"${HARNESS_DAG_RUN_DIR}/contracts/backend-test-execution.json\"; test -f \"${CONTRACT}\" || { echo \"missing backend-test execution contract: ${CONTRACT}\" >&2; exit 2; }; node -e 'const fs=require(\"fs\");const path=require(\"path\");const contractPath=process.argv[1];const contract=JSON.parse(fs.readFileSync(contractPath,\"utf8\"));const expected=\"testcase\";const errors=[];if(contract.framework!==\"pytest\") errors.push(\"framework must be pytest\");const testRoot=String(contract.testRoot||\"\");if(!testRoot||testRoot.includes(\"..\")||path.isAbsolute(testRoot)) errors.push(\"unsafe testRoot\");if(testRoot.replace(/\\/+$/,\"\")!==expected.replace(/\\/+$/,\"\")) errors.push(\"testRoot mismatch vs frozen command: \"+testRoot+\" !== \"+expected);const rootAbs=path.resolve(process.cwd(),testRoot);if(!fs.existsSync(rootAbs)) errors.push(\"testRoot does not exist: \"+testRoot);if(contract.targetMode===\"in-process\"&&!(Array.isArray(contract.existingFixtures)&&contract.existingFixtures.length)) errors.push(\"in-process requires existingFixtures\");for (const name of (contract.requiredEnvNames||[])) { if(!process.env[name]) errors.push(\"required env missing: \"+name); }if(contract.targetMode===\"external-running-service\"){ const n=contract.baseUrlEnvName; if(!n||!process.env[n]) errors.push(\"external base URL env missing: \"+String(n||\"<empty>\")); }if(contract.targetMode===\"managed-command\" && !(contract.managedCommand&&contract.managedCommand.sourceRef)) errors.push(\"managed-command requires sourceRef evidence\");if(errors.length){ console.error(errors.join(\"; \")); process.exit(2);} console.log(\"backend-test preflight ok: framework=pytest testRoot=\"+testRoot+\" targetMode=\"+contract.targetMode);' \"${CONTRACT}\"; REPORT=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-initial-junit.xml\"; EXIT_FILE=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-initial-pytest-exit.txt\"; mkdir -p \"$(dirname \"${REPORT}\")\"; PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python -m pytest testcase/ -v -p no:cacheprovider --junitxml=\"${REPORT}\"; STATUS=$?; printf \"%s\" \"${STATUS}\" > \"${EXIT_FILE}\"; printf \"JUnit report: %s\\n\" \"${REPORT}\"; printf \"pytestExitCode=%s\\n\" \"${STATUS}\"; if { [ \"${STATUS}\" -eq 0 ] || [ \"${STATUS}\" -eq 1 ]; } && [ -s \"${REPORT}\" ]; then exit 0; fi; exit \"${STATUS}\""
436
598
  ],
599
+ "envAllowlist": [],
437
600
  "verifyEvidence": {
438
601
  "phase": "final",
439
602
  "quota": "full",
440
603
  "commandSource": "inline",
441
604
  "commandCount": 1,
442
605
  "commandLabels": [
443
- "test -n \"${HARNESS_DAG_RUN_DIR:-}\" || { echo \"missing HARNESS_DAG_RUN_DIR for backend pytest preflight\" >&2; exit 2; }; CONTRACT=\"${HARNESS_DAG_RUN_DIR}/contracts/backend-test-execution.json\"; test -f \"${CONTRACT}\" || { echo \"missing backend-test execution contract: ${CONTRACT}\" >&2; exit 2; }; node -e 'const fs=require(\"fs\");const path=require(\"path\");const contractPath=process.argv[1];const contract=JSON.parse(fs.readFileSync(contractPath,\"utf8\"));const expected=\"testcase\";const errors=[];if(contract.framework!==\"pytest\") errors.push(\"framework must be pytest\");const testRoot=String(contract.testRoot||\"\");if(!testRoot||testRoot.includes(\"..\")||path.isAbsolute(testRoot)) errors.push(\"unsafe testRoot\");if(testRoot.replace(/\\/+$/,\"\")!==expected.replace(/\\/+$/,\"\")) errors.push(\"testRoot mismatch vs frozen command: \"+testRoot+\" !== \"+expected);const rootAbs=path.resolve(process.cwd(),testRoot);if(!fs.existsSync(rootAbs)) errors.push(\"testRoot does not exist: \"+testRoot);if(Array.isArray(contract.evidenceGaps)&&contract.evidenceGaps.length) errors.push(\"evidenceGaps present: \"+contract.evidenceGaps.length);if(contract.targetMode===\"in-process\"&&!(Array.isArray(contract.existingFixtures)&&contract.existingFixtures.length)) errors.push(\"in-process requires existingFixtures\");for (const name of (contract.requiredEnvNames||[])) { if(!process.env[name]) errors.push(\"required env missing: \"+name); }if(contract.targetMode===\"external-running-service\"){ const n=contract.baseUrlEnvName; if(!n||!process.env[n]) errors.push(\"external base URL env missing: \"+String(n||\"<empty>\")); }if(contract.targetMode===\"managed-command\" && !(contract.managedCommand&&contract.managedCommand.sourceRef)) errors.push(\"managed-command requires sourceRef evidence\");if(errors.length){ console.error(errors.join(\"; \")); process.exit(2);} console.log(\"backend-test preflight ok: framework=pytest testRoot=\"+testRoot+\" targetMode=\"+contract.targetMode);' \"${CONTRACT}\"; REPORT=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-junit.xml\"; EXIT_FILE=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt\"; mkdir -p \"$(dirname \"${REPORT}\")\"; PYTHONDONTWRITEBYTECODE=1 python -m pytest testcase/ -v -p no:cacheprovider --junitxml=\"${REPORT}\"; STATUS=$?; printf \"%s\" \"${STATUS}\" > \"${EXIT_FILE}\"; printf \"JUnit report: %s\\n\" \"${REPORT}\"; printf \"pytestExitCode=%s\\n\" \"${STATUS}\"; if { [ \"${STATUS}\" -eq 0 ] || [ \"${STATUS}\" -eq 1 ]; } && [ -s \"${REPORT}\" ]; then exit 0; fi; exit \"${STATUS}\""
606
+ "test -n \"${HARNESS_DAG_RUN_DIR:-}\" || { echo \"missing HARNESS_DAG_RUN_DIR for backend pytest preflight\" >&2; exit 2; }; CONTRACT=\"${HARNESS_DAG_RUN_DIR}/contracts/backend-test-execution.json\"; test -f \"${CONTRACT}\" || { echo \"missing backend-test execution contract: ${CONTRACT}\" >&2; exit 2; }; node -e 'const fs=require(\"fs\");const path=require(\"path\");const contractPath=process.argv[1];const contract=JSON.parse(fs.readFileSync(contractPath,\"utf8\"));const expected=\"testcase\";const errors=[];if(contract.framework!==\"pytest\") errors.push(\"framework must be pytest\");const testRoot=String(contract.testRoot||\"\");if(!testRoot||testRoot.includes(\"..\")||path.isAbsolute(testRoot)) errors.push(\"unsafe testRoot\");if(testRoot.replace(/\\/+$/,\"\")!==expected.replace(/\\/+$/,\"\")) errors.push(\"testRoot mismatch vs frozen command: \"+testRoot+\" !== \"+expected);const rootAbs=path.resolve(process.cwd(),testRoot);if(!fs.existsSync(rootAbs)) errors.push(\"testRoot does not exist: \"+testRoot);if(contract.targetMode===\"in-process\"&&!(Array.isArray(contract.existingFixtures)&&contract.existingFixtures.length)) errors.push(\"in-process requires existingFixtures\");for (const name of (contract.requiredEnvNames||[])) { if(!process.env[name]) errors.push(\"required env missing: \"+name); }if(contract.targetMode===\"external-running-service\"){ const n=contract.baseUrlEnvName; if(!n||!process.env[n]) errors.push(\"external base URL env missing: \"+String(n||\"<empty>\")); }if(contract.targetMode===\"managed-command\" && !(contract.managedCommand&&contract.managedCommand.sourceRef)) errors.push(\"managed-command requires sourceRef evidence\");if(errors.length){ console.error(errors.join(\"; \")); process.exit(2);} console.log(\"backend-test preflight ok: framework=pytest testRoot=\"+testRoot+\" targetMode=\"+contract.targetMode);' \"${CONTRACT}\"; REPORT=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-initial-junit.xml\"; EXIT_FILE=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-initial-pytest-exit.txt\"; mkdir -p \"$(dirname \"${REPORT}\")\"; PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python -m pytest testcase/ -v -p no:cacheprovider --junitxml=\"${REPORT}\"; STATUS=$?; printf \"%s\" \"${STATUS}\" > \"${EXIT_FILE}\"; printf \"JUnit report: %s\\n\" \"${REPORT}\"; printf \"pytestExitCode=%s\\n\" \"${STATUS}\"; if { [ \"${STATUS}\" -eq 0 ] || [ \"${STATUS}\" -eq 1 ]; } && [ -s \"${REPORT}\" ]; then exit 0; fi; exit \"${STATUS}\""
444
607
  ],
445
608
  "finalFullRequired": true
446
609
  },
447
610
  "cwd": ".",
448
- "timeoutMs": 300000
611
+ "timeoutMs": 300000,
612
+ "backendTestPipeline": "execute-parse-initial"
449
613
  }
450
614
  },
451
615
  {
452
- "id": "parse-backend-test-result-shell",
616
+ "id": "classify-backend-test-result-pi",
453
617
  "depends_on": [
454
- "execute-backend-pytest-shell"
618
+ "execute-and-parse-backend-pytest-initial-shell"
619
+ ],
620
+ "role": "reviewer",
621
+ "executor": "pi",
622
+ "complexity": "MED",
623
+ "writePolicy": "read-only",
624
+ "allowedPaths": [
625
+ "testcase/**",
626
+ "docs/test-reports/**"
627
+ ],
628
+ "forbiddenPaths": [
629
+ ".harness/**",
630
+ ".harness/dag-runs/**",
631
+ "artifacts/**"
632
+ ],
633
+ "outputContract": "Pure JSON classification: category in {ProductBug,TestBug,EnvFailure,ContractMismatch,FlakyTest,Unknown}, evidence[], confidence (capped), notes. No file writes.",
634
+ "subtask_prompt": "Read-only classifier for Backend Test Result v1.\n\nReturn exactly one JSON object (prefer pure JSON; single fenced json block tolerated; no trailing prose).\n\nThe object must contain exactly category, evidence, confidence, notes. evidence must be a non-empty array of strings, confidence must be a number from 0 through 1, and notes must be a non-empty string. Do not emit schemaVersion or custom fields.\n\nMinimal shape: {\"category\":\"Unknown\",\"evidence\":[\"outcome=completed-with-failures\"],\"confidence\":0.5,\"notes\":\"Single-run evidence is insufficient for a stronger classification.\"}\n\nRead contracts/backend-test-result-initial.json (run-owned initial Result v1). Do NOT invent pass rates from raw logs.\n\ncategory must be one of: ProductBug, TestBug, EnvFailure, ContractMismatch, FlakyTest, Unknown.\n\nHard constraints:\n\n- Single-run failure MUST NOT use FlakyTest (use Unknown, TestBug, or ProductBug).\n\n- executionStatus/outcome collection-error, command-error, or report-error MUST NOT use ProductBug.\n\n- Prefer EnvFailure/Unknown/TestBug for env, import, collection, and missing-report cases.\n\n- confidence must respect deterministic caps (≤0.75 for assertion failures; ≤0.6 for env/collection).\n\nInclude evidence[] referencing result fields (outcome, failed, failures[].name, executionStatus).\n\nRead-only: do not modify code, docs, artifacts, or repository files.",
635
+ "retryPolicy": {
636
+ "maxAttempts": 3,
637
+ "backoff": "exponential",
638
+ "initialDelayMs": 2000,
639
+ "maxDelayMs": 30000,
640
+ "retryCategories": [
641
+ "timeout",
642
+ "network",
643
+ "rate-limit",
644
+ "unavailable"
645
+ ]
646
+ }
647
+ },
648
+ {
649
+ "id": "materialize-classification-and-eligibility-shell",
650
+ "depends_on": [
651
+ "classify-backend-test-result-pi"
455
652
  ],
456
- "complexity": "LOW",
457
- "executor": "shell",
458
653
  "role": "verifier",
654
+ "executor": "shell",
655
+ "complexity": "LOW",
459
656
  "writePolicy": "read-only",
460
657
  "allowedPaths": [
461
- "**"
658
+ "testcase/**",
659
+ "docs/test-reports/**"
462
660
  ],
463
661
  "forbiddenPaths": [
464
662
  ".harness/**",
663
+ ".harness/dag-runs/**",
465
664
  "artifacts/**"
466
665
  ],
467
- "outputContract": "Validated run-owned Backend Test Result v1 at contracts/backend-test-result.json (schemaId backend-test-result-v1) with outcome/counts/failures from deterministic JUnit parse.",
468
- "subtask_prompt": "Materialize Backend Test Result v1 from JUnit + pytestExitCode under the current DAG run (fail-closed on missing/corrupt report).",
666
+ "outputContract": "Materialize Classification v1 and repair eligibility JSON.",
667
+ "subtask_prompt": "Validate classification and determine bounded repair eligibility.",
469
668
  "shell": {
470
669
  "commands": [],
471
- "jsonArtifactGate": {
472
- "fromNodeId": "execute-backend-pytest-shell",
473
- "schemaId": "backend-test-result-v1",
474
- "artifactName": "backend-test-result.json",
475
- "outputDir": "contracts"
476
- },
670
+ "backendTestPipeline": "classification-eligibility",
477
671
  "cwd": ".",
478
672
  "timeoutMs": 60000
479
673
  }
480
674
  },
481
675
  {
482
- "id": "classify-backend-test-result-pi",
676
+ "id": "repair-backend-pytest-pi",
483
677
  "depends_on": [
484
- "parse-backend-test-result-shell"
678
+ "materialize-classification-and-eligibility-shell"
485
679
  ],
486
- "complexity": "MED",
680
+ "role": "implementer",
487
681
  "executor": "pi",
488
- "role": "reviewer",
682
+ "toolProfile": "write",
683
+ "complexity": "HIGH",
684
+ "writePolicy": "exclusive",
685
+ "writeSet": [
686
+ "testcase/**/test_*.py",
687
+ "testcase/**/helpers/**",
688
+ "testcase/**/factories/**"
689
+ ],
690
+ "allowedPaths": [
691
+ "testcase/**/test_*.py",
692
+ "testcase/**/helpers/**",
693
+ "testcase/**/factories/**"
694
+ ],
695
+ "forbiddenPaths": [
696
+ ".harness/**",
697
+ ".harness/dag-runs/**",
698
+ "artifacts/**",
699
+ "apps/**",
700
+ "src/**",
701
+ ".env*",
702
+ "**/migrations/**"
703
+ ],
704
+ "outputContract": "Repair only existing generated pytest tests/helpers/factories for classified TestBug findings. No production/config/runtime evidence writes.",
705
+ "subtask_prompt": "This is the only automatic TestBug repair attempt (1/1).\n\nRead contracts/backend-test-result-initial.json, contracts/backend-test-classification.json, contracts/backend-test-analysis.json, contracts/backend-test-case-manifest.json, and only the generated pytest files named in failures[].name or manifest mappings.\n\nDo not re-read the full task source tree: the run-owned analysis/manifest are the compact authoritative context.\n\nRepair only test implementation defects directly supported by reviewed Expected Results or formal API contract evidence.\n\nNever modify product code, migrations, service configuration, conftest.py, pytest.ini, pyproject.toml, setup.cfg, source requirements, testcase/md/**, or .harness/**.\n\nNever delete a test, remove a case mapping, add skip/skipif/xfail, swallow AssertionError/network exceptions, switch to a mock server, or weaken documented status/value assertions.\n\nKeep the full suite runnable and preserve each BE-* case ID and pytest symbol mapping.\n\nStay within writeSet: testcase/**/test_*.py, helpers/**, factories/**.",
706
+ "runIf": "$.nodes['materialize-classification-and-eligibility-shell'].json.eligible == true"
707
+ },
708
+ {
709
+ "id": "validate-repair-safety-and-traceability-shell",
710
+ "depends_on": [
711
+ "repair-backend-pytest-pi"
712
+ ],
713
+ "role": "verifier",
714
+ "executor": "shell",
715
+ "complexity": "LOW",
489
716
  "writePolicy": "read-only",
490
717
  "allowedPaths": [
491
- "**"
718
+ "testcase/**"
492
719
  ],
493
720
  "forbiddenPaths": [
494
721
  ".harness/**",
722
+ ".harness/dag-runs/**",
495
723
  "artifacts/**"
496
724
  ],
497
- "outputContract": "Pure JSON classification: category in {ProductBug,TestBug,EnvFailure,ContractMismatch,FlakyTest,Unknown}, evidence[], confidence (capped), notes. No file writes.",
498
- "subtask_prompt_markdown": "./backend-test-dag.classify.prompt.md"
725
+ "runIf": "$.nodes['materialize-classification-and-eligibility-shell'].json.eligible == true",
726
+ "outputContract": "Validate repair safety and final traceability.",
727
+ "subtask_prompt": "Reject manufactured success and revalidate mapping.",
728
+ "shell": {
729
+ "commands": [],
730
+ "backendTestPipeline": "repair-safety-traceability",
731
+ "cwd": ".",
732
+ "timeoutMs": 60000
733
+ }
734
+ },
735
+ {
736
+ "id": "finalize-effective-backend-test-result-shell",
737
+ "depends_on": [
738
+ "materialize-classification-and-eligibility-shell",
739
+ "validate-repair-safety-and-traceability-shell",
740
+ "validate-backend-test-contracts-shell"
741
+ ],
742
+ "role": "verifier",
743
+ "executor": "shell",
744
+ "complexity": "LOW",
745
+ "writePolicy": "read-only",
746
+ "allowedPaths": [
747
+ "testcase/**",
748
+ "docs/test-reports/**"
749
+ ],
750
+ "forbiddenPaths": [
751
+ ".harness/**",
752
+ ".harness/dag-runs/**",
753
+ "artifacts/**"
754
+ ],
755
+ "outputContract": "If repaired, execute/parse final pytest; always materialize contracts/backend-test-result.json from final or initial result.",
756
+ "subtask_prompt": "Run pytest for the backend test suite; write JUnit + pytestExitCode evidence only under the current HARNESS_DAG_RUN_DIR/reports/.",
757
+ "shell": {
758
+ "commands": [
759
+ "test -n \"${HARNESS_DAG_RUN_DIR:-}\" || { echo \"missing HARNESS_DAG_RUN_DIR for backend pytest preflight\" >&2; exit 2; }; CONTRACT=\"${HARNESS_DAG_RUN_DIR}/contracts/backend-test-execution.json\"; test -f \"${CONTRACT}\" || { echo \"missing backend-test execution contract: ${CONTRACT}\" >&2; exit 2; }; node -e 'const fs=require(\"fs\");const path=require(\"path\");const contractPath=process.argv[1];const contract=JSON.parse(fs.readFileSync(contractPath,\"utf8\"));const expected=\"testcase\";const errors=[];if(contract.framework!==\"pytest\") errors.push(\"framework must be pytest\");const testRoot=String(contract.testRoot||\"\");if(!testRoot||testRoot.includes(\"..\")||path.isAbsolute(testRoot)) errors.push(\"unsafe testRoot\");if(testRoot.replace(/\\/+$/,\"\")!==expected.replace(/\\/+$/,\"\")) errors.push(\"testRoot mismatch vs frozen command: \"+testRoot+\" !== \"+expected);const rootAbs=path.resolve(process.cwd(),testRoot);if(!fs.existsSync(rootAbs)) errors.push(\"testRoot does not exist: \"+testRoot);if(contract.targetMode===\"in-process\"&&!(Array.isArray(contract.existingFixtures)&&contract.existingFixtures.length)) errors.push(\"in-process requires existingFixtures\");for (const name of (contract.requiredEnvNames||[])) { if(!process.env[name]) errors.push(\"required env missing: \"+name); }if(contract.targetMode===\"external-running-service\"){ const n=contract.baseUrlEnvName; if(!n||!process.env[n]) errors.push(\"external base URL env missing: \"+String(n||\"<empty>\")); }if(contract.targetMode===\"managed-command\" && !(contract.managedCommand&&contract.managedCommand.sourceRef)) errors.push(\"managed-command requires sourceRef evidence\");if(errors.length){ console.error(errors.join(\"; \")); process.exit(2);} console.log(\"backend-test preflight ok: framework=pytest testRoot=\"+testRoot+\" targetMode=\"+contract.targetMode);' \"${CONTRACT}\"; REPORT=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-final-junit.xml\"; EXIT_FILE=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-final-pytest-exit.txt\"; mkdir -p \"$(dirname \"${REPORT}\")\"; PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python -m pytest testcase/ -v -p no:cacheprovider --junitxml=\"${REPORT}\"; STATUS=$?; printf \"%s\" \"${STATUS}\" > \"${EXIT_FILE}\"; printf \"JUnit report: %s\\n\" \"${REPORT}\"; printf \"pytestExitCode=%s\\n\" \"${STATUS}\"; if { [ \"${STATUS}\" -eq 0 ] || [ \"${STATUS}\" -eq 1 ]; } && [ -s \"${REPORT}\" ]; then exit 0; fi; exit \"${STATUS}\""
760
+ ],
761
+ "envAllowlist": [],
762
+ "verifyEvidence": {
763
+ "phase": "final",
764
+ "quota": "full",
765
+ "commandSource": "inline",
766
+ "commandCount": 1,
767
+ "commandLabels": [
768
+ "test -n \"${HARNESS_DAG_RUN_DIR:-}\" || { echo \"missing HARNESS_DAG_RUN_DIR for backend pytest preflight\" >&2; exit 2; }; CONTRACT=\"${HARNESS_DAG_RUN_DIR}/contracts/backend-test-execution.json\"; test -f \"${CONTRACT}\" || { echo \"missing backend-test execution contract: ${CONTRACT}\" >&2; exit 2; }; node -e 'const fs=require(\"fs\");const path=require(\"path\");const contractPath=process.argv[1];const contract=JSON.parse(fs.readFileSync(contractPath,\"utf8\"));const expected=\"testcase\";const errors=[];if(contract.framework!==\"pytest\") errors.push(\"framework must be pytest\");const testRoot=String(contract.testRoot||\"\");if(!testRoot||testRoot.includes(\"..\")||path.isAbsolute(testRoot)) errors.push(\"unsafe testRoot\");if(testRoot.replace(/\\/+$/,\"\")!==expected.replace(/\\/+$/,\"\")) errors.push(\"testRoot mismatch vs frozen command: \"+testRoot+\" !== \"+expected);const rootAbs=path.resolve(process.cwd(),testRoot);if(!fs.existsSync(rootAbs)) errors.push(\"testRoot does not exist: \"+testRoot);if(contract.targetMode===\"in-process\"&&!(Array.isArray(contract.existingFixtures)&&contract.existingFixtures.length)) errors.push(\"in-process requires existingFixtures\");for (const name of (contract.requiredEnvNames||[])) { if(!process.env[name]) errors.push(\"required env missing: \"+name); }if(contract.targetMode===\"external-running-service\"){ const n=contract.baseUrlEnvName; if(!n||!process.env[n]) errors.push(\"external base URL env missing: \"+String(n||\"<empty>\")); }if(contract.targetMode===\"managed-command\" && !(contract.managedCommand&&contract.managedCommand.sourceRef)) errors.push(\"managed-command requires sourceRef evidence\");if(errors.length){ console.error(errors.join(\"; \")); process.exit(2);} console.log(\"backend-test preflight ok: framework=pytest testRoot=\"+testRoot+\" targetMode=\"+contract.targetMode);' \"${CONTRACT}\"; REPORT=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-final-junit.xml\"; EXIT_FILE=\"${HARNESS_DAG_RUN_DIR}/reports/backend-test-final-pytest-exit.txt\"; mkdir -p \"$(dirname \"${REPORT}\")\"; PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python -m pytest testcase/ -v -p no:cacheprovider --junitxml=\"${REPORT}\"; STATUS=$?; printf \"%s\" \"${STATUS}\" > \"${EXIT_FILE}\"; printf \"JUnit report: %s\\n\" \"${REPORT}\"; printf \"pytestExitCode=%s\\n\" \"${STATUS}\"; if { [ \"${STATUS}\" -eq 0 ] || [ \"${STATUS}\" -eq 1 ]; } && [ -s \"${REPORT}\" ]; then exit 0; fi; exit \"${STATUS}\""
769
+ ],
770
+ "finalFullRequired": true
771
+ },
772
+ "cwd": ".",
773
+ "timeoutMs": 300000,
774
+ "backendTestPipeline": "finalize-effective-result"
775
+ },
776
+ "dependsPolicy": "all-or-condition-skip"
499
777
  },
500
778
  {
501
779
  "id": "test-retrospect-pi",
502
780
  "depends_on": [
503
- "classify-backend-test-result-pi"
781
+ "finalize-effective-backend-test-result-shell"
504
782
  ],
505
- "complexity": "MED",
506
- "executor": "pi",
507
783
  "role": "closeout",
508
- "writePolicy": "exclusive",
784
+ "executor": "pi",
785
+ "complexity": "MED",
509
786
  "toolProfile": "write",
787
+ "writePolicy": "exclusive",
510
788
  "writeSet": [
511
789
  "docs/test-reports/**"
512
790
  ],
@@ -515,24 +793,28 @@
515
793
  ],
516
794
  "forbiddenPaths": [
517
795
  ".harness/**",
796
+ ".harness/dag-runs/**",
518
797
  "artifacts/**"
519
798
  ],
520
- "subtask_prompt_markdown": "./backend-test-dag.retrospect.prompt.md"
799
+ "outputContract": "Maturity rating in assistant output plus a report written under docs/test-reports/**.",
800
+ "subtask_prompt": "Read the complete JSON from direct upstream select-effective-backend-test-result-shell and generate a test retrospective report.\n\nThat JSON contains result, manifest (including coverageSummary), and classification. Treat those fields as authoritative; do not rely on pointer/hash summaries.\n\n\n\n## Output Steps (do in order):\n\n1. First, output the maturity rating on the first line: Rating: A/B/C/D\n\n2. Then write the full report under docs/test-reports/\n\n\n\n## Stats authority (deterministic only):\n\n- Pass rate, failed/error/skipped counts, and failure list MUST come from contracts/backend-test-result.json only.\n\n- AC coverage ratio / case counts MUST come from contracts/backend-test-case-manifest.json coverageSummary (or gate-derived fields). Do NOT invent coverage %.\n\n- Use classify-backend-test-result-pi JSON as interpretive evidence only.\n\n- NEVER rewrite a failed result as passed. Outcome gate (not this report) is authoritative for task success.\n\n\n\n## Report Structure:\n\n1. Maturity Rating with rationale\n\n2. Test Coverage Summary (manifest coverageSummary + Result v1 pass rate)\n\n3. Review Findings and resolution status\n\n4. Failed Test Analysis (if any) + classification category\n\n5. Recommendations for improvement\n\n\n\n## Rating Criteria:\n\n- A: coverageSummary.acCoverageRatio=1 + 100% pytest pass + no Critical findings\n\n- B: acCoverageRatio≥0.8 + ≥90% pass + Low findings only\n\n- C: acCoverageRatio≥0.6 + ≥70% pass + no Critical findings\n\n- D: below C thresholds\n\n\n\n## Constraints:\n\n- Stay within writeSet: docs/test-reports/**\n\n- Do NOT re-read source documents — use upstream outputs only\n\n- Do not write root artifacts/**"
521
801
  },
522
802
  {
523
803
  "id": "backend-test-outcome-gate-shell",
524
804
  "depends_on": [
525
805
  "test-retrospect-pi"
526
806
  ],
527
- "complexity": "LOW",
528
- "executor": "shell",
529
807
  "role": "verifier",
808
+ "executor": "shell",
809
+ "complexity": "LOW",
530
810
  "writePolicy": "read-only",
531
811
  "allowedPaths": [
532
- "**"
812
+ "testcase/**",
813
+ "docs/test-reports/**"
533
814
  ],
534
815
  "forbiddenPaths": [
535
816
  ".harness/**",
817
+ ".harness/dag-runs/**",
536
818
  "artifacts/**"
537
819
  ],
538
820
  "outputContract": "Shell exit 0 only when Result v1 outcome=passed with failed=0 and error=0; non-zero otherwise. Ignores retrospective Markdown.",
@@ -555,5 +837,19 @@
555
837
  "timeoutMs": 60000
556
838
  }
557
839
  }
558
- ]
840
+ ],
841
+ "sourceBinding": {
842
+ "schemaVersion": 1,
843
+ "taskId": "backend-test-template",
844
+ "sources": [
845
+ {
846
+ "kind": "requirement",
847
+ "path": "source/需求.md",
848
+ "sha256": "bdecb1206ebb38957da644736300813f6e59a919a10be1b48126fbc1282bc9f6"
849
+ }
850
+ ],
851
+ "requirementIds": [
852
+ "AC-001"
853
+ ]
854
+ }
559
855
  }