@tea-agent/loop-agent 0.39.0-beta.2 → 0.39.0-beta.4

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 (202) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +187 -92
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/recommendations.js +13 -2
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +16 -5
  8. package/dist/cli/program.js +16 -2
  9. package/dist/cli/update/notifier.js +2 -2
  10. package/dist/cli.js +2 -1
  11. package/dist/commands/dag-rerun.js +55 -1
  12. package/dist/commands/init-model-catalog.js +464 -0
  13. package/dist/commands/init-upgrade.js +265 -97
  14. package/dist/commands/init.js +429 -28
  15. package/dist/commands/inspect-next.js +8 -0
  16. package/dist/executors/dag-pi-executor.js +1691 -29
  17. package/dist/executors/pi-extension-resolver.js +233 -0
  18. package/dist/executors/pi-playwright-cli-tool.js +51 -12
  19. package/dist/executors/pi-sdk-executor.js +180 -33
  20. package/dist/executors/shell-executor.js +1152 -171
  21. package/dist/executors/shell-write-guard.js +14 -0
  22. package/dist/shared/dag-prompt-override.js +28 -0
  23. package/dist/shared/openspec-spec.js +70 -4
  24. package/dist/shared/operator/capabilities.js +32 -0
  25. package/dist/shared/playwright-cli-command-policy.js +15 -0
  26. package/dist/shared/update/console-notifier.js +100 -0
  27. package/dist/{cli → shared}/update/npm-client.js +41 -8
  28. package/dist/{cli → shared}/update/state.js +41 -13
  29. package/dist/task/config-types.js +19 -5
  30. package/dist/task/contract/project.js +3 -0
  31. package/dist/task/contract/schema.js +2 -1
  32. package/dist/task/frontend-project-capability.js +60 -17
  33. package/dist/task/source-prepare/parse-intent.js +24 -4
  34. package/dist/task/source-prepare/prepare.js +36 -0
  35. package/dist/worker/cli.js +31 -1
  36. package/dist/worker/console/chat/pi-console-config.js +81 -10
  37. package/dist/worker/console/chat/routes.js +1 -0
  38. package/dist/worker/console/dag-execution-receipt.js +33 -0
  39. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  40. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  41. package/dist/worker/console/index.js +3 -0
  42. package/dist/worker/console/operation-store.js +226 -6
  43. package/dist/worker/console/operator-actions.js +263 -0
  44. package/dist/worker/console/operator-user-error.js +2 -2
  45. package/dist/worker/console/routes.js +43 -2
  46. package/dist/worker/console/security.js +63 -9
  47. package/dist/worker/console/server.js +151 -6
  48. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-zsNyvGaH.js} +1 -1
  49. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BDjZ5kE1.js} +1 -1
  50. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-BkgaujnN.js} +1 -1
  51. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-CqxOJi0j.js} +1 -1
  52. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-C2JHLdRi.js} +1 -1
  53. package/dist/worker/console/static/assets/channel-BuqaGKAT.js +1 -0
  54. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-DF_YSvOS.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-D3Ort5Vo.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-XfAfZeUC.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-SLBibhf5.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-Dz29Ye_f.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DB2G4H8V.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-CIoGkxdu.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-CG78FGkn.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-ZDHAvXPd.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BKkkC4VA.js} +1 -1
  64. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DNdSHjH9.js +1 -0
  65. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DNdSHjH9.js +1 -0
  66. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-SposUZsO.js} +1 -1
  67. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-BSCtQpD7.js} +1 -1
  68. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-DgFHrw9l.js} +1 -1
  69. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-BVBasYbj.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-6r_4rN_c.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-JmuaB41P.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-xXvAY3sk.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-D2B3lY2o.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-tK54tMEX.js} +1 -1
  75. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CNtELzV-.js} +1 -1
  76. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-C1xKMKcW.js} +1 -1
  77. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CZ7dlPMz.js} +1 -1
  78. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BqGb42Xe.js} +1 -1
  79. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-D_BlXEtt.js} +1 -1
  80. package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-D5zGX6fG.js} +68 -68
  81. package/dist/worker/console/static/assets/index-DBbhESQ_.css +1 -0
  82. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Vce2vmIe.js} +1 -1
  83. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DDwVBAvI.js} +1 -1
  84. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-y_Ew-wv4.js} +1 -1
  85. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-CjXJFp1K.js} +1 -1
  86. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-1itMst1Q.js} +1 -1
  87. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-C-D3ZRUa.js} +5 -5
  88. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BiYywmZc.js} +1 -1
  89. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-l2fydNWA.js} +1 -1
  90. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-Dz0hNbYW.js} +1 -1
  91. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-BujI_mLZ.js} +1 -1
  92. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-pWe84beb.js} +1 -1
  93. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-ByYu2frg.js} +1 -1
  94. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-CoPnilFP.js} +1 -1
  95. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-1Md2LnS0.js} +1 -1
  96. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DqqqAoSH.js} +1 -1
  97. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C4sGkf--.js} +1 -1
  98. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8rmop_9.js +1 -0
  99. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-ByxJrUzW.js} +2 -2
  100. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B0ky-kBM.js +8 -0
  101. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-D8MLgM0O.js} +1 -1
  102. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DnBis-wZ.js} +1 -1
  103. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-uuyYT7UO.js} +1 -1
  104. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-BjgabhwK.js} +1 -1
  105. package/dist/worker/console/static/index.html +2 -2
  106. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  107. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  108. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  109. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  110. package/dist/worker/observe/node-input.js +74 -3
  111. package/dist/worker/observe/node-process.js +377 -0
  112. package/dist/worker/observe/routes.js +36 -3
  113. package/dist/worker/observe/shell-handler-keys.js +34 -0
  114. package/dist/worker/observe/static/api.js +66 -0
  115. package/dist/worker/observe/static/constants.js +8 -1
  116. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  117. package/dist/worker/observe/static/format.js +23 -0
  118. package/dist/worker/observe/static/markdown-render.js +20 -1
  119. package/dist/worker/observe/static/operator-chrome.js +4 -0
  120. package/dist/worker/observe/static/state.js +13 -2
  121. package/dist/worker/observe/static/styles.css +73 -0
  122. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  123. package/dist/worker/observe/static/views/dag.js +8 -1
  124. package/dist/worker/observe/static/views/failures.js +5 -2
  125. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  126. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
  127. package/dist/workflows/dag/contract-output-registry.js +15 -0
  128. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  129. package/dist/workflows/dag/failure-category.js +12 -0
  130. package/dist/workflows/dag/frontend-closeout.js +221 -0
  131. package/dist/workflows/dag/frontend-design-policy.js +366 -0
  132. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  133. package/dist/workflows/dag/frontend-implementation-contract.js +378 -160
  134. package/dist/workflows/dag/frontend-prewrite-gate.js +343 -391
  135. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  136. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  137. package/dist/workflows/dag/frontend-recovery-controller.js +201 -0
  138. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  139. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  140. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  141. package/dist/workflows/dag/frontend-repair.js +7 -424
  142. package/dist/workflows/dag/frontend-review-context.js +195 -8
  143. package/dist/workflows/dag/frontend-risk.js +15 -2
  144. package/dist/workflows/dag/frontend-shadow-dual-write.js +665 -0
  145. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  146. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  147. package/dist/workflows/dag/frontend-shape.js +425 -0
  148. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  149. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  150. package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
  151. package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
  152. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  153. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  154. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  155. package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
  156. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  157. package/dist/workflows/dag/frontend-typed-event-store.js +439 -0
  158. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  159. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  160. package/dist/workflows/dag/frontend-worktree-diff.js +97 -18
  161. package/dist/workflows/dag/frontend-writer-admission.js +260 -0
  162. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  163. package/dist/workflows/dag/init-hybrid.js +572 -500
  164. package/dist/workflows/dag/node-execution.js +307 -70
  165. package/dist/workflows/dag/prompt.js +10 -18
  166. package/dist/workflows/dag/rerun-plan.js +144 -5
  167. package/dist/workflows/dag/rerun-run.js +58 -6
  168. package/dist/workflows/dag/retry-policy.js +228 -4
  169. package/dist/workflows/dag/runner.js +314 -119
  170. package/dist/workflows/dag/scheduler.js +114 -16
  171. package/dist/workflows/dag/structured-output-repair.js +712 -0
  172. package/dist/workflows/dag/types.js +235 -23
  173. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  174. package/dist/workflows/dag/validate.js +60 -10
  175. package/docs/init-surface.manifest.json +5 -0
  176. package/docs/operations/README.md +1 -1
  177. package/docs/templates/agent-dag.schema.json +44 -0
  178. package/docs/templates/frontend-test-dag.json +8 -10
  179. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  180. package/docs/templates/init-managed-agents.md +13 -3
  181. package/harness.json +3 -3
  182. package/package.json +5 -1
  183. package/scripts/next-info.mjs +356 -0
  184. package/scripts/next-publish-gate.mjs +238 -0
  185. package/scripts/release-source-binding.mjs +251 -0
  186. package/skills/codebase-scout/SKILL.md +1 -1
  187. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  188. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  189. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  190. package/skills/frontend-implementation/SKILL.md +4 -4
  191. package/skills/frontend-implementation/references/design-spec.md +11 -7
  192. package/skills/frontend-implementation/references/node-contracts.md +20 -11
  193. package/skills/loop-agent/references/command-reference.md +11 -4
  194. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  195. package/skills/playwright-cli/SKILL.md +22 -0
  196. package/skills/playwright-cli-case-generator/SKILL.md +20 -1
  197. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  198. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  199. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  200. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  201. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  202. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -3,7 +3,8 @@ 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 { frontendPrewriteResultV1Schema, FRONTEND_PREWRITE_RESULT_ARTIFACT_NAME, } from "./frontend-prewrite-gate.js";
6
+ import { boundaryForTargetNode, computeFrontendShapeNodeDigest, evaluateFrontendShapeBoundary, } from "./frontend-shape-facts.js";
7
+ import { FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT, frontendWriterAdmissionResultV1Schema, } from "./frontend-writer-admission.js";
7
8
  import { FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH, FRONTEND_RECOVERY_INTENT_REL_DIR, } from "./frontend-recovery-plan.js";
8
9
  export function isConditionSkippedReason(reason) {
9
10
  return Boolean(reason?.startsWith("condition "));
@@ -111,16 +112,25 @@ async function mapConcurrent(items, limit, fn) {
111
112
  }
112
113
  export const FRONTEND_WRITER_NODE_IDS = [
113
114
  "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);
118
116
  /**
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.
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).
121
131
  */
122
132
  export async function readFrontendPrewriteResult(runDir) {
123
- const artifactPath = path.join(runDir, "contracts", FRONTEND_PREWRITE_RESULT_ARTIFACT_NAME);
133
+ const artifactPath = path.join(runDir, FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT);
124
134
  let raw;
125
135
  try {
126
136
  raw = await readFile(artifactPath, "utf8");
@@ -129,7 +139,7 @@ export async function readFrontendPrewriteResult(runDir) {
129
139
  if (error.code === "ENOENT") {
130
140
  return {
131
141
  ok: false,
132
- reason: `frontend prewrite result artifact missing: ${artifactPath}`,
142
+ reason: `frontend writer admission artifact missing: ${artifactPath}`,
133
143
  };
134
144
  }
135
145
  throw error;
@@ -142,22 +152,30 @@ export async function readFrontendPrewriteResult(runDir) {
142
152
  catch (error) {
143
153
  return {
144
154
  ok: false,
145
- reason: `frontend prewrite result artifact is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
155
+ reason: `frontend writer admission artifact is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
146
156
  };
147
157
  }
148
- const result = frontendPrewriteResultV1Schema.safeParse(parsed);
158
+ const result = frontendWriterAdmissionResultV1Schema.safeParse(parsed);
149
159
  if (!result.success) {
150
160
  return {
151
161
  ok: false,
152
- reason: "frontend prewrite result artifact failed schema validation",
162
+ reason: "frontend writer admission artifact failed schema validation",
153
163
  };
154
164
  }
155
165
  return { ok: true, result: result.data, artifactHash };
156
166
  }
157
- /** Authorize only accepted / accepted-normalized classifications. */
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. */
158
176
  export function isFrontendWriterAuthorized(result) {
159
177
  return result.classification === "accepted" ||
160
- result.classification === "accepted-normalized"
178
+ result.classification === "admitted"
161
179
  ? "authorized"
162
180
  : "denied";
163
181
  }
@@ -422,6 +440,86 @@ export async function executeDagRanksOnce(input) {
422
440
  }
423
441
  const frontendAdmissionSkippedSet = new Set(frontendAdmissionSettled);
424
442
  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));
425
523
  const blocked = pending.filter((id) => {
426
524
  const task = input.tasksById.get(id);
427
525
  return (dependencyReadiness(task, input.state.nodes, input.tasksById) === "skip");
@@ -436,12 +534,12 @@ export async function executeDagRanksOnce(input) {
436
534
  if (blocked.length > 0) {
437
535
  await input.persistState();
438
536
  }
439
- const pauseGateRunnable = runnableAfterAdmission.filter((id) => {
537
+ const pauseGateRunnable = runnableAfterShape.filter((id) => {
440
538
  const task = input.tasksById.get(id);
441
539
  return isPauseOnHumanDecisionGate(task);
442
540
  });
443
- const regularRunnable = runnableAfterAdmission.filter((id) => !pauseGateRunnable.includes(id));
444
- const rankWriterNodeIds = runnableAfterAdmission.filter((id) => {
541
+ const regularRunnable = runnableAfterShape.filter((id) => !pauseGateRunnable.includes(id));
542
+ const rankWriterNodeIds = runnableAfterShape.filter((id) => {
445
543
  const task = input.tasksById.get(id);
446
544
  return (task?.executor === "pi" &&
447
545
  task.toolProfile === "write" &&