@tea-agent/loop-agent 0.41.0-next.3 → 0.41.1-beta.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.
- package/CHANGELOG.md +264 -32
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3784 -231
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +945 -217
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +34 -7
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +15 -9
- package/dist/task/source-prepare/prepare.js +89 -1
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/pi-runtime.js +2 -3
- package/dist/worker/console/chat/routes.js +3 -12
- package/dist/worker/console/chat/semantic-activity.js +11 -12
- package/dist/worker/console/chat/session-stats.js +0 -96
- package/dist/worker/console/chat/turn-process.js +4 -28
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operation-store.js +226 -6
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/pi-plugins.js +1 -3
- package/dist/worker/console/server.js +3 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DwVJxUxz.js → abnfDiagram-N423BO3Z-Bho4we4c.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DqfCcXdu.js → arc-TBDkTeK0.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-qMVtggRv.js → architectureDiagram-T3A2C74G-BF_fZrn2.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-ECrV38dh.js → blockDiagram-VBNYF7ZC-CCnPgUe2.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DBaaLqQv.js → c4Diagram-5PPSVZJV-BCYZJLAT.js} +1 -1
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BvfNHkAz.js → chunk-2GRJ4B5K-D8K6adhU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5StiIbc.js → chunk-2Q5K7J3B-DkT1Q78b.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-HabGiJkS.js → chunk-5RXB4S5H-DuhvpIr7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BiTg127i.js → chunk-5VM5RSS4-BnK6dC49.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CZsXBPuR.js → chunk-6Q2QTUOP-DPz__z7r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D-r1MxLK.js → chunk-GF5L2VYU-0kkppM2j.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DQxb9plr.js → chunk-JWPE2WC7-BB41iK5t.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMndGvCT.js → chunk-KBJHAD2P-CiVrxKHU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-ByL4BgOJ.js → chunk-RYQCIY6F-C2h9Xje1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVe1b1gL.js → chunk-XXDRQBXY-3iOSHdbM.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BAyGzN8j.js → cose-bilkent-JH36ORCC-BN3A7tkk.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-C5yVYU7E.js → cynefin-VYW2F7L2-CTuBy0Or.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-x-MwsEm9.js → cynefinDiagram-MW4NZA55-wb6tUlMN.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CT5Tle5F.js → dagre-VZM6K2ZE-GP1Ivx8B.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BARERhlS.js → diagram-7IWD3JNH-ejrcK6L0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BwfUz0VM.js → diagram-B4RE2ZJO-YFkCCvUU.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Mk4r2WvJ.js → diagram-LBJQPF4R-B4rrvIaL.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-KUrxuBs0.js → diagram-Q27KOJAE-C-YIZcUf.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CjZOceZn.js → diagram-UB23O5K3-BPF5wcL1.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-rnwUCiEI.js → ebnfDiagram-BXEA7PRR-CU5SzXjO.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DM4j23FM.js → erDiagram-JOGREHBK-CpL414DE.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DD5HdDEs.js → flowDiagram-UKHOOZJN-MrhLzNau.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CIAGSF47.js → ganttDiagram-PKOTCBZU-Bg4qFuUo.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DbwFbVEL.js → gitGraphDiagram-DS77QQ5N-ColxSXLR.js} +1 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +1 -0
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +437 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-B-Pb0T66.js → infoDiagram-6WML65LV-Czvtm6gc.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B9Unk-YX.js → ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C2F3JkPt.js → journeyDiagram-NVQOT4AX-DWYZcvph.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Bl8-Xcoz.js → kanban-definition-27J2QSJJ-KTLZRUjx.js} +1 -1
- package/dist/worker/console/static/assets/{linear-yK4uZmC5.js → linear-_Exn7bfl.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Bpa6PmuE.js → mermaid.core-Cxg8kX8V.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BfGRoCSe.js → mindmap-definition-FAOFIHXS-oKrlo2Bt.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Bz6_HtST.js → pegDiagram-VL7TDLO6-XAeteiNd.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-i0Umbsby.js → pieDiagram-7S7Q4E2Y-BTf1K-lp.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DaVQBobe.js → quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DMTGnSq7.js → railroadDiagram-AXF67PYL-BVbKtZhW.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-2ao_DaSq.js → requirementDiagram-LRYGKXZP-C5e4JFcr.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-2SG1lTB9.js → sankeyDiagram-W5VNT64P-Cz30hc29.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BQYOLybR.js → sequenceDiagram-SI44F4Z6-DS33n6d0.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C8g3yjuI.js → sizeCapture-X5ZJPWSS-46w7v-9T.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-q_F8nAQG.js → stateDiagram-OKZ733FA-pbcD5SsZ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-8LrWpH2s.js → swimlanes-SLNWSIFB-DuivYBSj.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BQoY3qzl.js → timeline-definition-Z64GVDOM-Cb9VZVIl.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BDu1UjSp.js → vennDiagram-T6HMQDX7-D2-mfOgR.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-C48M7Y0a.js → wardleyDiagram-T6FBY63Y-DeS2aC76.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YU6FTl_F.js → xychartDiagram-ELKLHX3M-WGsrFjxH.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -9
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +0 -2
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +0 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +3 -28
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +0 -106
- package/dist/worker/console/workspace-context.js +111 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observability/read-model.js +0 -4
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/worker/observe/static/views/dag-inspector.js +7 -2
- package/dist/worker/observe/static/views/session-timeline.js +15 -46
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +932 -162
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +255 -349
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-read-budgets.js +12 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +914 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +427 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +249 -24
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +285 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +685 -494
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +732 -4
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +256 -1
- package/dist/workflows/dag/rerun-plan.js +10 -2
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +214 -104
- package/dist/workflows/dag/runner.js +429 -122
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/types.js +239 -12
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-CL1NRAmS.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-PBGzzjZA.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-PBGzzjZA.js +0 -1
- package/dist/worker/console/static/assets/index-BfleX-qj.css +0 -1
- package/dist/worker/console/static/assets/index-Bih9w9h9.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrxGBDJu.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BhpDxzAf.js +0 -8
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +0 -22
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -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 {
|
|
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
|
-
*
|
|
120
|
-
*
|
|
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,
|
|
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
|
|
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
|
|
155
|
+
reason: `frontend writer admission artifact is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
146
156
|
};
|
|
147
157
|
}
|
|
148
|
-
const result =
|
|
158
|
+
const result = frontendWriterAdmissionResultV1Schema.safeParse(parsed);
|
|
149
159
|
if (!result.success) {
|
|
150
160
|
return {
|
|
151
161
|
ok: false,
|
|
152
|
-
reason: "frontend
|
|
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
|
-
/**
|
|
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 === "
|
|
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 =
|
|
537
|
+
const pauseGateRunnable = runnableAfterShape.filter((id) => {
|
|
440
538
|
const task = input.tasksById.get(id);
|
|
441
539
|
return isPauseOnHumanDecisionGate(task);
|
|
442
540
|
});
|
|
443
|
-
const regularRunnable =
|
|
444
|
-
const rankWriterNodeIds =
|
|
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" &&
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, OPENSPEC_SPEC_EXT_RE, } from "../../shared/openspec-spec.js";
|
|
3
3
|
import { campaignBudgetSchema, } from "../../application/evaluation/budget.js";
|
|
4
4
|
import { assertDagPromptSourceRule } from "./prompt-source.js";
|
|
5
|
-
import
|
|
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";
|
|
6
11
|
import { dagLivenessPolicySchema, } from "./liveness-policy.js";
|
|
7
12
|
import { dagOutputProtocolSchema } from "./output-protocol.js";
|
|
8
13
|
export const dagComplexitySchema = z.enum(["HIGH", "MED", "LOW"]);
|
|
@@ -185,7 +190,16 @@ const dagFrontendNodeIdSchema = z
|
|
|
185
190
|
.regex(/^[a-z][a-z0-9-]*$/, "frontend node id must be kebab-case");
|
|
186
191
|
const openspecCandidatePathSchema = z
|
|
187
192
|
.string()
|
|
188
|
-
.refine((candidate) => !candidate.includes("\\") &&
|
|
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();
|
|
189
203
|
export const dagFrontendPrewriteGateSchema = z.object({
|
|
190
204
|
schemaVersion: z.literal(1),
|
|
191
205
|
planFromNodeId: dagFrontendNodeIdSchema,
|
|
@@ -204,7 +218,24 @@ export const dagFrontendPrewriteGateSchema = z.object({
|
|
|
204
218
|
outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
|
|
205
219
|
requireSourceFreshness: z.literal(true),
|
|
206
220
|
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(),
|
|
207
228
|
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(),
|
|
208
239
|
/**
|
|
209
240
|
* Generation-frozen component/theme specification bucket. Derived from
|
|
210
241
|
* `designEvidence.classified.component ∪ theme ∪ rule.components` at DAG
|
|
@@ -249,14 +280,28 @@ export const dagFrontendPrewriteGateSchema = z.object({
|
|
|
249
280
|
scanStrict: z.array(openspecCandidatePathSchema).default([]),
|
|
250
281
|
})
|
|
251
282
|
.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
|
+
}
|
|
252
304
|
});
|
|
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();
|
|
260
305
|
export const dagFrontendVerificationBundleSchema = z
|
|
261
306
|
.object({
|
|
262
307
|
schemaVersion: z.literal(1),
|
|
@@ -270,6 +315,10 @@ export const dagFrontendVerificationBundleSchema = z
|
|
|
270
315
|
behaviorEvidence: dagShellVerifyEvidenceSchema,
|
|
271
316
|
lintBaselineNodeId: dagFrontendNodeIdSchema.optional(),
|
|
272
317
|
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). */
|
|
273
322
|
mode: z.enum(["initial", "repair"]),
|
|
274
323
|
})
|
|
275
324
|
.superRefine((bundle, context) => {
|
|
@@ -345,6 +394,135 @@ export const dagFrontendReviewContextSchema = z.object({
|
|
|
345
394
|
schemaVersion: z.literal(1),
|
|
346
395
|
requireBaseline: z.literal(true),
|
|
347
396
|
});
|
|
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
|
+
});
|
|
348
526
|
export const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
|
349
527
|
export const dagVersionSchema = z
|
|
350
528
|
.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)])
|
|
@@ -392,7 +570,14 @@ export const dagWriteGuardPolicySchema = z.enum(["full", "tools-only", "filesyst
|
|
|
392
570
|
*/
|
|
393
571
|
export const dagWriterOutcomePolicySchema = z
|
|
394
572
|
.object({
|
|
395
|
-
|
|
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"]),
|
|
396
581
|
/** Generation writers may forbid already-satisfied/no-diff completion. */
|
|
397
582
|
requireChangedFiles: z.boolean().optional(),
|
|
398
583
|
})
|
|
@@ -525,10 +710,13 @@ export const dagShellConfigSchema = z.object({
|
|
|
525
710
|
.optional(),
|
|
526
711
|
requirementCoverageGate: dagRequirementCoverageGateSchema.optional(),
|
|
527
712
|
jsonArtifactGate: dagJsonArtifactGateSchema.optional(),
|
|
713
|
+
frontendDesignPolicy: dagFrontendDesignPolicySchema.optional(),
|
|
714
|
+
frontendWriterAdmission: dagFrontendWriterAdmissionShellSchema.optional(),
|
|
528
715
|
frontendPrewriteGate: dagFrontendPrewriteGateSchema.optional(),
|
|
529
|
-
frontendLintBaseline: dagFrontendLintBaselineSchema.optional(),
|
|
530
716
|
frontendVerificationBundle: dagFrontendVerificationBundleSchema.optional(),
|
|
531
717
|
frontendReviewContext: dagFrontendReviewContextSchema.optional(),
|
|
718
|
+
frontendContractImport: dagFrontendContractImportSchema.optional(),
|
|
719
|
+
frontendCloseout: dagFrontendCloseoutSchema.optional(),
|
|
532
720
|
frontendTestCaseChecklist: dagFrontendTestCaseChecklistSchema.optional(),
|
|
533
721
|
frontendBrowserToolPreflight: dagFrontendBrowserToolPreflightSchema.optional(),
|
|
534
722
|
frontendTestStandardScenarios: dagFrontendTestStandardScenariosSchema.optional(),
|
|
@@ -767,6 +955,19 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
|
|
|
767
955
|
role: dagRoleSchema.optional(),
|
|
768
956
|
skills: z.array(z.string()).optional(),
|
|
769
957
|
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(),
|
|
770
971
|
/**
|
|
771
972
|
* Explicit Pi extension allowlist (backend implementation DAGs only):
|
|
772
973
|
* frozen short ids resolved at run time against the Pi settings package
|
|
@@ -1014,6 +1215,22 @@ export const dagSpecSchema = z
|
|
|
1014
1215
|
taskContractBinding: dagTaskContractBindingSchema.optional(),
|
|
1015
1216
|
/** Optional unresolved parent-run feedback for a full task rerun. */
|
|
1016
1217
|
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(),
|
|
1017
1234
|
/** Backend-test-only workspace mutation control; absent means Git. */
|
|
1018
1235
|
backendTestWorkspaceControl: z.enum(["git", "filesystem-only"]).optional(),
|
|
1019
1236
|
/** Frozen backend-test artifact layout (plan A); absent = historical default testcase/ layout. */
|
|
@@ -1139,6 +1356,9 @@ export const FRONTEND_RECOVERY_RESULT_SCHEMA_VERSION = 1;
|
|
|
1139
1356
|
* Phase 3a parents converge first, so `rollback-pending` is not produced and
|
|
1140
1357
|
* was removed by AC-1; the phase starts at `child-staging` and ends at
|
|
1141
1358
|
* `settled`.
|
|
1359
|
+
*
|
|
1360
|
+
* @deprecated Read-only compatibility only; the M6 operation controller no
|
|
1361
|
+
* longer uses this phase as the authoritative recovery entry point.
|
|
1142
1362
|
*/
|
|
1143
1363
|
export const frontendRecoveryPhaseSchema = z.enum([
|
|
1144
1364
|
"child-staging",
|
|
@@ -1158,6 +1378,9 @@ export const frontendRecoveryPhaseSchema = z.enum([
|
|
|
1158
1378
|
* runner's `(existing?.revision ?? 0) + 1` CAS write (the schema only bounds it
|
|
1159
1379
|
* non-negative). `childRunId` may be absent through `child-activating` and must
|
|
1160
1380
|
* 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).
|
|
1161
1384
|
*/
|
|
1162
1385
|
export const frontendRecoveryStateSchema = z
|
|
1163
1386
|
.object({
|
|
@@ -1229,6 +1452,10 @@ export const frontendRecoveryEvidenceRefSchema = z
|
|
|
1229
1452
|
* stays permissive (failureClass optional, evidenceRefs may be empty) so it
|
|
1230
1453
|
* accepts the runner's actual phase-3a products, which do not always carry a
|
|
1231
1454
|
* 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.
|
|
1232
1459
|
*/
|
|
1233
1460
|
export const frontendRecoveryResultSchema = z
|
|
1234
1461
|
.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, } from "./retry-policy.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";
|
|
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,7 +464,9 @@ function validateShellTaskConfig(task, spec, issues) {
|
|
|
464
464
|
if (commands.length === 0 &&
|
|
465
465
|
!shell.jsonArtifactGate &&
|
|
466
466
|
!shell.backendTestPipeline &&
|
|
467
|
-
!shell.
|
|
467
|
+
!shell.frontendDesignPolicy &&
|
|
468
|
+
!shell.frontendWriterAdmission &&
|
|
469
|
+
!shell.frontendCloseout &&
|
|
468
470
|
!shell.frontendBrowserToolPreflight &&
|
|
469
471
|
!shell.frontendTestStandardScenarios &&
|
|
470
472
|
!shell.frontendTestEnvironmentProbe &&
|
|
@@ -537,18 +539,26 @@ function validateShellTaskConfig(task, spec, issues) {
|
|
|
537
539
|
}
|
|
538
540
|
}
|
|
539
541
|
}
|
|
540
|
-
if (shell.
|
|
541
|
-
const sourceNodeIds = [
|
|
542
|
-
shell.frontendPrewriteGate.planFromNodeId,
|
|
543
|
-
...shell.frontendPrewriteGate.planFallbackFromNodeIds,
|
|
544
|
-
shell.frontendPrewriteGate.reviewFromNodeId,
|
|
545
|
-
...shell.frontendPrewriteGate.reviewFallbackFromNodeIds,
|
|
546
|
-
];
|
|
542
|
+
if (shell.frontendDesignPolicy) {
|
|
543
|
+
const sourceNodeIds = [shell.frontendDesignPolicy.planFromNodeId];
|
|
547
544
|
for (const sourceNodeId of new Set(sourceNodeIds)) {
|
|
548
545
|
if (!task.depends_on.includes(sourceNodeId)) {
|
|
549
546
|
issues.push({
|
|
550
547
|
type: "missing-dependency",
|
|
551
|
-
message: `shell task ${task.id}
|
|
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
|
+
: [];
|
|
557
|
+
for (const sourceNodeId of new Set(sourceNodeIds)) {
|
|
558
|
+
if (!task.depends_on.includes(sourceNodeId)) {
|
|
559
|
+
issues.push({
|
|
560
|
+
type: "missing-dependency",
|
|
561
|
+
message: `shell task ${task.id} frontendWriterAdmission source ${sourceNodeId} must be a direct dependency`,
|
|
552
562
|
});
|
|
553
563
|
}
|
|
554
564
|
}
|
|
@@ -646,12 +656,18 @@ function validateRetryPolicyTaskConfig(task, issues) {
|
|
|
646
656
|
}
|
|
647
657
|
if (isWriterTransportCandidate) {
|
|
648
658
|
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));
|
|
649
664
|
if (task.retryPolicy.maxAttempts !== 2 ||
|
|
650
|
-
categories.size
|
|
665
|
+
categories.size === 0 ||
|
|
666
|
+
!allAllowed ||
|
|
651
667
|
!categories.has(WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY)) {
|
|
652
668
|
issues.push({
|
|
653
669
|
type: "invalid-retry-policy",
|
|
654
|
-
message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only
|
|
670
|
+
message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only writer-clean-timeout (plus optional context-overflow)`,
|
|
655
671
|
});
|
|
656
672
|
}
|
|
657
673
|
}
|