@tea-agent/loop-agent 0.41.1-beta.0 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/CHANGELOG.md +47 -267
  2. package/dist/application/dag/generate-task-dag.js +19 -75
  3. package/dist/application/task-lifecycle/advance.js +5 -24
  4. package/dist/application/task-lifecycle/observe.js +17 -171
  5. package/dist/application/task-lifecycle/plan-transitions.js +7 -42
  6. package/dist/application/task-lifecycle/recommendations.js +2 -13
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +0 -7
  9. package/dist/cli/program.js +0 -5
  10. package/dist/commands/client-recovery.js +0 -3
  11. package/dist/commands/init.js +1 -27
  12. package/dist/commands/task-advance.js +0 -19
  13. package/dist/executors/dag-pi-executor.js +231 -3784
  14. package/dist/executors/pi-executor.js +4 -15
  15. package/dist/executors/shell-executor.js +217 -945
  16. package/dist/executors/shell-write-guard.js +0 -7
  17. package/dist/shared/dag-failure-category.js +0 -12
  18. package/dist/shared/openspec-spec.js +4 -70
  19. package/dist/shared/operator/capabilities.js +0 -7
  20. package/dist/task/config-types.js +7 -34
  21. package/dist/task/contract/adopt.js +0 -4
  22. package/dist/task/contract/import-revision.js +0 -4
  23. package/dist/task/contract/project.js +0 -3
  24. package/dist/task/contract/schema.js +1 -2
  25. package/dist/task/frontend-project-capability.js +20 -203
  26. package/dist/task/runtime.js +2 -5
  27. package/dist/task/source-prepare/build-draft.js +3 -3
  28. package/dist/task/source-prepare/fragment-inventory.js +9 -15
  29. package/dist/task/source-prepare/prepare.js +1 -89
  30. package/dist/task/source-prepare/semantic-intake.js +2 -6
  31. package/dist/task/source-references.js +1 -22
  32. package/dist/worker/console/chat/chat-ui-policy.js +4 -2
  33. package/dist/worker/console/chat/pi-runtime.js +176 -9
  34. package/dist/worker/console/chat/repo-browser.js +10 -3
  35. package/dist/worker/console/chat/routes.js +42 -22
  36. package/dist/worker/console/chat/semantic-activity.js +12 -11
  37. package/dist/worker/console/chat/session-stats.js +96 -0
  38. package/dist/worker/console/chat/session-store.js +117 -70
  39. package/dist/worker/console/chat/shortcuts.js +1 -1
  40. package/dist/worker/console/chat/turn-process.js +139 -60
  41. package/dist/worker/console/index.js +0 -3
  42. package/dist/worker/console/operation-store.js +6 -226
  43. package/dist/worker/console/operator-actions.js +7 -169
  44. package/dist/worker/console/pi-plugins.js +3 -1
  45. package/dist/worker/console/server.js +0 -3
  46. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-D7oU983t.js} +1 -1
  47. package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-BrKSRdFS.js} +1 -1
  48. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-CiXV7L56.js} +1 -1
  49. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-D_Hs_gIG.js} +1 -1
  50. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-DbM-Lqqh.js} +1 -1
  51. package/dist/worker/console/static/assets/channel-D1xajGpe.js +1 -0
  52. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-D1M8uqJw.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-4D3RFHT6.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CdwpY_n9.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-BLeLZ9PG.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-CeRNEMvc.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-CjkHP8Vy.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-B54ZUQY-.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-4bKaMVmx.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-Cyjfznu_.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CDN-zGFV.js} +1 -1
  62. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BmuGTfRr.js +1 -0
  63. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BmuGTfRr.js +1 -0
  64. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-HxJmz411.js} +1 -1
  65. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-DtSt5K2w.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-VtkSpRmG.js} +1 -1
  67. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-Bj4DT4du.js} +1 -1
  68. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-Bpuk6BmP.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-BHYGv_yO.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-DEx3WwnT.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-Cj0dS5e8.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-B97h8l21.js} +1 -1
  73. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-DMSFAhBd.js} +1 -1
  74. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-DhVOCbt6.js} +1 -1
  75. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-GlakITWL.js} +1 -1
  76. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-VxPpu9eQ.js} +1 -1
  77. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bs_e5DRT.js} +1 -1
  78. package/dist/worker/console/static/assets/index-BLzcAykx.js +451 -0
  79. package/dist/worker/console/static/assets/index-Cs9JUSPs.css +1 -0
  80. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-BXYuU4Uw.js} +1 -1
  81. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-C34erCbU.js} +1 -1
  82. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-DZCAiOU3.js} +1 -1
  83. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-C9gaTdUm.js} +1 -1
  84. package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CvI0Z17U.js} +1 -1
  85. package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-DMMSIx7j.js} +5 -5
  86. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-BKKP0EO6.js} +1 -1
  87. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-C3LiFbl5.js} +1 -1
  88. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-Bb3Om2Dz.js} +1 -1
  89. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-DNthMjzc.js} +1 -1
  90. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-miFsr4Tv.js} +1 -1
  91. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-yXUncmn6.js} +1 -1
  92. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-DLeWKLak.js} +1 -1
  93. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-CI3he1R3.js} +1 -1
  94. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-B7jfDkS5.js} +1 -1
  95. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-BmrZod7J.js} +1 -1
  96. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-D9ykKEWP.js +1 -0
  97. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-CDOlHVSY.js} +2 -2
  98. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BvPOXALT.js +8 -0
  99. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-Cz-XX1gy.js} +1 -1
  100. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-caV4cI8C.js} +1 -1
  101. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-dImPlnIO.js} +1 -1
  102. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CtRYVJcn.js} +1 -1
  103. package/dist/worker/console/static/index.html +2 -2
  104. package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
  105. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
  106. package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
  107. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
  108. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
  109. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
  110. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  111. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
  112. package/dist/worker/console/workspace-context.js +1 -111
  113. package/dist/worker/observability/read-model.js +4 -0
  114. package/dist/worker/observe/static/dag-node-purpose.js +0 -5
  115. package/dist/worker/observe/static/views/dag-inspector.js +2 -7
  116. package/dist/worker/observe/static/views/session-timeline.js +46 -15
  117. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  118. package/dist/workflows/dag/contract-validator-registrations.js +2 -1
  119. package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
  120. package/dist/workflows/dag/frontend-plan-render.js +1 -2
  121. package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
  122. package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
  123. package/dist/workflows/dag/frontend-recovery-run.js +20 -33
  124. package/dist/workflows/dag/frontend-repair.js +432 -1
  125. package/dist/workflows/dag/frontend-review-context.js +15 -261
  126. package/dist/workflows/dag/frontend-verification-trace.js +24 -249
  127. package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
  128. package/dist/workflows/dag/init-hybrid.js +494 -685
  129. package/dist/workflows/dag/interrupt-request.js +0 -7
  130. package/dist/workflows/dag/node-execution.js +4 -732
  131. package/dist/workflows/dag/prompt.js +1 -34
  132. package/dist/workflows/dag/report.js +1 -37
  133. package/dist/workflows/dag/rerun-feedback.js +1 -256
  134. package/dist/workflows/dag/rerun-plan.js +2 -10
  135. package/dist/workflows/dag/rerun-run.js +0 -23
  136. package/dist/workflows/dag/rerun-task.js +0 -29
  137. package/dist/workflows/dag/retry-policy.js +104 -214
  138. package/dist/workflows/dag/runner.js +122 -429
  139. package/dist/workflows/dag/scheduler.js +16 -114
  140. package/dist/workflows/dag/types.js +12 -239
  141. package/dist/workflows/dag/validate.js +12 -28
  142. package/docs/init-surface.manifest.json +12 -30
  143. package/docs/skills/vetted-skill-registry.md +2 -4
  144. package/docs/templates/README.md +0 -2
  145. package/docs/templates/agent-dag-report.schema.json +2 -8
  146. package/docs/templates/agent-dag.schema.json +1 -1
  147. package/docs/templates/frontend-implementation-contract.schema.json +1 -4
  148. package/harness.json +3 -3
  149. package/package.json +1 -1
  150. package/skills/frontend-bounded-implement/SKILL.md +14 -15
  151. package/skills/frontend-design-review/SKILL.md +41 -22
  152. package/skills/frontend-implementation/SKILL.md +52 -0
  153. package/skills/frontend-implementation/references/code-standards.md +33 -0
  154. package/skills/frontend-implementation/references/design-spec.md +56 -0
  155. package/skills/frontend-implementation/references/node-contracts.md +31 -0
  156. package/skills/frontend-review/SKILL.md +15 -20
  157. package/skills/frontend-review/references/review-findings.md +7 -6
  158. package/skills/frontend-verification/SKILL.md +1 -1
  159. package/skills/loop-agent/references/command-reference.md +0 -1
  160. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  161. package/dist/commands/dag-follow-up.js +0 -138
  162. package/dist/executors/pi-read-budget-policy.js +0 -239
  163. package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
  164. package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
  165. package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
  166. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
  167. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
  168. package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
  169. package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
  170. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
  171. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
  172. package/dist/worker/console/static-src/active-run-badge.js +0 -17
  173. package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
  174. package/dist/workflows/dag/dag-retry-schema.js +0 -138
  175. package/dist/workflows/dag/frontend-closeout.js +0 -221
  176. package/dist/workflows/dag/frontend-design-policy.js +0 -400
  177. package/dist/workflows/dag/frontend-human-decision.js +0 -182
  178. package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
  179. package/dist/workflows/dag/frontend-read-budgets.js +0 -12
  180. package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
  181. package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
  182. package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
  183. package/dist/workflows/dag/frontend-review-findings.js +0 -270
  184. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
  185. package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
  186. package/dist/workflows/dag/frontend-shape-facts.js +0 -409
  187. package/dist/workflows/dag/frontend-shape.js +0 -427
  188. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
  189. package/dist/workflows/dag/frontend-split-application-service.js +0 -203
  190. package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
  191. package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
  192. package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
  193. package/dist/workflows/dag/frontend-writer-admission.js +0 -285
  194. package/dist/workflows/dag/frontend-writer-status.js +0 -256
  195. package/dist/workflows/dag/recovery-lease.js +0 -80
  196. package/docs/templates/frontend-implementation-dag.json +0 -89
  197. package/docs/templates/spec-registry.schema.json +0 -45
  198. package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
  199. package/skills/frontend-contract/SKILL.md +0 -23
  200. package/skills/frontend-contract/references/contract-protocol.md +0 -34
  201. package/skills/frontend-plan/SKILL.md +0 -26
  202. package/skills/frontend-plan/references/decision-contract.md +0 -37
  203. package/skills/frontend-plan/references/design-decisions.md +0 -17
  204. package/skills/frontend-scout/SKILL.md +0 -25
  205. package/skills/frontend-scout/references/design-evidence.md +0 -16
  206. package/skills/frontend-scout/references/scout-evidence.md +0 -23
@@ -3,8 +3,7 @@ import path from "node:path";
3
3
  import { isPauseOnHumanDecisionGate } from "./decision-envelope.js";
4
4
  import { evaluateConditionExpression } from "./dynamic-runtime/condition.js";
5
5
  import { sha256Hex } from "./frontend-implementation-contract.js";
6
- import { boundaryForTargetNode, computeFrontendShapeNodeDigest, evaluateFrontendShapeBoundary, } from "./frontend-shape-facts.js";
7
- import { FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT, frontendWriterAdmissionResultV1Schema, } from "./frontend-writer-admission.js";
6
+ import { frontendPrewriteResultV1Schema, FRONTEND_PREWRITE_RESULT_ARTIFACT_NAME, } from "./frontend-prewrite-gate.js";
8
7
  import { FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH, FRONTEND_RECOVERY_INTENT_REL_DIR, } from "./frontend-recovery-plan.js";
9
8
  export function isConditionSkippedReason(reason) {
10
9
  return Boolean(reason?.startsWith("condition "));
@@ -112,25 +111,16 @@ async function mapConcurrent(items, limit, fn) {
112
111
  }
113
112
  export const FRONTEND_WRITER_NODE_IDS = [
114
113
  "frontend-implement-pi",
114
+ "frontend-repair-pi",
115
115
  ];
116
+ /** Run-relative artifact location written by the prewrite gate generator. */
117
+ export const FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT = path.posix.join("contracts", FRONTEND_PREWRITE_RESULT_ARTIFACT_NAME);
116
118
  /**
117
- * Run-relative artifact location read by the frontend writer admission gate.
118
- * M8 re-pointed the legacy prewrite-result source at the new
119
- * frontend-writer-admission-result.json artifact (written by the
120
- * frontend-writer-admission-shell); the exported name is retained because
121
- * runner.ts / node-execution.ts reference it as the admission evidence source.
122
- */
123
- export const FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT = FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT;
124
- export { FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT };
125
- /**
126
- * Read and validate the writer admission result artifact.
127
- * Fail-closed: a missing file or invalid payload returns ok:false and never
128
- * throws. This is the M8 write authorization source (replaces the legacy
129
- * frontend-prewrite-result-v1 for dispatch; the legacy artifact remains
130
- * produced only by the shadow `runFrontendPrewriteGate` library export).
119
+ * Read and validate the frontend-prewrite-result-v1 artifact. Fail-closed:
120
+ * a missing file or invalid payload returns ok:false and never throws.
131
121
  */
132
122
  export async function readFrontendPrewriteResult(runDir) {
133
- const artifactPath = path.join(runDir, FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT);
123
+ const artifactPath = path.join(runDir, "contracts", FRONTEND_PREWRITE_RESULT_ARTIFACT_NAME);
134
124
  let raw;
135
125
  try {
136
126
  raw = await readFile(artifactPath, "utf8");
@@ -139,7 +129,7 @@ export async function readFrontendPrewriteResult(runDir) {
139
129
  if (error.code === "ENOENT") {
140
130
  return {
141
131
  ok: false,
142
- reason: `frontend writer admission artifact missing: ${artifactPath}`,
132
+ reason: `frontend prewrite result artifact missing: ${artifactPath}`,
143
133
  };
144
134
  }
145
135
  throw error;
@@ -152,30 +142,22 @@ export async function readFrontendPrewriteResult(runDir) {
152
142
  catch (error) {
153
143
  return {
154
144
  ok: false,
155
- reason: `frontend writer admission artifact is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
145
+ reason: `frontend prewrite result artifact is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
156
146
  };
157
147
  }
158
- const result = frontendWriterAdmissionResultV1Schema.safeParse(parsed);
148
+ const result = frontendPrewriteResultV1Schema.safeParse(parsed);
159
149
  if (!result.success) {
160
150
  return {
161
151
  ok: false,
162
- reason: "frontend writer admission artifact failed schema validation",
152
+ reason: "frontend prewrite result artifact failed schema validation",
163
153
  };
164
154
  }
165
155
  return { ok: true, result: result.data, artifactHash };
166
156
  }
167
- /**
168
- * M8 alias: read the writer admission result artifact under the explicit name.
169
- * Same fail-closed semantics as `readFrontendPrewriteResult`.
170
- */
171
- export async function readFrontendWriterAdmissionResult(runDir) {
172
- return readFrontendPrewriteResult(runDir);
173
- }
174
- /** Authorize only the `accepted` classification (four-state admission).
175
- * `requires-human-approval`, `blocked`, and `stale` are all denied. */
157
+ /** Authorize only accepted / accepted-normalized classifications. */
176
158
  export function isFrontendWriterAuthorized(result) {
177
159
  return result.classification === "accepted" ||
178
- result.classification === "admitted"
160
+ result.classification === "accepted-normalized"
179
161
  ? "authorized"
180
162
  : "denied";
181
163
  }
@@ -440,86 +422,6 @@ export async function executeDagRanksOnce(input) {
440
422
  }
441
423
  const frontendAdmissionSkippedSet = new Set(frontendAdmissionSettled);
442
424
  const runnableAfterAdmission = actuallyRunnable.filter((id) => !frontendAdmissionSkippedSet.has(id));
443
- // Shape boundaries are rank gates: evaluate every protected candidate
444
- // before creating an executor or starting any sibling in the rank.
445
- const shapeBlocked = new Set();
446
- const checkedBoundaries = new Set();
447
- let shapeTerminalCommitted = false;
448
- for (const id of runnableAfterAdmission) {
449
- const mapping = boundaryForTargetNode(id);
450
- if (!mapping || checkedBoundaries.has(mapping.boundary))
451
- continue;
452
- checkedBoundaries.add(mapping.boundary);
453
- const sourceNode = input.state.nodes[mapping.sourceNodeId];
454
- const fact = input.state.frontendShapeState?.committedFacts[mapping.boundary];
455
- const outcome = evaluateFrontendShapeBoundary({
456
- boundary: mapping.boundary,
457
- sourceNode: sourceNode
458
- ? { status: sourceNode.status, digest: computeFrontendShapeNodeDigest(sourceNode) }
459
- : undefined,
460
- fact,
461
- });
462
- if (outcome.kind === "continue") {
463
- if (input.state.frontendShapeState) {
464
- input.state.frontendShapeState.lastBoundary = mapping.boundary;
465
- input.state.frontendShapeState.lastOutcome = outcome.kind;
466
- await input.persistState();
467
- }
468
- continue;
469
- }
470
- if (outcome.kind === "blocked") {
471
- if (input.state.frontendShapeState) {
472
- input.state.frontendShapeState.lastBoundary = mapping.boundary;
473
- input.state.frontendShapeState.lastOutcome = outcome.kind;
474
- }
475
- const finishedAt = new Date().toISOString();
476
- for (const targetId of mapping.targetNodeIds) {
477
- if (!runnableAfterAdmission.includes(targetId))
478
- continue;
479
- const node = input.state.nodes[targetId];
480
- if (!node || node.status !== "PENDING")
481
- continue;
482
- node.status = "SKIPPED";
483
- node.skippedReason = outcome.reason;
484
- node.finishedAt = finishedAt;
485
- shapeBlocked.add(targetId);
486
- }
487
- await input.persistState();
488
- continue;
489
- }
490
- if (!fact || !input.commitFrontendShapeTransition) {
491
- const finishedAt = new Date().toISOString();
492
- for (const targetId of mapping.targetNodeIds) {
493
- const node = input.state.nodes[targetId];
494
- if (!node || node.status !== "PENDING")
495
- continue;
496
- node.status = "SKIPPED";
497
- node.skippedReason = "frontend-shape-transition-cas-unavailable";
498
- node.finishedAt = finishedAt;
499
- shapeBlocked.add(targetId);
500
- }
501
- await input.persistState();
502
- continue;
503
- }
504
- const committed = await input.commitFrontendShapeTransition({
505
- boundary: mapping.boundary,
506
- outcome: outcome.kind,
507
- reason: outcome.reason,
508
- expectedRevision: input.state.frontendShapeState?.revision ?? 0,
509
- fact,
510
- });
511
- if (committed.ok) {
512
- shapeTerminalCommitted = true;
513
- break;
514
- }
515
- // A failed transition CAS must remain zero-write. Stop the controller
516
- // before any executor starts; never compensate with a capsule-less
517
- // terminal state or mutate pending descendants.
518
- throw new Error(`frontend shape transition CAS failed at ${mapping.boundary}: ${committed.reason}`);
519
- }
520
- if (shapeTerminalCommitted)
521
- break;
522
- const runnableAfterShape = runnableAfterAdmission.filter((id) => !shapeBlocked.has(id));
523
425
  const blocked = pending.filter((id) => {
524
426
  const task = input.tasksById.get(id);
525
427
  return (dependencyReadiness(task, input.state.nodes, input.tasksById) === "skip");
@@ -534,12 +436,12 @@ export async function executeDagRanksOnce(input) {
534
436
  if (blocked.length > 0) {
535
437
  await input.persistState();
536
438
  }
537
- const pauseGateRunnable = runnableAfterShape.filter((id) => {
439
+ const pauseGateRunnable = runnableAfterAdmission.filter((id) => {
538
440
  const task = input.tasksById.get(id);
539
441
  return isPauseOnHumanDecisionGate(task);
540
442
  });
541
- const regularRunnable = runnableAfterShape.filter((id) => !pauseGateRunnable.includes(id));
542
- const rankWriterNodeIds = runnableAfterShape.filter((id) => {
443
+ const regularRunnable = runnableAfterAdmission.filter((id) => !pauseGateRunnable.includes(id));
444
+ const rankWriterNodeIds = runnableAfterAdmission.filter((id) => {
543
445
  const task = input.tasksById.get(id);
544
446
  return (task?.executor === "pi" &&
545
447
  task.toolProfile === "write" &&
@@ -1,13 +1,8 @@
1
1
  import { z } from "zod";
2
- import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, OPENSPEC_SPEC_EXT_RE, } from "../../shared/openspec-spec.js";
2
+ import { isOpenspecSpecFilePath } from "../../shared/openspec-spec.js";
3
3
  import { campaignBudgetSchema, } from "../../application/evaluation/budget.js";
4
4
  import { assertDagPromptSourceRule } from "./prompt-source.js";
5
- // Leaf-module import: dagRetryPolicySchema must be defined by a module that
6
- // never transitively reaches types.ts during init (retry-policy does, via
7
- // frontend-recovery-capsule → frontend-recovery-lineage →
8
- // frontend-implementation-contract), or a run whose entry module initializes
9
- // retry-policy first reads an undefined schema here.
10
- import { dagRetryPolicySchema, TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE, } from "./dag-retry-schema.js";
5
+ import { dagRetryPolicySchema, TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE, } from "./retry-policy.js";
11
6
  import { dagLivenessPolicySchema, } from "./liveness-policy.js";
12
7
  import { dagOutputProtocolSchema } from "./output-protocol.js";
13
8
  export const dagComplexitySchema = z.enum(["HIGH", "MED", "LOW"]);
@@ -190,16 +185,7 @@ const dagFrontendNodeIdSchema = z
190
185
  .regex(/^[a-z][a-z0-9-]*$/, "frontend node id must be kebab-case");
191
186
  const openspecCandidatePathSchema = z
192
187
  .string()
193
- .refine((candidate) => !candidate.includes("\\") &&
194
- !candidate.startsWith("/") &&
195
- !candidate.split("/").includes("..") &&
196
- OPENSPEC_SPEC_EXT_RE.test(candidate), "openspec candidate must be a repo-relative supported specification file");
197
- const openspecCandidateSnapshotSchema = z
198
- .object({
199
- path: openspecCandidatePathSchema,
200
- sha256: z.string().regex(/^[a-f0-9]{64}$/),
201
- })
202
- .strict();
188
+ .refine((candidate) => !candidate.includes("\\") && isOpenspecSpecFilePath(candidate), "openspec candidate must be a repo-relative supported file under openspec/schemas/, openspec/project-specs/, or ai_workspace/");
203
189
  export const dagFrontendPrewriteGateSchema = z.object({
204
190
  schemaVersion: z.literal(1),
205
191
  planFromNodeId: dagFrontendNodeIdSchema,
@@ -218,24 +204,7 @@ export const dagFrontendPrewriteGateSchema = z.object({
218
204
  outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
219
205
  requireSourceFreshness: z.literal(true),
220
206
  implementationWriteSet: z.array(z.string().min(1)).min(1).optional(),
221
- /**
222
- * Generation-frozen roots that bound custom normative directories. Omitted
223
- * legacy DAGs retain the canonical OpenSpec + ai_workspace roots.
224
- */
225
- openspecSpecRoots: z
226
- .array(z.string().refine((root) => isValidFrontendSpecRoot(root), "openspec spec root must be a normalized repo-relative directory"))
227
- .optional(),
228
207
  openspecCandidatePaths: z.array(openspecCandidatePathSchema).default([]),
229
- /** Read-only selector output; omitted for scan-strict and legacy DAGs. */
230
- openspecSelectionNodeId: dagFrontendNodeIdSchema.optional(),
231
- /** Contract-declared and source-explicit paths that cannot be downgraded. */
232
- openspecMandatoryPaths: z.array(openspecCandidatePathSchema).optional(),
233
- /**
234
- * Content fingerprints frozen with the generation-time OpenSpec scope. New
235
- * DAGs use these to reject a plan whose cited specification changed after it
236
- * was read; legacy DAGs without the field retain existence-only drift checks.
237
- */
238
- openspecCandidateSnapshots: z.array(openspecCandidateSnapshotSchema).optional(),
239
208
  /**
240
209
  * Generation-frozen component/theme specification bucket. Derived from
241
210
  * `designEvidence.classified.component ∪ theme ∪ rule.components` at DAG
@@ -280,28 +249,14 @@ export const dagFrontendPrewriteGateSchema = z.object({
280
249
  scanStrict: z.array(openspecCandidatePathSchema).default([]),
281
250
  })
282
251
  .optional(),
283
- }).superRefine((gate, context) => {
284
- const roots = gate.openspecSpecRoots ?? DEFAULT_FRONTEND_SPEC_ROOTS;
285
- const candidateGroups = [
286
- ["openspecCandidatePaths", gate.openspecCandidatePaths],
287
- ["openspecMandatoryPaths", gate.openspecMandatoryPaths],
288
- ["componentSpecCandidatePaths", gate.componentSpecCandidatePaths],
289
- ["openspecCandidateSources.declared", gate.openspecCandidateSources?.declared],
290
- ["openspecCandidateSources.taskSourceCited", gate.openspecCandidateSources?.taskSourceCited],
291
- ["openspecCandidateSources.scanStrict", gate.openspecCandidateSources?.scanStrict],
292
- ];
293
- for (const [field, paths] of candidateGroups) {
294
- for (const [index, candidate] of (paths ?? []).entries()) {
295
- if (!isFrontendSpecFilePath(candidate, roots)) {
296
- context.addIssue({
297
- code: z.ZodIssueCode.custom,
298
- path: [...field.split("."), index],
299
- message: "openspec candidate must be a supported file under the generation-frozen openspec spec roots",
300
- });
301
- }
302
- }
303
- }
304
252
  });
253
+ export const dagFrontendLintBaselineSchema = z
254
+ .object({
255
+ schemaVersion: z.literal(1),
256
+ lintCommands: z.array(z.string().min(1)).min(1),
257
+ lintEvidence: dagShellVerifyEvidenceSchema,
258
+ })
259
+ .strict();
305
260
  export const dagFrontendVerificationBundleSchema = z
306
261
  .object({
307
262
  schemaVersion: z.literal(1),
@@ -315,10 +270,6 @@ export const dagFrontendVerificationBundleSchema = z
315
270
  behaviorEvidence: dagShellVerifyEvidenceSchema,
316
271
  lintBaselineNodeId: dagFrontendNodeIdSchema.optional(),
317
272
  writerNodeIds: z.array(dagFrontendNodeIdSchema).optional(),
318
- /** "initial" = first-pass assess bundle; "repair" = the convergence
319
- * loop's post-repair reverify, which re-runs the same frozen commands
320
- * and fails on any failure (still no same-run repair branch inside the
321
- * node itself). */
322
273
  mode: z.enum(["initial", "repair"]),
323
274
  })
324
275
  .superRefine((bundle, context) => {
@@ -394,135 +345,6 @@ export const dagFrontendReviewContextSchema = z.object({
394
345
  schemaVersion: z.literal(1),
395
346
  requireBaseline: z.literal(true),
396
347
  });
397
- /**
398
- * Deterministic frontend contract-import shell (micro topology). Re-validates
399
- * the generation-frozen managed Contract's source/binding/schema freshness and
400
- * emits a digest. It never invokes a model and never rewrites requirements —
401
- * micro requires a pre-validated managed Contract, not natural-language intake.
402
- */
403
- export const dagFrontendContractImportSchema = z.object({
404
- schemaVersion: z.literal(1),
405
- artifactName: z.string().regex(/^[a-z0-9][a-z0-9._-]*\.json$/),
406
- outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
407
- requireSourceFreshness: z.literal(true),
408
- });
409
- /**
410
- * Deterministic frontend design-policy shell (M8). Materializes the canonical
411
- * implementation contract from the plan node and evaluates the design policy
412
- * (Mock strategy, source freshness, component spec candidates, writeSet
413
- * patterns, allowed dependencies). Runs BEFORE frontend-design-review-pi, so it
414
- * never consumes a design verdict; the verdict is enforced by the writer
415
- * admission shell instead. Replaces the prewrite-gate authorization path.
416
- */
417
- export const dagFrontendDesignPolicySchema = z
418
- .object({
419
- schemaVersion: z.literal(1),
420
- planFromNodeId: dagFrontendNodeIdSchema,
421
- requiredRequirementIds: z
422
- .array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/))
423
- .default([]),
424
- allowedMockStrategies: z
425
- .array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
426
- .min(1),
427
- /** Frozen Mock verification command labels the DAG will actually execute. */
428
- mockCommandLabels: z.array(z.string()).default([]),
429
- artifactName: z.string().regex(/^[a-z0-9][a-z0-9._-]*\.json$/),
430
- outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
431
- requireSourceFreshness: z.literal(true),
432
- implementationWriteSet: z.array(z.string().min(1)).min(1).optional(),
433
- /** Generation-frozen roots that bound custom normative directories. */
434
- openspecSpecRoots: z
435
- .array(z.string().refine((root) => isValidFrontendSpecRoot(root), "openspec spec root must be a normalized repo-relative directory"))
436
- .optional(),
437
- openspecCandidatePaths: z.array(openspecCandidatePathSchema).default([]),
438
- /** Read-only selector output; omitted for scan-strict and legacy DAGs. */
439
- openspecSelectionNodeId: dagFrontendNodeIdSchema.optional(),
440
- /** Contract-declared and source-explicit paths that cannot be downgraded. */
441
- openspecMandatoryPaths: z.array(openspecCandidatePathSchema).optional(),
442
- openspecCandidateSnapshots: z
443
- .array(openspecCandidateSnapshotSchema)
444
- .optional(),
445
- /**
446
- * Generation-frozen component/theme specification bucket. Derived from
447
- * `designEvidence.classified.component ∪ theme ∪ rule.components` at DAG
448
- * generation. When non-empty and the canonical contract has UI-visible
449
- * work, the design policy requires a non-empty `uiComponentChoices` and
450
- * cross-checks each `specReference.path` against this set + the effective
451
- * plan openspec-citations block.
452
- */
453
- componentSpecCandidatePaths: z.array(openspecCandidatePathSchema).optional(),
454
- /** OpenSpec gate policy (cited | scan-strict); generation always writes it. */
455
- openspecPolicy: z.enum(["cited", "scan-strict"]).optional(),
456
- /** Generation-frozen candidate source metadata (diagnostic). */
457
- openspecCandidateSources: z
458
- .object({
459
- declared: z.array(openspecCandidatePathSchema).default([]),
460
- taskSourceCited: z.array(openspecCandidatePathSchema).default([]),
461
- scanStrict: z.array(openspecCandidatePathSchema).default([]),
462
- })
463
- .optional(),
464
- /** Dependency identifiers authorized at generation time (empty = none). */
465
- allowedDependencies: z.array(z.string()).default([]),
466
- })
467
- .superRefine((gate, context) => {
468
- const roots = gate.openspecSpecRoots ?? DEFAULT_FRONTEND_SPEC_ROOTS;
469
- const candidateGroups = [
470
- ["openspecCandidatePaths", gate.openspecCandidatePaths],
471
- ["openspecMandatoryPaths", gate.openspecMandatoryPaths],
472
- ["componentSpecCandidatePaths", gate.componentSpecCandidatePaths],
473
- ["openspecCandidateSources.declared", gate.openspecCandidateSources?.declared],
474
- ["openspecCandidateSources.taskSourceCited", gate.openspecCandidateSources?.taskSourceCited],
475
- ["openspecCandidateSources.scanStrict", gate.openspecCandidateSources?.scanStrict],
476
- ];
477
- for (const [field, paths] of candidateGroups) {
478
- for (const [index, candidate] of (paths ?? []).entries()) {
479
- if (!isFrontendSpecFilePath(candidate, roots)) {
480
- context.addIssue({
481
- code: z.ZodIssueCode.custom,
482
- path: [...field.split("."), index],
483
- message: "openspec candidate must be a supported file under the generation-frozen openspec spec roots",
484
- });
485
- }
486
- }
487
- }
488
- });
489
- /**
490
- * Deterministic frontend writer-admission shell (M8). Runs after
491
- * frontend-design-review-pi: freezes the writer baseline, derives the concrete
492
- * writeSet, folds the design-policy result + design verdict into admission,
493
- * and writes contracts/frontend-writer-admission-result.json
494
- * (schemaId frontend-writer-admission-shell-v1).
495
- */
496
- export const dagFrontendWriterAdmissionShellSchema = z
497
- .object({
498
- schemaVersion: z.literal(1),
499
- /** Design verdict source (frontend-design-review-pi); pass is enforced.
500
- * Omitted in small topology, which has no design review node. */
501
- designReviewFromNodeId: dagFrontendNodeIdSchema.optional(),
502
- /** Frozen command labels the verification bundle will execute. */
503
- frozenCommandLabels: z.array(z.string()).default([]),
504
- allowedMockStrategies: z
505
- .array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
506
- .min(1),
507
- /** Writer-preceding lint commands; baseline is materialized here (R5). */
508
- lintCommands: z.array(z.string().min(1)).optional(),
509
- lintEvidence: dagShellVerifyEvidenceSchema.optional(),
510
- })
511
- .superRefine((admission, context) => {
512
- if ((admission.lintCommands?.length ?? 0) > 0 && !admission.lintEvidence) {
513
- context.addIssue({
514
- code: z.ZodIssueCode.custom,
515
- path: ["lintEvidence"],
516
- message: "lintEvidence is required when lintCommands are present",
517
- });
518
- }
519
- });
520
- /** Deterministic frontend closeout shell (M8). Renders the closeout from
521
- * committed facts only (review context + typed review terminal + trace). */
522
- export const dagFrontendCloseoutSchema = z.object({
523
- schemaVersion: z.literal(1),
524
- reviewFromNodeId: dagFrontendNodeIdSchema,
525
- });
526
348
  export const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
527
349
  export const dagVersionSchema = z
528
350
  .union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)])
@@ -570,14 +392,7 @@ export const dagWriteGuardPolicySchema = z.enum(["full", "tools-only", "filesyst
570
392
  */
571
393
  export const dagWriterOutcomePolicySchema = z
572
394
  .object({
573
- /**
574
- * Protocol face: `implementation-outcome-v1` keeps the legacy
575
- * IMPLEMENTATION_OUTCOME first-line protocol (backend/README/module
576
- * writers + frontend-repair-pi); `frontend-facts-v1` derives the status
577
- * from mechanical facts (write-tool events / run delta / write guard /
578
- * requirement coverage / focused-check) and never reads the first line.
579
- */
580
- type: z.enum(["implementation-outcome-v1", "frontend-facts-v1"]),
395
+ type: z.literal("implementation-outcome-v1"),
581
396
  /** Generation writers may forbid already-satisfied/no-diff completion. */
582
397
  requireChangedFiles: z.boolean().optional(),
583
398
  })
@@ -710,13 +525,10 @@ export const dagShellConfigSchema = z.object({
710
525
  .optional(),
711
526
  requirementCoverageGate: dagRequirementCoverageGateSchema.optional(),
712
527
  jsonArtifactGate: dagJsonArtifactGateSchema.optional(),
713
- frontendDesignPolicy: dagFrontendDesignPolicySchema.optional(),
714
- frontendWriterAdmission: dagFrontendWriterAdmissionShellSchema.optional(),
715
528
  frontendPrewriteGate: dagFrontendPrewriteGateSchema.optional(),
529
+ frontendLintBaseline: dagFrontendLintBaselineSchema.optional(),
716
530
  frontendVerificationBundle: dagFrontendVerificationBundleSchema.optional(),
717
531
  frontendReviewContext: dagFrontendReviewContextSchema.optional(),
718
- frontendContractImport: dagFrontendContractImportSchema.optional(),
719
- frontendCloseout: dagFrontendCloseoutSchema.optional(),
720
532
  frontendTestCaseChecklist: dagFrontendTestCaseChecklistSchema.optional(),
721
533
  frontendBrowserToolPreflight: dagFrontendBrowserToolPreflightSchema.optional(),
722
534
  frontendTestStandardScenarios: dagFrontendTestStandardScenariosSchema.optional(),
@@ -955,19 +767,6 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
955
767
  role: dagRoleSchema.optional(),
956
768
  skills: z.array(z.string()).optional(),
957
769
  toolProfile: dagToolProfileSchema.optional(),
958
- /** Bounded read-only discovery budget for nodes that inspect a repository. */
959
- readBudget: z
960
- .object({
961
- maxFiles: z.number().int().positive(),
962
- maxBytes: z.number().int().positive(),
963
- maxMs: z.number().int().positive().optional(),
964
- /** Maximum read/grep/find/ls calls. */
965
- maxOperations: z.number().int().positive().optional(),
966
- /** On any read budget exhaustion, return concise completion guidance instead of failing the node. */
967
- onExhaustion: z.enum(["return-guidance"]).optional(),
968
- })
969
- .strict()
970
- .optional(),
971
770
  /**
972
771
  * Explicit Pi extension allowlist (backend implementation DAGs only):
973
772
  * frozen short ids resolved at run time against the Pi settings package
@@ -1215,22 +1014,6 @@ export const dagSpecSchema = z
1215
1014
  taskContractBinding: dagTaskContractBindingSchema.optional(),
1216
1015
  /** Optional unresolved parent-run feedback for a full task rerun. */
1217
1016
  rerunFeedback: dagRerunFeedbackSchema.optional(),
1218
- /** Frontend recovery continuation quota (frozen at generation time from
1219
- * task.json frontendRecovery.maxContinuations). Bounds how many times a
1220
- * failed frontend run may auto-recover via M6 recovery operations before
1221
- * the run stays failed and needs human intervention. */
1222
- frontendRecovery: z
1223
- .object({
1224
- maxContinuations: z
1225
- .number()
1226
- .int()
1227
- .min(1)
1228
- .max(5)
1229
- .optional()
1230
- .default(1),
1231
- })
1232
- .strict()
1233
- .optional(),
1234
1017
  /** Backend-test-only workspace mutation control; absent means Git. */
1235
1018
  backendTestWorkspaceControl: z.enum(["git", "filesystem-only"]).optional(),
1236
1019
  /** Frozen backend-test artifact layout (plan A); absent = historical default testcase/ layout. */
@@ -1356,9 +1139,6 @@ export const FRONTEND_RECOVERY_RESULT_SCHEMA_VERSION = 1;
1356
1139
  * Phase 3a parents converge first, so `rollback-pending` is not produced and
1357
1140
  * was removed by AC-1; the phase starts at `child-staging` and ends at
1358
1141
  * `settled`.
1359
- *
1360
- * @deprecated Read-only compatibility only; the M6 operation controller no
1361
- * longer uses this phase as the authoritative recovery entry point.
1362
1142
  */
1363
1143
  export const frontendRecoveryPhaseSchema = z.enum([
1364
1144
  "child-staging",
@@ -1378,9 +1158,6 @@ export const frontendRecoveryPhaseSchema = z.enum([
1378
1158
  * runner's `(existing?.revision ?? 0) + 1` CAS write (the schema only bounds it
1379
1159
  * non-negative). `childRunId` may be absent through `child-activating` and must
1380
1160
  * be non-empty from `child-running` onward.
1381
- *
1382
- * @deprecated Read-only compatibility only; the runner no longer writes this
1383
- * intent (the M6 operation controller supervises recovery instead).
1384
1161
  */
1385
1162
  export const frontendRecoveryStateSchema = z
1386
1163
  .object({
@@ -1452,10 +1229,6 @@ export const frontendRecoveryEvidenceRefSchema = z
1452
1229
  * stays permissive (failureClass optional, evidenceRefs may be empty) so it
1453
1230
  * accepts the runner's actual phase-3a products, which do not always carry a
1454
1231
  * failure class or evidence reference.
1455
- *
1456
- * @deprecated Read-only compatibility only; the M6 operation controller writes
1457
- * `recovered`/`auto-recovery-blocked` results directly and never uses this
1458
- * schema as the authoritative recovery settlement.
1459
1232
  */
1460
1233
  export const frontendRecoveryResultSchema = z
1461
1234
  .object({
@@ -3,7 +3,7 @@ import { resolveShellCommands } from "../../executors/shell-executor.js";
3
3
  import { pathMatchesPattern } from "../../shared/git-progress.js";
4
4
  import { resolveRepairTaskForGate } from "./repair-artifact.js";
5
5
  import { topoSortToRanks } from "./topo.js";
6
- import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, CONTEXT_OVERFLOW_RETRY_CATEGORY, } from "./retry-policy.js";
6
+ import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "./retry-policy.js";
7
7
  import { isBackendTestCompletenessRetryCandidate } from "./backend-test-writer-completeness.js";
8
8
  import { defaultFrontendTestLayout, frontendTestLayoutFromSpec, } from "./frontend-test-layout.js";
9
9
  const GOVERNANCE_WARNING_TYPES = new Set([
@@ -464,9 +464,7 @@ function validateShellTaskConfig(task, spec, issues) {
464
464
  if (commands.length === 0 &&
465
465
  !shell.jsonArtifactGate &&
466
466
  !shell.backendTestPipeline &&
467
- !shell.frontendDesignPolicy &&
468
- !shell.frontendWriterAdmission &&
469
- !shell.frontendCloseout &&
467
+ !shell.frontendPrewriteGate &&
470
468
  !shell.frontendBrowserToolPreflight &&
471
469
  !shell.frontendTestStandardScenarios &&
472
470
  !shell.frontendTestEnvironmentProbe &&
@@ -539,26 +537,18 @@ function validateShellTaskConfig(task, spec, issues) {
539
537
  }
540
538
  }
541
539
  }
542
- if (shell.frontendDesignPolicy) {
543
- const sourceNodeIds = [shell.frontendDesignPolicy.planFromNodeId];
544
- for (const sourceNodeId of new Set(sourceNodeIds)) {
545
- if (!task.depends_on.includes(sourceNodeId)) {
546
- issues.push({
547
- type: "missing-dependency",
548
- message: `shell task ${task.id} frontendDesignPolicy source ${sourceNodeId} must be a direct dependency`,
549
- });
550
- }
551
- }
552
- }
553
- if (shell.frontendWriterAdmission) {
554
- const sourceNodeIds = shell.frontendWriterAdmission.designReviewFromNodeId
555
- ? [shell.frontendWriterAdmission.designReviewFromNodeId]
556
- : [];
540
+ if (shell.frontendPrewriteGate) {
541
+ const sourceNodeIds = [
542
+ shell.frontendPrewriteGate.planFromNodeId,
543
+ ...shell.frontendPrewriteGate.planFallbackFromNodeIds,
544
+ shell.frontendPrewriteGate.reviewFromNodeId,
545
+ ...shell.frontendPrewriteGate.reviewFallbackFromNodeIds,
546
+ ];
557
547
  for (const sourceNodeId of new Set(sourceNodeIds)) {
558
548
  if (!task.depends_on.includes(sourceNodeId)) {
559
549
  issues.push({
560
550
  type: "missing-dependency",
561
- message: `shell task ${task.id} frontendWriterAdmission source ${sourceNodeId} must be a direct dependency`,
551
+ message: `shell task ${task.id} frontendPrewriteGate source ${sourceNodeId} must be a direct dependency`,
562
552
  });
563
553
  }
564
554
  }
@@ -656,18 +646,12 @@ function validateRetryPolicyTaskConfig(task, issues) {
656
646
  }
657
647
  if (isWriterTransportCandidate) {
658
648
  const categories = new Set(task.retryPolicy.retryCategories);
659
- const allowedWriterTransportCategories = new Set([
660
- WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
661
- CONTEXT_OVERFLOW_RETRY_CATEGORY,
662
- ]);
663
- const allAllowed = [...categories].every((category) => allowedWriterTransportCategories.has(category));
664
649
  if (task.retryPolicy.maxAttempts !== 2 ||
665
- categories.size === 0 ||
666
- !allAllowed ||
650
+ categories.size !== 1 ||
667
651
  !categories.has(WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY)) {
668
652
  issues.push({
669
653
  type: "invalid-retry-policy",
670
- message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only writer-clean-timeout (plus optional context-overflow)`,
654
+ message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only ${WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY}`,
671
655
  });
672
656
  }
673
657
  }