@tea-agent/loop-agent 0.16.26 → 0.17.0

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 (103) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +14 -3
  3. package/dist/cli/command-definitions.js +43 -0
  4. package/dist/cli/program.js +26 -0
  5. package/dist/commands/dag-approve.js +4 -0
  6. package/dist/commands/dag-resume.js +1 -0
  7. package/dist/commands/dag-validate.js +6 -0
  8. package/dist/commands/operator.js +44 -0
  9. package/dist/commands/task-contract.js +271 -0
  10. package/dist/executors/dag-pi-executor.js +55 -7
  11. package/dist/executors/pi-executor.js +206 -13
  12. package/dist/executors/pi-sdk-executor.js +21 -6
  13. package/dist/executors/shell-executor.js +55 -8
  14. package/dist/executors/shell-presets.js +16 -3
  15. package/dist/shared/operator/capabilities.js +255 -0
  16. package/dist/shared/operator/envelope.js +59 -0
  17. package/dist/shared/operator/index.js +4 -0
  18. package/dist/shared/operator/registry.js +38 -0
  19. package/dist/shared/operator/types.js +5 -0
  20. package/dist/task/contract/adopt.js +166 -0
  21. package/dist/task/contract/apply.js +326 -0
  22. package/dist/task/contract/canonicalize.js +60 -0
  23. package/dist/task/contract/constants.js +29 -0
  24. package/dist/task/contract/diff.js +177 -0
  25. package/dist/task/contract/hash.js +42 -0
  26. package/dist/task/contract/import-revision.js +96 -0
  27. package/dist/task/contract/index.js +17 -0
  28. package/dist/task/contract/journal.js +155 -0
  29. package/dist/task/contract/lock.js +153 -0
  30. package/dist/task/contract/observe.js +296 -0
  31. package/dist/task/contract/paths.js +19 -0
  32. package/dist/task/contract/project.js +170 -0
  33. package/dist/task/contract/recover.js +312 -0
  34. package/dist/task/contract/request-ledger.js +37 -0
  35. package/dist/task/contract/schema.js +151 -0
  36. package/dist/task/contract/transaction.js +160 -0
  37. package/dist/task/contract/types.js +1 -0
  38. package/dist/task/contract/validate-draft.js +106 -0
  39. package/dist/task/index.js +3 -0
  40. package/dist/task/operator/capabilities.js +6 -0
  41. package/dist/task/operator/envelope.js +2 -0
  42. package/dist/task/operator/index.js +5 -0
  43. package/dist/task/operator/registry.js +2 -0
  44. package/dist/task/operator/types.js +1 -0
  45. package/dist/task/runtime.js +5 -1
  46. package/dist/task/source-references.js +7 -0
  47. package/dist/worker/cli.js +150 -32
  48. package/dist/worker/console/app-data.js +185 -0
  49. package/dist/worker/console/dag-confirmation.js +313 -0
  50. package/dist/worker/console/doctor.js +169 -0
  51. package/dist/worker/console/draft-store.js +80 -0
  52. package/dist/worker/console/index.js +15 -0
  53. package/dist/worker/console/interview/assessment.js +67 -0
  54. package/dist/worker/console/interview/session.js +100 -0
  55. package/dist/worker/console/interview/tools.js +109 -0
  56. package/dist/worker/console/loopback.js +16 -0
  57. package/dist/worker/console/observe-health-match.js +174 -0
  58. package/dist/worker/console/observe-link.js +33 -0
  59. package/dist/worker/console/operation-runner.js +166 -0
  60. package/dist/worker/console/operation-sse.js +158 -0
  61. package/dist/worker/console/operation-store.js +147 -0
  62. package/dist/worker/console/operator-actions.js +769 -0
  63. package/dist/worker/console/pi-readiness.js +94 -0
  64. package/dist/worker/console/recovery-cta.js +133 -0
  65. package/dist/worker/console/repo-fingerprint.js +29 -0
  66. package/dist/worker/console/resource-loader.js +95 -0
  67. package/dist/worker/console/routes.js +368 -0
  68. package/dist/worker/console/security.js +126 -0
  69. package/dist/worker/console/server.js +149 -0
  70. package/dist/worker/console/sibling-controller.js +28 -0
  71. package/dist/worker/console/static/assets/index-CbnMgdWa.js +9 -0
  72. package/dist/worker/console/static/assets/index-Dnj0RVs8.css +1 -0
  73. package/dist/worker/console/static/index.html +13 -0
  74. package/dist/worker/console/vite.config.js +27 -0
  75. package/dist/worker/observe/health.js +57 -0
  76. package/dist/worker/observe/routes.js +16 -9
  77. package/dist/workflows/dag/backend-test-analysis-contract.js +34 -9
  78. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -0
  79. package/dist/workflows/dag/frontend-repair.js +1 -10
  80. package/dist/workflows/dag/init-hybrid.js +269 -116
  81. package/dist/workflows/dag/node-execution.js +25 -1
  82. package/dist/workflows/dag/retry-policy.js +16 -1
  83. package/dist/workflows/dag/runner.js +9 -0
  84. package/dist/workflows/dag/task-contract-binding.js +138 -0
  85. package/dist/workflows/dag/types.js +74 -10
  86. package/dist/workflows/dag/validate.js +25 -7
  87. package/docs/README.md +2 -0
  88. package/docs/architecture/evolution.md +2 -0
  89. package/docs/architecture/system-overview.md +6 -0
  90. package/docs/architecture/worker-and-feature.md +7 -0
  91. package/docs/templates/agent-dag.schema.json +49 -2
  92. package/docs/templates/backend-test-dag.classify.prompt.md +1 -1
  93. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +5 -5
  94. package/docs/templates/backend-test-dag.json +26 -154
  95. package/docs/templates/backend-test-dag.retrospect.prompt.md +1 -1
  96. package/docs/templates/backend-test-dag.review-cases.prompt.md +2 -2
  97. package/package.json +8 -2
  98. package/skills/agent-worker/SKILL.md +1 -0
  99. package/skills/agent-worker/references/agent-worker-operator.md +3 -2
  100. package/skills/frontend-design-review/SKILL.md +25 -16
  101. package/skills/frontend-implementation/references/node-contracts.md +5 -5
  102. package/skills/loop-agent/references/command-reference.md +48 -1
  103. package/skills/loop-agent/references/hybrid-dag.md +4 -4
@@ -7,7 +7,7 @@ import { DAG_AGENT_RUNTIME_PI_ONLY, DAG_REPAIR_WRITER_PROTOCOL_EXPLICIT_NODE_V1,
7
7
  import { pathMatchesPattern } from "../../shared/git-progress.js";
8
8
  import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
9
9
  import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
10
- import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, } from "./retry-policy.js";
10
+ import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, } from "./retry-policy.js";
11
11
  import { resolveAdapter } from "../../adapters/index.js";
12
12
  import { loadHarnessManifest } from "../../governance/harness.js";
13
13
  import { buildAuthoritySurfaceAuditNode, buildAuthoritySurfaceGateNode, resolveAuthoritySurfaceAudit, } from "./authority-surface.js";
@@ -15,6 +15,7 @@ import { applySddEmbeddedEnhancements, probeRepoLocalSddSkills, } from "./sdd-em
15
15
  import { discoverProjectGovernancePresence } from "./project-governance-context.js";
16
16
  import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
17
17
  import { materializeTaskReferenceDocs } from "../../task/source-references.js";
18
+ import { observeTaskContract } from "../../task/contract/observe.js";
18
19
  import { resolveVerifyPreset } from "../../executors/shell-verification.js";
19
20
  import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
20
21
  import { normalizeTaskRequirementText, resolveTaskDagTemplateSelection, } from "./task-demand-routing.js";
@@ -1379,6 +1380,7 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
1379
1380
  subtask_prompt: [
1380
1381
  "Perform read-only Mock assessment and select one safe frontend data strategy.",
1381
1382
  "The first line must be exactly one of: MOCK_STRATEGY: native, MOCK_STRATEGY: browser-intercept, MOCK_STRATEGY: request-adapter, MOCK_STRATEGY: not-needed, or MOCK_STRATEGY: blocked. Do not emit blank lines, headings, or explanatory preamble before it.",
1383
+ "Do not wrap the first line in Markdown bullets, quotes, emphasis, code fences, inline code, Chinese punctuation, or a split label/value form. Put any headings or explanation only after the exact first line.",
1382
1384
  "Prefer an existing native Mock facility. Use browser-intercept only with an existing browser/e2e harness. When no Mock exists but the API layer is writable, use request-adapter by adding a minimal reversible adapter/DI seam within the approved writeSet; the real adapter must remain the production default.",
1383
1385
  autoMaySkipMissingMock
1384
1386
  ? "Auto mode may skip Mock when no project Mock capability is confirmed. Select not-needed with positive evidence from contract/scout that no project Mock capability is confirmed, continue without adding Mock files or dependencies, run the fixed verification entrypoints, and record any unproved real API behavior in Real Integration Gap. Do not block solely because no project Mock capability, browser interception harness, or request adapter exists."
@@ -1402,7 +1404,9 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
1402
1404
  "- Do not infer Mock service from lockfile-only or transitive dependency evidence.",
1403
1405
  "- Output MOCK_STRATEGY: blocked if capability evidence conflicts, contract fields are missing/conflicting, paths or dependencies are unauthorized, specs were not actually read, sources conflict, production-default-off cannot be proven, the API layer is not writable for a new adapter, or the frozen entrypoints cannot verify the selected strategy.",
1404
1406
  ...(autoMaySkipMissingMock
1405
- ? ["- In auto mode with no confirmed project Mock capability, do not block solely because Mock is unavailable or Mock-specific verification is absent; choose not-needed and make the Real Integration Gap explicit unless project specs require Mock or another safety rule is violated."]
1407
+ ? [
1408
+ "- In auto mode with no confirmed project Mock capability, do not block solely because Mock is unavailable or Mock-specific verification is absent; choose not-needed and make the Real Integration Gap explicit unless project specs require Mock or another safety rule is violated.",
1409
+ ]
1406
1410
  : []),
1407
1411
  "- Never comment out or replace the real request with inline data, hard-code Mock enablement, import test mocks from a production entrypoint, invent API fields, or place secrets/real user data in fixtures.",
1408
1412
  "- Mock-backed behavior evidence proves the documented frontend contract only; it never proves real API integration.",
@@ -1650,11 +1654,10 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1650
1654
  if (risk.forceFullGates || risk.selectedRisk !== "small") {
1651
1655
  return tasks;
1652
1656
  }
1653
- // small topology: drop first design gate + plan revision + second design cycle;
1657
+ // small topology: drop the initial design review + conditional revision cycle;
1654
1658
  // keep mock assess/gate, single design review (final), contract, implement, verify, repair chain, review.
1655
1659
  const drop = new Set([
1656
1660
  "frontend-design-gate-pi",
1657
- "frontend-first-design-gate-shell",
1658
1661
  "frontend-plan-revision-pi",
1659
1662
  ]);
1660
1663
  // If we drop plan-revision, contract shell must depend on plan-pi instead; final design review depends on plan.
@@ -1670,8 +1673,7 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1670
1673
  next.push("frontend-plan-pi");
1671
1674
  continue;
1672
1675
  }
1673
- if (dep === "frontend-first-design-gate-shell" ||
1674
- dep === "frontend-design-gate-pi") {
1676
+ if (dep === "frontend-design-gate-pi") {
1675
1677
  // skip removed gates
1676
1678
  continue;
1677
1679
  }
@@ -1692,6 +1694,12 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1692
1694
  if (byId.has(need) && !depends_on.includes(need))
1693
1695
  depends_on.push(need);
1694
1696
  }
1697
+ return {
1698
+ ...task,
1699
+ depends_on,
1700
+ dependsPolicy: "all",
1701
+ runIf: undefined,
1702
+ };
1695
1703
  }
1696
1704
  if (task.id === "frontend-requirement-coverage-shell") {
1697
1705
  const shell = task.shell?.requirementCoverageGate
@@ -1702,6 +1710,7 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1702
1710
  fromNodeIds: task.shell.requirementCoverageGate.fromNodeIds.map((nodeId) => nodeId === "frontend-plan-revision-pi"
1703
1711
  ? "frontend-plan-pi"
1704
1712
  : nodeId),
1713
+ fallbackFromNodeIds: undefined,
1705
1714
  },
1706
1715
  }
1707
1716
  : task.shell;
@@ -1720,12 +1729,25 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1720
1729
  ? {
1721
1730
  ...task.shell.jsonArtifactGate,
1722
1731
  fromNodeId: "frontend-plan-pi",
1732
+ fallbackFromNodeIds: undefined,
1723
1733
  }
1724
1734
  : task.shell.jsonArtifactGate,
1725
1735
  }
1726
1736
  : task.shell;
1727
1737
  return { ...task, depends_on: nextDeps, shell };
1728
1738
  }
1739
+ if (task.id === "frontend-final-design-gate-shell") {
1740
+ const shell = task.shell?.verdictGate
1741
+ ? {
1742
+ ...task.shell,
1743
+ verdictGate: {
1744
+ ...task.shell.verdictGate,
1745
+ fallbackFromNodeIds: undefined,
1746
+ },
1747
+ }
1748
+ : task.shell;
1749
+ return { ...task, depends_on, dependsPolicy: "all", shell };
1750
+ }
1729
1751
  if (task.id === "frontend-implement-pi") {
1730
1752
  // still requires final design gate
1731
1753
  for (const need of [
@@ -1830,7 +1852,7 @@ function buildFrontendHybridDagFromTask(sources) {
1830
1852
  ? [`See 执行约束.md in task source (${sources.taskId})`]
1831
1853
  : []),
1832
1854
  ...STANDARD_GLOBAL_CONSTRAINTS,
1833
- "Frontend implementation DAGs must pass the final design verdict gate before any write node executes; the first design gate also accepts request-revision for plan revision only.",
1855
+ "Frontend implementation DAGs must pass the effective final design verdict gate before any write node executes; an initial pass uses the original plan, while request-revision selects the read-only revision and final-review branch.",
1834
1856
  "Final design gate pass is the only authorization for frontend implementation writes.",
1835
1857
  "Plan revision remains read-only and never edits business code.",
1836
1858
  "Design revision failures route to replan-and-rerun, never dev-fix.",
@@ -1962,6 +1984,8 @@ function buildFrontendHybridDagFromTask(sources) {
1962
1984
  executor: "pi",
1963
1985
  complexity: "MED",
1964
1986
  writePolicy: "read-only",
1987
+ outputMode: "structured-required",
1988
+ retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
1965
1989
  allowedPaths: readOnlyPaths,
1966
1990
  forbiddenPaths,
1967
1991
  skills: FRONTEND_IMPLEMENTATION_SKILLS,
@@ -2000,53 +2024,27 @@ function buildFrontendHybridDagFromTask(sources) {
2000
2024
  sourceContext,
2001
2025
  ].join("\n\n"),
2002
2026
  },
2003
- {
2004
- id: "frontend-first-design-gate-shell",
2005
- depends_on: ["frontend-design-gate-pi"],
2006
- role: "verifier",
2007
- executor: "shell",
2008
- complexity: "LOW",
2009
- writePolicy: "read-only",
2010
- allowedPaths: readOnlyPaths,
2011
- forbiddenPaths,
2012
- outputContract: "Deterministic first design verdict gate: exit 0 when frontend-design-gate-pi emits VERDICT: pass or VERDICT: request-revision. Does not authorize code writes.",
2013
- subtask_prompt: "Deterministic gate: validate frontend-design-gate-pi first-line VERDICT is pass or request-revision; block downstream only on malformed/unexpected verdict.",
2014
- shell: {
2015
- commands: [],
2016
- verdictGate: {
2017
- fromNodeId: "frontend-design-gate-pi",
2018
- accept: ["VERDICT: pass", "VERDICT: request-revision"],
2019
- label: "frontend first design gate",
2020
- lineMode: "first-verdict-line",
2021
- },
2022
- cwd: ".",
2023
- timeoutMs: 60000,
2024
- },
2025
- },
2026
2027
  {
2027
2028
  id: "frontend-plan-revision-pi",
2028
2029
  depends_on: [
2029
- "frontend-first-design-gate-shell",
2030
2030
  "frontend-plan-pi",
2031
2031
  "frontend-design-gate-pi",
2032
2032
  "frontend-mock-assess-pi",
2033
2033
  ],
2034
+ runIf: "$.nodes['frontend-design-gate-pi'].firstVerdictLine == 'VERDICT: request-revision'",
2034
2035
  role: "planner",
2035
2036
  executor: "pi",
2036
2037
  complexity: "MED",
2037
2038
  writePolicy: "read-only",
2039
+ outputMode: "structured-required",
2040
+ retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
2038
2041
  allowedPaths: readOnlyPaths,
2039
2042
  forbiddenPaths,
2040
2043
  skills: FRONTEND_IMPLEMENTATION_SKILLS,
2041
- outputContract: "Markdown revision plan followed by exactly one fenced json object conforming to frontend-implementation-contract-v1. The JSON is the authoritative materialization input. No file writes.",
2044
+ outputContract: "When the initial design review requests revision, return a complete Markdown revision plan followed by exactly one fenced json object conforming to frontend-implementation-contract-v1. The JSON is the authoritative materialization input. No file writes.",
2042
2045
  subtask_prompt: [
2043
2046
  "Consume frontend-plan-pi (original plan) and frontend-design-gate-pi (first design review findings).",
2044
- "If the first design gate passed (VERDICT: pass from frontend-design-gate-pi), output exactly:",
2045
- "PASS_NO_REVISION_NEEDED",
2046
- "The original plan from frontend-plan-pi is confirmed and does not require changes.",
2047
- "Then reproduce a complete Requirement Coverage section containing every explicit REQ-/BR-/AC- identifier from the authoritative task sources so this node is the single effective-plan evidence source for the deterministic coverage gate.",
2048
- "",
2049
- "If the first design gate requested revision (VERDICT: request-revision), produce a complete revised implementation plan that addresses every Required Plan Correction from the design findings.",
2047
+ "This node runs only when frontend-design-gate-pi emitted VERDICT: request-revision. Produce a complete revised implementation plan that addresses every Required Plan Correction from the design findings.",
2050
2048
  "The revised plan must include Requirement Coverage, Implementation Steps, Target Files, UI State Handling, Styling / Component Strategy, Interaction Notes, Mock / API Strategy, Dependency Policy, Verification Plan, Real Integration Gap, and Residual Risks.",
2051
2049
  requirementCoverageInstruction,
2052
2050
  "Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
@@ -2060,7 +2058,8 @@ function buildFrontendHybridDagFromTask(sources) {
2060
2058
  ? [
2061
2059
  {
2062
2060
  id: "frontend-requirement-coverage-shell",
2063
- depends_on: ["frontend-plan-revision-pi"],
2061
+ depends_on: ["frontend-plan-revision-pi", "frontend-plan-pi"],
2062
+ dependsPolicy: "all-or-condition-skip",
2064
2063
  role: "verifier",
2065
2064
  executor: "shell",
2066
2065
  complexity: "LOW",
@@ -2073,6 +2072,7 @@ function buildFrontendHybridDagFromTask(sources) {
2073
2072
  commands: [],
2074
2073
  requirementCoverageGate: {
2075
2074
  fromNodeIds: ["frontend-plan-revision-pi"],
2075
+ fallbackFromNodeIds: ["frontend-plan-pi"],
2076
2076
  requiredIds: requirementIds,
2077
2077
  label: "frontend requirement coverage",
2078
2078
  },
@@ -2084,7 +2084,8 @@ function buildFrontendHybridDagFromTask(sources) {
2084
2084
  : []),
2085
2085
  {
2086
2086
  id: "frontend-implementation-contract-shell",
2087
- depends_on: ["frontend-plan-revision-pi"],
2087
+ depends_on: ["frontend-plan-revision-pi", "frontend-plan-pi"],
2088
+ dependsPolicy: "all-or-condition-skip",
2088
2089
  role: "verifier",
2089
2090
  executor: "shell",
2090
2091
  complexity: "LOW",
@@ -2097,6 +2098,7 @@ function buildFrontendHybridDagFromTask(sources) {
2097
2098
  commands: [],
2098
2099
  jsonArtifactGate: {
2099
2100
  fromNodeId: "frontend-plan-revision-pi",
2101
+ fallbackFromNodeIds: ["frontend-plan-pi"],
2100
2102
  schemaId: "frontend-implementation-contract-v1",
2101
2103
  artifactName: "frontend-implementation-contract.json",
2102
2104
  outputDir: "contracts",
@@ -2117,6 +2119,8 @@ function buildFrontendHybridDagFromTask(sources) {
2117
2119
  ? ["frontend-requirement-coverage-shell"]
2118
2120
  : []),
2119
2121
  ],
2122
+ dependsPolicy: "all-or-condition-skip",
2123
+ runIf: "$.nodes['frontend-design-gate-pi'].firstVerdictLine == 'VERDICT: request-revision'",
2120
2124
  role: "reviewer",
2121
2125
  executor: "pi",
2122
2126
  complexity: "MED",
@@ -2124,12 +2128,11 @@ function buildFrontendHybridDagFromTask(sources) {
2124
2128
  allowedPaths: readOnlyPaths,
2125
2129
  forbiddenPaths,
2126
2130
  skills: FRONTEND_DESIGN_REVIEW_SKILLS,
2127
- outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings and Checked Items. No file writes.",
2131
+ outputContract: "For the effective frontend plan, return plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings and Checked Items. No file writes.",
2128
2132
  subtask_prompt: [
2129
- "Audit the revised (or confirmed) frontend plan from frontend-plan-revision-pi before implementation.",
2133
+ "Audit the effective frontend plan before implementation. In the standard topology this node runs after request-revision and consumes frontend-plan-revision-pi; in a small-risk topology where that branch is pruned, audit frontend-plan-pi directly.",
2130
2134
  "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
2131
- "If frontend-plan-revision-pi returned PASS_NO_REVISION_NEEDED, confirm the original plan against all design constraints and task requirements. Re-verify that all applicable UI states are covered, dependencies are authorized, and deterministic verification commands are present.",
2132
- "If frontend-plan-revision-pi revised the plan, verify that every Required Plan Correction from the first design review has been fully addressed.",
2135
+ "When an initial design review requested revision, verify that every Required Plan Correction has been fully addressed. In the small-risk topology, independently audit the original plan against the full checklist because there is no initial-review finding set.",
2133
2136
  "Recheck the selected Mock / API strategy, contract-to-fixture mapping, authorized paths/dependencies, explicit activation, production-default-off behavior, behavior verification, and Real Integration Gap. MOCK_STRATEGY: blocked cannot receive VERDICT: pass.",
2134
2137
  "The frontend requirement coverage gate has verified that every explicit REQ-/BR-/AC- identifier remains present in the current-run plan evidence; review the mapped behavior rather than accepting identifier presence alone.",
2135
2138
  "Request revision if any design gap remains, if corrections are incomplete, or if the revised plan introduces new unaddressed issues.",
@@ -2140,19 +2143,24 @@ function buildFrontendHybridDagFromTask(sources) {
2140
2143
  },
2141
2144
  {
2142
2145
  id: "frontend-final-design-gate-shell",
2143
- depends_on: ["frontend-final-design-review-pi"],
2146
+ depends_on: [
2147
+ "frontend-final-design-review-pi",
2148
+ "frontend-design-gate-pi",
2149
+ ],
2150
+ dependsPolicy: "all-or-condition-skip",
2144
2151
  role: "verifier",
2145
2152
  executor: "shell",
2146
2153
  complexity: "LOW",
2147
2154
  writePolicy: "read-only",
2148
2155
  allowedPaths: readOnlyPaths,
2149
2156
  forbiddenPaths,
2150
- outputContract: "Deterministic final design verdict gate: exit 0 only when frontend-final-design-review-pi emits VERDICT: pass. This is the sole authorization for frontend implementation writes.",
2151
- subtask_prompt: "Deterministic gate: block frontend implementation unless frontend-final-design-review-pi emitted VERDICT: pass.",
2157
+ outputContract: "Deterministic effective design verdict gate: resolve the selected design review output and exit 0 only on VERDICT: pass. This is the sole authorization for frontend implementation writes.",
2158
+ subtask_prompt: "Deterministic gate: block frontend implementation unless the effective design review emitted VERDICT: pass.",
2152
2159
  shell: {
2153
2160
  commands: [],
2154
2161
  verdictGate: {
2155
2162
  fromNodeId: "frontend-final-design-review-pi",
2163
+ fallbackFromNodeIds: ["frontend-design-gate-pi"],
2156
2164
  accept: ["VERDICT: pass"],
2157
2165
  label: "frontend final design gate",
2158
2166
  lineMode: "first-verdict-line",
@@ -2171,6 +2179,7 @@ function buildFrontendHybridDagFromTask(sources) {
2171
2179
  "frontend-plan-pi",
2172
2180
  "frontend-mock-assess-pi",
2173
2181
  ],
2182
+ dependsPolicy: "all-or-condition-skip",
2174
2183
  role: "implementer",
2175
2184
  executor: "pi",
2176
2185
  toolProfile: "write",
@@ -2186,7 +2195,7 @@ function buildFrontendHybridDagFromTask(sources) {
2186
2195
  "Execute in fixed stages and report each in the delivery summary: (1) Contract confirm, (2) Tests sync, (3) Component/UI state implementation, (4) API/Mock wiring per contract.mockApi, (5) Focused checks behind frozen entrypoints only, (6) Diff cleanup.",
2187
2196
  "Map every requirement id and applicable UI state from the contract to concrete files. Do not invent shell verification commands; only frozen static/behavior entrypoints will run.",
2188
2197
  "Implement only the approved Mock strategy from frontend-mock-assess-pi as carried in the contract. Preserve the real request path as the default, require explicit test/dev activation, and never comment out or replace the real request with inline data.",
2189
- "The frontend-final-design-review-pi verdict confirmed the plan is ready. Stay within writeSet and preserve unrelated files.",
2198
+ "frontend-final-design-gate-shell confirmed the effective initial-or-final design verdict is pass. Stay within writeSet and preserve unrelated files.",
2190
2199
  "For native, browser-intercept, or request-adapter, implement contract-aligned fixtures/states and a dev/test-only activation boundary in this same writer. For not-needed, do not add Mock files or a framework and state the positive reason.",
2191
2200
  "Do not write root artifacts/** unless explicitly included in writeSet. Do not claim Browser/visual verification.",
2192
2201
  writerDeliveryContract(taskConfig),
@@ -2311,6 +2320,7 @@ function buildFrontendHybridDagFromTask(sources) {
2311
2320
  "frontend-failure-assess-shell",
2312
2321
  implementId,
2313
2322
  ],
2323
+ runIf: "$.nodes['frontend-failure-assess-shell'].json.eligible == true",
2314
2324
  role: "implementer",
2315
2325
  executor: "pi",
2316
2326
  toolProfile: "write",
@@ -2320,10 +2330,10 @@ function buildFrontendHybridDagFromTask(sources) {
2320
2330
  allowedPaths: implementPaths.allowedPaths,
2321
2331
  forbiddenPaths,
2322
2332
  skills: FRONTEND_IMPLEMENTATION_SKILLS,
2323
- outputContract: "Repair summary or explicit no-op when assessment has no failure/eligible=false path already gated. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
2333
+ outputContract: "Repair summary for an eligible repairable assessment. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
2324
2334
  subtask_prompt: [
2325
2335
  "Read contracts/frontend-repair-assessment.json and the validated frontend implementation contract.",
2326
- "If failedNodeIds is empty, return no-op with evidence. If eligible repairable failure, apply the smallest fix inside the original implement writeSet only.",
2336
+ "This node runs only for eligible=true. Apply the smallest fix for the classified repairable failure inside the original implement writeSet only.",
2327
2337
  "Do not change lint/type/test config, do not add .skip/.only, do not comment out real requests, do not default-enable Mock, do not add dependencies.",
2328
2338
  "Do not re-plan requirements or expand allowed paths. Browser/visual remain not-run.",
2329
2339
  writerDeliveryContract(taskConfig),
@@ -2399,6 +2409,7 @@ function buildFrontendHybridDagFromTask(sources) {
2399
2409
  "frontend-repair-pi",
2400
2410
  implementId,
2401
2411
  ],
2412
+ dependsPolicy: "all-or-condition-skip",
2402
2413
  role: "verifier",
2403
2414
  executor: "shell",
2404
2415
  complexity: "LOW",
@@ -2406,7 +2417,7 @@ function buildFrontendHybridDagFromTask(sources) {
2406
2417
  allowedPaths: readOnlyPaths,
2407
2418
  forbiddenPaths,
2408
2419
  outputContract: "Run-owned actual worktree diff_patch (artifacts/diff_patch.patch) plus contracts/frontend-worktree-diff.json inventory/hash for review. No product worktree writes.",
2409
- subtask_prompt: "Capture the authoritative actual diff after implement/repair/reverify so frontend-review-pi can audit changed files without relying on failure-path patches or model summaries.",
2420
+ subtask_prompt: "Capture the authoritative actual diff after implementation and any selected repair/reverify branch so frontend-review-pi can audit changed files without relying on failure-path patches or model summaries.",
2410
2421
  shell: {
2411
2422
  commands: ["frontend-worktree-diff-gate"],
2412
2423
  cwd: ".",
@@ -2421,11 +2432,15 @@ function buildFrontendHybridDagFromTask(sources) {
2421
2432
  "frontend-static-reverify-shell",
2422
2433
  "frontend-behavior-reverify-shell",
2423
2434
  "frontend-repair-pi",
2435
+ "frontend-verification-trace-shell",
2436
+ "frontend-static-verify-shell",
2437
+ "frontend-behavior-verify-shell",
2424
2438
  "frontend-failure-assess-shell",
2425
2439
  implementId,
2426
2440
  "frontend-implementation-contract-shell",
2427
2441
  "frontend-contract-pi",
2428
2442
  "frontend-plan-pi",
2443
+ "frontend-design-gate-pi",
2429
2444
  "frontend-plan-revision-pi",
2430
2445
  "frontend-final-design-review-pi",
2431
2446
  "frontend-mock-assess-pi",
@@ -2433,6 +2448,7 @@ function buildFrontendHybridDagFromTask(sources) {
2433
2448
  ? ["frontend-mock-verify-shell"]
2434
2449
  : []),
2435
2450
  ],
2451
+ dependsPolicy: "all-or-condition-skip",
2436
2452
  role: "reviewer",
2437
2453
  executor: "pi",
2438
2454
  complexity: "HIGH",
@@ -2445,9 +2461,9 @@ function buildFrontendHybridDagFromTask(sources) {
2445
2461
  "Review the frontend implementation and verification evidence.",
2446
2462
  "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
2447
2463
  "Any Critical or Important finding must force VERDICT: request-revision.",
2448
- "Read the validated frontend-implementation-contract, frontend-verification-trace evidence, static/behavior shell facts, and the run-owned actual diff from frontend-worktree-diff-shell (contracts/frontend-worktree-diff.json + artifacts/diff_patch.patch). Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/symbol binding only—not semantic correctness.",
2464
+ "Read the validated frontend-implementation-contract, the effective initial-or-post-repair frontend verification trace, the corresponding static/behavior shell facts, and the run-owned actual diff from frontend-worktree-diff-shell (contracts/frontend-worktree-diff.json + artifacts/diff_patch.patch). Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/symbol binding only—not semantic correctness.",
2449
2465
  "Flag .skip/.only, deleted or weakened tests, unauthorized config changes, Mock-only evidence claimed as real integration, and Browser/visual claims (always not-run in this workflow).",
2450
- "Use the direct contract, original plan, revision/no-op result, and final design review to reconstruct the approved plan and design verdict; do not infer them from the implementation summary.",
2466
+ "Use the direct contract and the effective plan/design branch: original plan plus initial pass when revision was skipped, or revised plan plus final design review when revision ran. Do not infer them from the implementation summary.",
2451
2467
  "Treat a commented-out real request, default-enabled Mock, production entrypoint importing test mocks, API/fixture contract drift, unauthorized Mock dependency/path, or missing behavior evidence for the selected strategy as at least Important. Mock strategies require Mock-backed evidence. not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case verify that the real request remains the default and the Real Integration Gap is preserved.",
2452
2468
  "Inspect the production/default-real-path static evidence directly and require Mock activation to be off for that check.",
2453
2469
  "Distinguish Mock-backed evidence from real API integration evidence and preserve the Real Integration Gap when the backend was not exercised.",
@@ -2488,6 +2504,9 @@ function buildFrontendHybridDagFromTask(sources) {
2488
2504
  "frontend-static-reverify-shell",
2489
2505
  "frontend-behavior-reverify-shell",
2490
2506
  "frontend-repair-pi",
2507
+ "frontend-verification-trace-shell",
2508
+ "frontend-static-verify-shell",
2509
+ "frontend-behavior-verify-shell",
2491
2510
  "frontend-failure-assess-shell",
2492
2511
  "frontend-implementation-contract-shell",
2493
2512
  "frontend-mock-assess-pi",
@@ -2495,6 +2514,7 @@ function buildFrontendHybridDagFromTask(sources) {
2495
2514
  ? ["frontend-mock-verify-shell"]
2496
2515
  : []),
2497
2516
  ],
2517
+ dependsPolicy: "all-or-condition-skip",
2498
2518
  role: "closeout",
2499
2519
  executor: "pi",
2500
2520
  complexity: "MED",
@@ -2507,7 +2527,7 @@ function buildFrontendHybridDagFromTask(sources) {
2507
2527
  outputContract: "Markdown closeout summary with Changes, Mock Decision / Strategy / Files / Verification / Production Boundary, Verification Evidence, Review Result, Frontend Status, Real Integration Status, Known Risks, and Follow-up. No file writes.",
2508
2528
  subtask_prompt: [
2509
2529
  "Return a frontend closeout summary covering Mock decision/strategy/files/verification/production boundary, changes, verification evidence, review result, known risks, and follow-up.",
2510
- "Include a coverage matrix for each requirement id, applicable UI state, and verification target/check with status passed|failed|not-run|blocked|unavailable. Always state Browser accessibility verification: not-run and Visual regression: not-run. Use frontend-verification-trace facts; do not invent Browser evidence from component tests.",
2530
+ "Include a coverage matrix for each requirement id, applicable UI state, and verification target/check with status passed|failed|not-run|blocked|unavailable. Always state Browser accessibility verification: not-run and Visual regression: not-run. Use the effective initial-or-post-repair frontend verification trace facts; do not invent Browser evidence from component tests.",
2511
2531
  `When only Mock-backed evidence passed, state exactly Frontend status: mock-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task to create/run after backend readiness. This follow-up is not auto-created or auto-executed. Never describe Mock evidence as real API integration.`,
2512
2532
  `When Mock was skipped in auto mode and no real API evidence passed, state exactly Frontend status: locally-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task when backend readiness matters.`,
2513
2533
  "Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
@@ -2709,7 +2729,7 @@ const BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS = [
2709
2729
  "Every 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.",
2710
2730
  "acIds MUST exactly match the explicit AC-* values in the written case body; do not infer ACs from Business Rules or summary matrices.",
2711
2731
  "Use full BE-<MODULE>-<NNN> caseId strings. Do not invent coverage percentages and do not emit coverageSummary; shell always writes the canonical summary.",
2712
- "Minimal 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\":[]}",
2732
+ 'Minimal 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":[]}',
2713
2733
  ].join("\n\n");
2714
2734
  function buildEmitBackendCaseManifestNode(sources, options) {
2715
2735
  return {
@@ -2799,7 +2819,7 @@ function buildReviewBackendCasesNode(sources, options) {
2799
2819
  writePolicy: "read-only",
2800
2820
  allowedPaths: commonReadOnlyPaths(sources),
2801
2821
  forbiddenPaths: commonForbiddenPaths(sources),
2802
- outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision; followed by Findings and Coverage Assessment. No file writes. The deterministic gate accepts pass only; request-revision ends this run.",
2822
+ outputContract: "advisory case review evidence whose first non-empty line is VERDICT: pass or VERDICT: request-revision; followed by Findings and Coverage Assessment. No file writes; this review neither authorizes nor blocks pytest generation.",
2803
2823
  subtask_prompt: [
2804
2824
  "Review the generated backend functional test cases under testcase/md/ and the validated Case Manifest v1.",
2805
2825
  "",
@@ -2830,7 +2850,7 @@ function buildReviewBackendCasesNode(sources, options) {
2830
2850
  "## Verdict Rules:",
2831
2851
  "- All Critical checks pass + Important findings ≤ 2 → VERDICT: pass",
2832
2852
  "- Any Critical fails OR Important > 2 → VERDICT: request-revision",
2833
- "- Any request-revision verdict ends the current run at the deterministic gate; describe findings clearly for an independent follow-up task.",
2853
+ "- Any request-revision verdict is advisory evidence for canonical context, retrospective, and L-5; it does not authorize or block the pytest writer.",
2834
2854
  "",
2835
2855
  "## Output After Verdict:",
2836
2856
  "1. Coverage Assessment table (AC → full BE-* case IDs) using manifest + MD",
@@ -2886,8 +2906,8 @@ function buildGenerateBackendPytestNode(sources) {
2886
2906
  return {
2887
2907
  id: "generate-backend-pytest-pi",
2888
2908
  depends_on: [
2889
- "review-backend-cases-gate-shell",
2890
- "backend-test-execution-contract-shell",
2909
+ "backend-test-case-manifest-shell",
2910
+ "validate-backend-test-contracts-shell",
2891
2911
  ],
2892
2912
  role: "implementer",
2893
2913
  executor: "pi",
@@ -2907,11 +2927,11 @@ function buildGenerateBackendPytestNode(sources) {
2907
2927
  // 注意:Pi 节点超时由 executor 层控制(默认 30 分钟)
2908
2928
  // 如需调整,在 harness.json 的 executors.pi 中配置 modelConfig.timeoutMs
2909
2929
  subtask_prompt: [
2910
- "Convert the reviewed test cases under testcase/md/ into pytest automation code.",
2930
+ "Convert the validated test cases under testcase/md/ into pytest automation code.",
2911
2931
  "",
2912
2932
  "## Inputs (MUST use validated contracts):",
2913
- "- Reviewed cases under testcase/md/ (after review-backend-cases-gate-shell).",
2914
- "- Validated Backend Test Analysis v1 under the current run contracts/ (analysis gate).",
2933
+ "- Validated cases under testcase/md/ and contracts/backend-test-case-manifest.json (case review runs independently as advisory evidence).",
2934
+ "- Validated Backend Test Analysis v2 under contracts/backend-test-analysis.json.",
2915
2935
  "- Validated Backend Test Execution Contract v1 under contracts/backend-test-execution.json (execution gate).",
2916
2936
  "Use 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.",
2917
2937
  "When targetMode is in-process (including demoted local npm/node managed servers): bootstrap the service inside function-scoped pytest fixtures under testcase/** — e.g. subprocess node server.js / startWelcomeServer with PORT=0 — and never require host-injected base URL env vars (clean-env shell will not provide WELCOME_BASE_URL / API_BASE_URL).",
@@ -3027,7 +3047,7 @@ function buildBackendTestSemanticReviewNode(sources, options = {}) {
3027
3047
  "Return exactly one pure JSON object with only verdict, findings, summary; no Markdown fence or surrounding prose.",
3028
3048
  "verdict must be pass or request-revision. Each findings[] item must contain exactly severity, caseId, testFile, testSymbol, contractRefs, issue, requiredChange.",
3029
3049
  "severity 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.",
3030
- "Minimal shape: {\"verdict\":\"pass\",\"findings\":[],\"summary\":\"No contract-backed semantic contradiction found.\"}",
3050
+ 'Minimal shape: {"verdict":"pass","findings":[],"summary":"No contract-backed semantic contradiction found."}',
3031
3051
  "Check 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.",
3032
3052
  "Do not use aliases such as file, symbol, refs, finding, or requiredFix; the strict contract requires testFile, testSymbol, contractRefs, issue, requiredChange.",
3033
3053
  "request-revision only for concrete semantic contradiction with reviewed cases/formal analysis evidence. No style findings.",
@@ -3125,7 +3145,7 @@ function buildClassifyBackendTestResultNode(sources) {
3125
3145
  "Read-only classifier for Backend Test Result v1.",
3126
3146
  "Return exactly one JSON object (prefer pure JSON; single fenced json block tolerated; no trailing prose).",
3127
3147
  "The 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.",
3128
- "Minimal shape: {\"category\":\"Unknown\",\"evidence\":[\"outcome=completed-with-failures\"],\"confidence\":0.5,\"notes\":\"Single-run evidence is insufficient for a stronger classification.\"}",
3148
+ 'Minimal shape: {"category":"Unknown","evidence":["outcome=completed-with-failures"],"confidence":0.5,"notes":"Single-run evidence is insufficient for a stronger classification."}',
3129
3149
  "Read contracts/backend-test-result-initial.json (run-owned initial Result v1). Do NOT invent pass rates from raw logs.",
3130
3150
  "category must be one of: ProductBug, TestBug, EnvFailure, ContractMismatch, FlakyTest, Unknown.",
3131
3151
  "Hard constraints:",
@@ -3178,7 +3198,7 @@ function buildTestRetrospectNode(sources) {
3178
3198
  "- Code coverage MUST come only from the validated contracts/code-coverage-v1.json artifact generated by coverage.py/pytest-cov or JaCoCo. Show line, branch, function/method, covered, total, ratio, threshold, status, source scope, requirement IDs, tool, commit, and artifact hash.",
3179
3199
  "- Stability MUST come from independent Stability Evidence: use successfulRuns / recordedRuns, same suite/version, and require n≥5; a single run is unavailable.",
3180
3200
  "- Use classify-backend-test-result-pi JSON as interpretive evidence only.",
3181
- "- NEVER rewrite a failed result as passed. Outcome gate (not this report) is authoritative for task success.",
3201
+ "- NEVER rewrite a failed result as passed. Result v1 is authoritative for testOutcome; pipeline completion and L-5 readiness are separate conclusions.",
3182
3202
  "",
3183
3203
  "## Report Structure:",
3184
3204
  "1. Maturity Rating with rationale",
@@ -3245,7 +3265,7 @@ function buildL5MetricsNode(sources) {
3245
3265
  "The direct upstream test-retrospect-pi output is the primary report to assess. Read it together with the run-owned Result v1, Case Manifest v1, Code Coverage v1, and Stability Evidence artifacts when present.",
3246
3266
  "Do not create a new DAG, rewrite the retrospective report, change test outcome, or modify any repository file.",
3247
3267
  "Return exactly one JSON object and no surrounding prose.",
3248
- "Required shape: {\"status\":\"ready\"|\"not-ready\",\"metrics\":{\"passRate\":metric,\"acCoverage\":metric,\"automationCoverage\":metric,\"stability\":metric,\"lineCoverage\":metric,\"branchCoverage\":metric,\"skipped\":metric,\"criticalRisks\":metric},\"blockingItems\":[string]}.",
3268
+ 'Required shape: {"status":"ready"|"not-ready","metrics":{"passRate":metric,"acCoverage":metric,"automationCoverage":metric,"stability":metric,"lineCoverage":metric,"branchCoverage":metric,"skipped":metric,"criticalRisks":metric},"blockingItems":[string]}.',
3249
3269
  "Each metric must contain numerator, denominator, ratio, threshold, status=pass|fail|unavailable, and reason (null only when passed).",
3250
3270
  "Use only explicit evidence. Missing or invalid required evidence is unavailable, never zero or an estimate.",
3251
3271
  "L-5 ready requires pass rate=100%, AC coverage=100%, automation coverage>=90%, stability>=95% with n>=5, line coverage>=80%, branch coverage>=70%, skipped=0, and zero blocking Critical risks.",
@@ -3270,75 +3290,115 @@ function buildBackendTestHybridDag(sources) {
3270
3290
  const globalConstraints = [
3271
3291
  ...taskConfig.hardConstraints,
3272
3292
  ...STANDARD_GLOBAL_CONSTRAINTS,
3273
- "backend-test-dag uses exactly 16 real top-level tasks and executes pytest exactly once.",
3274
- "Case and semantic request-revision verdicts fail at deterministic gates; no in-run revision or repair writer is authorized.",
3275
- "Analysis, execution, manifest, semantic review, single-run result, classification, canonical result, retrospective and outcome evidence remain run-owned and fail-closed.",
3293
+ "backend-test-dag uses exactly 12 real top-level tasks and executes pytest exactly once.",
3294
+ "Case review is advisory evidence consumed by canonical context, retrospective, and L-5; it does not authorize or block the pytest writer.",
3295
+ "Deterministic traceability is the only generated-asset hard gate before pytest.",
3296
+ "Analysis, execution, manifest, case review, traceability, single-run result, classification, canonical context, retrospective and L-5 evidence remain run-owned and fail-closed.",
3276
3297
  "Functional test case IDs must use BE-<MODULE>-<NNN> format.",
3277
3298
  "pytest writers may only create the initially declared testcase assets; production code, config, skip/xfail, swallowed failures and mock substitution are forbidden.",
3278
3299
  ];
3279
3300
  const analyze = buildAnalyzeInputsNode(sources);
3280
3301
  analyze.id = "analyze-and-discover-backend-test-pi";
3281
- analyze.outputContract = "Pure JSON envelope {analysis: Backend Test Analysis v2, execution: Backend Test Execution Contract v1}; no prose or writes.";
3302
+ analyze.outputContract =
3303
+ "Pure JSON envelope {analysis: Backend Test Analysis v2, execution: Backend Test Execution Contract v1}; no prose or writes.";
3282
3304
  analyze.subtask_prompt = `${analyze.subtask_prompt}\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.`;
3283
3305
  const contracts = {
3284
- id: "validate-backend-test-contracts-shell", depends_on: [analyze.id], role: "verifier", executor: "shell", complexity: "LOW",
3285
- writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
3306
+ id: "validate-backend-test-contracts-shell",
3307
+ depends_on: [analyze.id],
3308
+ role: "verifier",
3309
+ executor: "shell",
3310
+ complexity: "LOW",
3311
+ writePolicy: "read-only",
3312
+ allowedPaths: commonReadOnlyPaths(sources),
3313
+ forbiddenPaths: commonForbiddenPaths(sources),
3286
3314
  outputContract: "Materialize and validate contracts/backend-test-analysis.json and contracts/backend-test-execution.json.",
3287
- subtask_prompt: "Validate both backend-test intake contracts fail-closed.", shell: { commands: [], backendTestPipeline: "contracts", cwd: ".", timeoutMs: 60000 },
3315
+ subtask_prompt: "Validate both backend-test intake contracts fail-closed.",
3316
+ shell: {
3317
+ commands: [],
3318
+ backendTestPipeline: "contracts",
3319
+ cwd: ".",
3320
+ timeoutMs: 60000,
3321
+ },
3288
3322
  };
3289
3323
  const generateCases = buildGenerateBackendFunctionalCasesNode(sources);
3290
3324
  generateCases.id = "generate-backend-cases-and-manifest-pi";
3291
3325
  generateCases.depends_on = [contracts.id];
3292
- generateCases.outputContract = "Write testcase/md/** and end with one fenced json Backend Test Case Manifest v1 block matching the strict field contract.";
3326
+ generateCases.outputContract =
3327
+ "Write testcase/md/** and end with one fenced json Backend Test Case Manifest v1 block matching the strict field contract.";
3293
3328
  generateCases.subtask_prompt += `\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\n${BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS}`;
3294
- const manifest = buildBackendTestCaseManifestGateNode(sources, { dependsOn: [generateCases.id], fromNodeId: generateCases.id });
3295
- const reviewCases = buildReviewBackendCasesNode(sources, { dependsOn: [manifest.id, contracts.id] });
3296
- const caseGate = buildReviewBackendCasesGateNode(sources);
3297
- caseGate.depends_on = [reviewCases.id];
3298
- caseGate.dependsPolicy = undefined;
3299
- caseGate.outputContract = "Deterministic backend case review gate: exit 0 only when the first and only review emits VERDICT: pass.";
3300
- caseGate.subtask_prompt = "Block pytest generation when backend case review requests revision; do not authorize an in-run writer.";
3301
- caseGate.shell.verdictGate = { fromNodeId: reviewCases.id, accept: ["VERDICT: pass"], label: "backend case review", lineMode: "first-verdict-line" };
3329
+ const manifest = buildBackendTestCaseManifestGateNode(sources, {
3330
+ dependsOn: [generateCases.id],
3331
+ fromNodeId: generateCases.id,
3332
+ });
3333
+ const reviewCases = buildReviewBackendCasesNode(sources, {
3334
+ dependsOn: [manifest.id, contracts.id, "generate-backend-pytest-pi"],
3335
+ });
3302
3336
  const generatePytest = buildGenerateBackendPytestNode(sources);
3303
- generatePytest.depends_on = [caseGate.id, contracts.id];
3304
- const semanticReview = buildBackendTestSemanticReviewNode(sources, { dependsOn: [generatePytest.id, contracts.id, manifest.id] });
3305
- const semanticMaterialize = {
3306
- id: "validate-semantic-review-and-traceability-shell", depends_on: [semanticReview.id, manifest.id], role: "verifier", executor: "shell", complexity: "LOW",
3307
- writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
3308
- outputContract: "Materialize the only semantic review and validate pytest traceability.", subtask_prompt: "Materialize semantic facts and traceability; verdict authorization is handled by the next deterministic gate.",
3309
- shell: { commands: [], backendTestPipeline: "semantic-initial", cwd: ".", timeoutMs: 60000 },
3310
- };
3311
- const semanticGate = {
3312
- id: "backend-test-semantic-gate-shell", depends_on: [semanticMaterialize.id, semanticReview.id], role: "verifier", executor: "shell", complexity: "LOW",
3313
- writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
3314
- outputContract: "Pass-only authorization by reading contracts/backend-test-semantic-review.json; only verdict=pass proceeds to the single pytest execution.",
3315
- subtask_prompt: "Read the canonical semantic review artifact written by validate-semantic-review-and-traceability-shell. Authorize only when verdict is pass. Do not materialize, do not parse raw Pi Markdown or VERDICT lines, and do not authorize an in-run pytest writer.",
3337
+ generatePytest.depends_on = [manifest.id, contracts.id];
3338
+ const traceability = buildBackendTestTraceabilityGateNode(sources, {
3339
+ dependsOn: [generatePytest.id, manifest.id],
3340
+ });
3341
+ const execute = buildExecuteBackendPytestNode(sources, {
3342
+ id: "execute-and-parse-backend-pytest-shell",
3343
+ dependsOn: [traceability.id, contracts.id],
3344
+ reportStem: "backend-test-initial",
3345
+ });
3346
+ execute.shell.backendTestPipeline = "execute-parse-initial";
3347
+ const classify = buildClassifyBackendTestResultNode(sources);
3348
+ classify.depends_on = [execute.id];
3349
+ const context = {
3350
+ id: "materialize-classification-and-result-context-shell",
3351
+ depends_on: [classify.id, manifest.id, reviewCases.id, traceability.id],
3352
+ role: "verifier",
3353
+ executor: "shell",
3354
+ complexity: "LOW",
3355
+ writePolicy: "read-only",
3356
+ allowedPaths: commonReadOnlyPaths(sources),
3357
+ forbiddenPaths: commonForbiddenPaths(sources),
3358
+ outputContract: "Materialize Classification v1, copy the unique initial Result to canonical contracts/backend-test-result.json, and emit Result + Manifest + Classification + advisory case review + traceability context.",
3359
+ subtask_prompt: "Validate classification and materialize canonical single-run result context with auditable case review and traceability evidence, without repair eligibility or rerun.",
3316
3360
  shell: {
3317
3361
  commands: [],
3318
- backendTestPipeline: "semantic-initial",
3362
+ backendTestPipeline: "classification-result-context",
3319
3363
  cwd: ".",
3320
3364
  timeoutMs: 60000,
3321
3365
  },
3322
3366
  };
3323
- const execute = buildExecuteBackendPytestNode(sources, { id: "execute-and-parse-backend-pytest-shell", dependsOn: [semanticGate.id, contracts.id], reportStem: "backend-test-initial" });
3324
- execute.shell.backendTestPipeline = "execute-parse-initial";
3325
- const classify = buildClassifyBackendTestResultNode(sources);
3326
- classify.depends_on = [execute.id];
3327
- const context = {
3328
- id: "materialize-classification-and-result-context-shell", depends_on: [classify.id, manifest.id], role: "verifier", executor: "shell", complexity: "LOW",
3329
- writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
3330
- outputContract: "Materialize Classification v1, copy the unique initial Result to canonical contracts/backend-test-result.json, and emit Result + Manifest + Classification context.",
3331
- subtask_prompt: "Validate classification and materialize canonical single-run result context without repair eligibility or rerun.",
3332
- shell: { commands: [], backendTestPipeline: "classification-result-context", cwd: ".", timeoutMs: 60000 },
3333
- };
3334
3367
  const retrospect = buildTestRetrospectNode(sources);
3335
3368
  retrospect.depends_on = [context.id];
3336
3369
  retrospect.subtask_prompt = retrospect.subtask_prompt.replaceAll("select-effective-backend-test-result-shell", context.id);
3337
3370
  const l5Metrics = buildL5MetricsNode(sources);
3338
3371
  l5Metrics.depends_on = [retrospect.id];
3339
- const outcome = buildBackendTestOutcomeGateNode(sources);
3340
- const tasks = [analyze, contracts, generateCases, manifest, reviewCases, caseGate, generatePytest, semanticReview, semanticMaterialize, semanticGate, execute, classify, context, retrospect, l5Metrics, outcome];
3341
- const spec = { version: 3, title: `Backend test DAG: ${taskConfig.title}`, runtimeContract: GENERATED_DAG_RUNTIME_CONTRACT, outputLanguage: sources.outputLanguage ?? DEFAULT_DAG_OUTPUT_LANGUAGE, objective: extractObjective(sources.requirementMarkdown, taskConfig.title), successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId), globalConstraints, defaults: { ...BACKEND_TEST_DEFAULTS, contextProfile: taskConfig.contextProfile }, skillsByRole: BACKEND_TEST_SKILLS_BY_ROLE, executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS, tasks };
3372
+ const tasks = [
3373
+ analyze,
3374
+ contracts,
3375
+ generateCases,
3376
+ manifest,
3377
+ reviewCases,
3378
+ generatePytest,
3379
+ traceability,
3380
+ execute,
3381
+ classify,
3382
+ context,
3383
+ retrospect,
3384
+ l5Metrics,
3385
+ ];
3386
+ const spec = {
3387
+ version: 3,
3388
+ title: `Backend test DAG: ${taskConfig.title}`,
3389
+ runtimeContract: GENERATED_DAG_RUNTIME_CONTRACT,
3390
+ outputLanguage: sources.outputLanguage ?? DEFAULT_DAG_OUTPUT_LANGUAGE,
3391
+ objective: extractObjective(sources.requirementMarkdown, taskConfig.title),
3392
+ successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId),
3393
+ globalConstraints,
3394
+ defaults: {
3395
+ ...BACKEND_TEST_DEFAULTS,
3396
+ contextProfile: taskConfig.contextProfile,
3397
+ },
3398
+ skillsByRole: BACKEND_TEST_SKILLS_BY_ROLE,
3399
+ executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
3400
+ tasks,
3401
+ };
3342
3402
  applyDefaultReadOnlyRetryPolicy(spec);
3343
3403
  parseDagSpec(spec);
3344
3404
  assertValidDagSpec(spec);
@@ -3540,7 +3600,10 @@ function buildFrontendTestHybridDag(sources) {
3540
3600
  subtask_prompt: "Perform the final frontend case review after the sole permitted revision. Apply the same traceability, isolation, manifest, reset, session, snapshot, and evidence checks. First verdict line must be exact; any Important or Critical finding requires request-revision. Do not write files.",
3541
3601
  }, {
3542
3602
  id: "final-frontend-case-review-gate-shell",
3543
- depends_on: ["review-frontend-cases-pi", "review-frontend-cases-final-pi"],
3603
+ depends_on: [
3604
+ "review-frontend-cases-pi",
3605
+ "review-frontend-cases-final-pi",
3606
+ ],
3544
3607
  dependsPolicy: "all-or-condition-skip",
3545
3608
  role: "verifier",
3546
3609
  executor: "shell",
@@ -3689,7 +3752,11 @@ function buildFrontendTestHybridDag(sources) {
3689
3752
  forbiddenPaths: forbidden,
3690
3753
  outputContract: "Optional quality gate: pass only when frontend-test-result-v1 is outcome=passed and integrationMode=real with 0 failed/blocked and no missing AC. Does not gate retrospective closeout.",
3691
3754
  subtask_prompt: "Opt-in Delivery/Worker quality gate (frontendTest.strictOutcomeGate=true). Retrospective does not depend on this node.",
3692
- shell: { commands: [frontendTestOutcomeGate], cwd: ".", timeoutMs: 60000 },
3755
+ shell: {
3756
+ commands: [frontendTestOutcomeGate],
3757
+ cwd: ".",
3758
+ timeoutMs: 60000,
3759
+ },
3693
3760
  });
3694
3761
  }
3695
3762
  tasks.push({
@@ -4643,7 +4710,86 @@ function buildKnowledgeGraphBootstrapHybridDag(sources) {
4643
4710
  assertValidDagSpec(spec);
4644
4711
  return spec;
4645
4712
  }
4646
- function buildHybridDagForTemplate(sources, template) {
4713
+ export async function requireManagedTaskContractBinding(input) {
4714
+ const state = await observeTaskContract({
4715
+ repoRoot: input.repoRoot,
4716
+ taskId: input.taskId,
4717
+ });
4718
+ if (state.effectiveStatus !== "managed" || !state.ref) {
4719
+ const err = new Error(`dag generate requires managed Task Contract for task ${input.taskId} (effectiveStatus=${state.effectiveStatus}); run: loop-agent task contract adopt|apply --task ${input.taskId} ...`);
4720
+ err.code =
4721
+ state.effectiveStatus === "externally-modified"
4722
+ ? "EXTERNALLY_MODIFIED"
4723
+ : "NOT_MANAGED";
4724
+ throw err;
4725
+ }
4726
+ const ref = state.ref;
4727
+ return {
4728
+ schemaVersion: 1,
4729
+ taskId: ref.taskId,
4730
+ revision: ref.revision,
4731
+ projectionVersion: ref.projectionVersion,
4732
+ canonicalizerVersion: ref.canonicalizerVersion,
4733
+ taskConfigSchemaVersion: ref.taskConfigSchemaVersion,
4734
+ canonicalHash: ref.canonicalHash,
4735
+ taskConfigSha256: ref.taskConfigSha256,
4736
+ };
4737
+ }
4738
+ /** In-memory unit-test path only: produce a syntactically valid v4 binding. */
4739
+ function synthesizeInMemoryTaskContractBinding(sources) {
4740
+ const requirementSha = createHash("sha256")
4741
+ .update(sources.requirementMarkdown, "utf8")
4742
+ .digest("hex");
4743
+ const constraintsSha = createHash("sha256")
4744
+ .update(sources.constraintMarkdown ?? "", "utf8")
4745
+ .digest("hex");
4746
+ const taskConfigSha256 = createHash("sha256")
4747
+ .update(JSON.stringify(sources.taskConfig), "utf8")
4748
+ .digest("hex");
4749
+ const canonicalHash = createHash("sha256")
4750
+ .update(JSON.stringify({
4751
+ projectionVersion: 1,
4752
+ canonicalizerVersion: 1,
4753
+ taskConfigSchemaVersion: 1,
4754
+ sourceHashes: {
4755
+ requirementSha256: requirementSha,
4756
+ constraintsSha256: constraintsSha,
4757
+ },
4758
+ taskConfigSha256,
4759
+ }), "utf8")
4760
+ .digest("hex");
4761
+ return {
4762
+ schemaVersion: 1,
4763
+ taskId: sources.taskId,
4764
+ revision: 1,
4765
+ projectionVersion: 1,
4766
+ canonicalizerVersion: 1,
4767
+ taskConfigSchemaVersion: 1,
4768
+ canonicalHash,
4769
+ taskConfigSha256,
4770
+ };
4771
+ }
4772
+ async function resolveTaskContractBindingForGenerate(sources) {
4773
+ // Real CLI generation freezes the managed ref. Direct builder unit tests
4774
+ // may provide repoRoot-shaped fixture paths without a persisted task; keep
4775
+ // their deterministic synthetic binding path so topology/prompt tests remain
4776
+ // isolated from contract storage.
4777
+ if (sources.repoRoot) {
4778
+ const state = await observeTaskContract({
4779
+ repoRoot: sources.repoRoot,
4780
+ taskId: sources.taskId,
4781
+ });
4782
+ if (state.effectiveStatus === "managed" && state.ref) {
4783
+ return requireManagedTaskContractBinding({
4784
+ repoRoot: sources.repoRoot,
4785
+ taskId: sources.taskId,
4786
+ });
4787
+ }
4788
+ }
4789
+ return synthesizeInMemoryTaskContractBinding(sources);
4790
+ }
4791
+ async function buildHybridDagForTemplate(sources, template) {
4792
+ const taskContractBinding = await resolveTaskContractBindingForGenerate(sources);
4647
4793
  let spec;
4648
4794
  if (template === "frontend-implementation") {
4649
4795
  spec = buildFrontendHybridDagFromTask(sources);
@@ -4666,7 +4812,13 @@ function buildHybridDagForTemplate(sources, template) {
4666
4812
  spec = buildSupervisedHybridDag(standard, sources);
4667
4813
  }
4668
4814
  applyProjectGovernanceReview(spec, template, sources);
4815
+ // New generate path always emits DagSpec v4 + bindings.
4816
+ spec.version = 4;
4817
+ if (!spec.runtimeContract) {
4818
+ spec.runtimeContract = GENERATED_DAG_RUNTIME_CONTRACT;
4819
+ }
4669
4820
  spec.sourceBinding = buildDagSourceBinding(sources);
4821
+ spec.taskContractBinding = taskContractBinding;
4670
4822
  assertNoGovernanceFlagOnDisallowedTemplate(spec, template);
4671
4823
  parseDagSpec(spec);
4672
4824
  assertValidDagSpec(spec);
@@ -4694,7 +4846,7 @@ function assertNoGovernanceFlagOnDisallowedTemplate(spec, template) {
4694
4846
  }
4695
4847
  }
4696
4848
  }
4697
- export function buildHybridDagFromTask(sources, options = {}) {
4849
+ export async function buildHybridDagFromTask(sources, options = {}) {
4698
4850
  const selection = resolveTaskDagTemplateSelection({
4699
4851
  taskKind: sources.taskConfig.taskKind,
4700
4852
  title: sources.taskConfig.title,
@@ -4836,7 +4988,8 @@ function applyProjectGovernanceReview(spec, template, sources) {
4836
4988
  insertGovernanceStandardGate(spec, sources);
4837
4989
  return;
4838
4990
  }
4839
- if (template === "review-gated-dag" || template === "supervised-implementation") {
4991
+ if (template === "review-gated-dag" ||
4992
+ template === "supervised-implementation") {
4840
4993
  const review = spec.tasks.find((task) => task.id === "review-pi");
4841
4994
  if (review)
4842
4995
  enableProjectGovernanceOnNode(review);
@@ -5320,7 +5473,7 @@ export async function writeHybridDagDraft(sources, outputPath, options = {}) {
5320
5473
  const preparedSources = template === "frontend-implementation"
5321
5474
  ? await prepareFrontendMockSources(sources)
5322
5475
  : sources;
5323
- const spec = buildHybridDagForTemplate(preparedSources, template);
5476
+ const spec = await buildHybridDagForTemplate(preparedSources, template);
5324
5477
  await writeFile(outputPath, `${JSON.stringify(spec, null, 2)}\n`, "utf-8");
5325
5478
  return {
5326
5479
  taskId: sources.taskId,