@tea-agent/loop-agent 0.40.0-next.2 → 0.40.0-next.3
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/AGENTS.md +1 -1
- package/CHANGELOG.md +8 -2
- package/README.md +2 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +12 -26
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +129 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +3 -0
- package/dist/worker/console/server.js +73 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DLMldIE8.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DVOg1dSW.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-715v7joo.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BtMEGtAQ.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-qUxXsdxu.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BYMag72f.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5jt5rtx.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DvZghfS2.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CXutxAmY.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BkANIDWk.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bwy1wwlr.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3pWYVCG.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DRzjzmFD.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C4xXDxXL.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BngIZ90b.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-B40ACoFn.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BE-1ZRAI.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BoYbwVTh.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-B5-KA0fK.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DJpCToT5.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-De8R04Ng.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BU1N8DiA.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BUUUzSYI.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-zLoT1RC4.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-_q4-4N8c.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CTqzoNSL.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMKSYKJM.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Dj6U8Sb7.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BfoxVYky.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/{index-CA0nQEbb.js → index-CW88Fjbw.js} +81 -81
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-T5NfvHGV.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BEhUMkut.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-B2Ld6nNt.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-nV9T85yS.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CIO1zDYh.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-B_ucAqLq.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BIA3VNcZ.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CNzLjNV3.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-PWytzfc_.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CS0wGoIq.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-C3Wsqph8.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DF6FDTYg.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BdQUJm6y.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-Bv9rppGg.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C3Hpvb_B.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CZOWLpno.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CPQRIZx5.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CkwM9BgA.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-wGSoDzYc.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-GNVas2gx.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-C5USnHw7.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +5 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/static/api.js +3 -3
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +203 -0
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/state.js +2 -2
- package/dist/worker/observe/static/styles.css +338 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +531 -78
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +4 -3
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/init-hybrid.js +125 -4
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +42 -11
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/package.json +1 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-BG6TLen_.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dgsvn63q.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DaNfG1BJ.js +0 -8
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface DagNodeRemark {
|
|
2
|
+
failed: boolean;
|
|
3
|
+
text: string;
|
|
4
|
+
isErrorSummary: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function resolveNodeRemark(node: {
|
|
8
|
+
status?: string | null;
|
|
9
|
+
errorPreview?: string | null;
|
|
10
|
+
label?: string | null;
|
|
11
|
+
nodeId: string;
|
|
12
|
+
rank?: string | number | null;
|
|
13
|
+
} | null | undefined): DagNodeRemark;
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
dashboardVisibleFeatures,
|
|
71
71
|
findDagForRun,
|
|
72
72
|
} from "../dag-model.js";
|
|
73
|
+
import { describeDagNodePurpose } from "../dag-node-purpose.js";
|
|
73
74
|
import { layoutDag } from "../dag-layout.js";
|
|
74
75
|
import {
|
|
75
76
|
showView,
|
|
@@ -136,7 +137,7 @@ export function extractFirstErrorSummary(errorPreview) {
|
|
|
136
137
|
return effective ?? "";
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
/** Resolve 备注 cell content for a DAG node (failed → summary; else
|
|
140
|
+
/** Resolve 备注 cell content for a DAG node (failed → summary; else purpose). */
|
|
140
141
|
export function resolveNodeRemark(node) {
|
|
141
142
|
const failed = isNodeFailed(node?.status);
|
|
142
143
|
if (failed) {
|
|
@@ -147,10 +148,10 @@ export function resolveNodeRemark(node) {
|
|
|
147
148
|
isErrorSummary: Boolean(summary),
|
|
148
149
|
};
|
|
149
150
|
}
|
|
150
|
-
const
|
|
151
|
+
const purpose = describeDagNodePurpose(node?.nodeId, node);
|
|
151
152
|
return {
|
|
152
153
|
failed: false,
|
|
153
|
-
text:
|
|
154
|
+
text: purpose || "—",
|
|
154
155
|
isErrorSummary: false,
|
|
155
156
|
};
|
|
156
157
|
}
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
repoBaseName,
|
|
35
35
|
parseMarkdownBlocks,
|
|
36
36
|
markdownLinkHref,
|
|
37
|
+
formatLedgerSummary,
|
|
37
38
|
} from "../format.js";
|
|
38
39
|
import { parseRoute, navigate, buildHashPath } from "../router.js";
|
|
39
40
|
import {
|
|
@@ -94,6 +95,7 @@ export async function renderTaskDetail(featureId, taskId) {
|
|
|
94
95
|
const linksEl = document.getElementById("task-links");
|
|
95
96
|
const evidenceEl = document.getElementById("task-evidence");
|
|
96
97
|
const runsEl = document.getElementById("task-runs");
|
|
98
|
+
const ledgerEl = document.getElementById("task-ledger");
|
|
97
99
|
if (!metaEl || !linksEl || !evidenceEl || !runsEl) return false;
|
|
98
100
|
if (titleEl) titleEl.textContent = displayLabel;
|
|
99
101
|
mountViewState(metaEl, "loading", "加载 Task…");
|
|
@@ -252,9 +254,62 @@ export async function renderTaskDetail(featureId, taskId) {
|
|
|
252
254
|
const active = ["running", "pending", "blocked", "stale"].includes(
|
|
253
255
|
(task.status ?? "").toLowerCase(),
|
|
254
256
|
);
|
|
257
|
+
if (ledgerEl) {
|
|
258
|
+
void renderTaskLedger(ledgerEl, taskId);
|
|
259
|
+
}
|
|
255
260
|
return active;
|
|
256
261
|
}
|
|
257
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Task 详情页 ledger 安全摘要(AC-009):从只读 GET /api/tasks/:id/ledger 拉取,
|
|
265
|
+
* 计数/状态/failure code + 有界排除理由;无 ledger 显示空态,失败安全降级。
|
|
266
|
+
*/
|
|
267
|
+
async function renderTaskLedger(ledgerEl, taskId) {
|
|
268
|
+
clearNode(ledgerEl);
|
|
269
|
+
ledgerEl.appendChild(el("h3", "section-heading", "Source fidelity ledger"));
|
|
270
|
+
mountViewState(ledgerEl, "loading", "加载 ledger…");
|
|
271
|
+
let body = null;
|
|
272
|
+
try {
|
|
273
|
+
body = await fetchJson(`/api/tasks/${encodeURIComponent(taskId)}/ledger`);
|
|
274
|
+
} catch {
|
|
275
|
+
body = null;
|
|
276
|
+
}
|
|
277
|
+
if (!body) {
|
|
278
|
+
mountViewState(ledgerEl, "error", "ledger 只读接口失败(安全降级)。");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (body.present !== true) {
|
|
282
|
+
mountViewState(
|
|
283
|
+
ledgerEl,
|
|
284
|
+
body.error ? "error" : "empty",
|
|
285
|
+
body.error
|
|
286
|
+
? `ledger 不可用(${body.error})。`
|
|
287
|
+
: "无 source fidelity ledger(任务无引用或 ledger 缺失,不伪造计数)。",
|
|
288
|
+
);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const pre = el(
|
|
292
|
+
"pre",
|
|
293
|
+
"cmd",
|
|
294
|
+
formatLedgerSummary({
|
|
295
|
+
present: true,
|
|
296
|
+
fragmentCount: body.fragmentCount,
|
|
297
|
+
canonicalRequirementCount: body.canonicalRequirementCount,
|
|
298
|
+
contextOnlyCount: body.contextOnlyCount,
|
|
299
|
+
duplicateCount: body.duplicateCount,
|
|
300
|
+
unresolvedCount: body.unresolvedCount,
|
|
301
|
+
conflictCount: body.conflictCount,
|
|
302
|
+
gateStatus: body.gateStatus,
|
|
303
|
+
failureCode: body.failureCode,
|
|
304
|
+
exclusions: body.exclusions ?? [],
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
pre.dataset.testid = "task-ledger-summary";
|
|
308
|
+
clearNode(ledgerEl);
|
|
309
|
+
ledgerEl.appendChild(el("h3", "section-heading", "Source fidelity ledger"));
|
|
310
|
+
ledgerEl.appendChild(pre);
|
|
311
|
+
}
|
|
312
|
+
|
|
258
313
|
export async function loadOlderTaskRuns(featureId, taskId, before, runsEl) {
|
|
259
314
|
if (runsEl === undefined) {
|
|
260
315
|
runsEl = before;
|
|
@@ -8,6 +8,7 @@ import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-sto
|
|
|
8
8
|
import { findPackageRoot } from "../../shared/package-metadata.js";
|
|
9
9
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
10
10
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
11
|
+
import { isDagSourceBindingV2 } from "./types.js";
|
|
11
12
|
import { canonicalContractRel, classifyStructuredContractFailure, formatStructuredArtifactPointer, openspecCitationsRel, parseOpenspecCitationsBlock, persistRuntimeSkeleton, } from "./structured-output-repair.js";
|
|
12
13
|
export const frontendNormalizationActionSchema = z.enum([
|
|
13
14
|
"remove-trailing-commas",
|
|
@@ -279,8 +280,33 @@ export const frontendImplementationContractSchema = z
|
|
|
279
280
|
requirementSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
280
281
|
referencePaths: z.array(safePath),
|
|
281
282
|
requirementIds: z.array(id),
|
|
283
|
+
// Source binding v2 (frontend): bound ledger pointer + fragment map.
|
|
284
|
+
ledgerPath: safePath.optional(),
|
|
285
|
+
ledgerSha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
286
|
+
inputDigest: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
287
|
+
requirementToFragments: z
|
|
288
|
+
.record(z.string(), z.array(z.string()))
|
|
289
|
+
.optional(),
|
|
282
290
|
})
|
|
283
|
-
.strict()
|
|
291
|
+
.strict()
|
|
292
|
+
.superRefine((value, ctx) => {
|
|
293
|
+
// v2 all-or-none:任一 ledger 字段出现则四项必须齐全,防止 v1+optional 冒充 v2。
|
|
294
|
+
const anyLedgerField = value.ledgerPath !== undefined ||
|
|
295
|
+
value.ledgerSha256 !== undefined ||
|
|
296
|
+
value.inputDigest !== undefined ||
|
|
297
|
+
value.requirementToFragments !== undefined;
|
|
298
|
+
const allLedgerFields = value.ledgerPath !== undefined &&
|
|
299
|
+
value.ledgerSha256 !== undefined &&
|
|
300
|
+
value.inputDigest !== undefined &&
|
|
301
|
+
value.requirementToFragments !== undefined;
|
|
302
|
+
if (anyLedgerField && !allLedgerFields) {
|
|
303
|
+
ctx.addIssue({
|
|
304
|
+
code: z.ZodIssueCode.custom,
|
|
305
|
+
path: ["sourceBinding"],
|
|
306
|
+
message: "source binding v2 ledger fields (ledgerPath/ledgerSha256/inputDigest/requirementToFragments) must be provided together; partial or optional v1+ledger bindings are rejected",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}),
|
|
284
310
|
riskLevel: z.enum(["small", "standard", "high-risk"]),
|
|
285
311
|
targets: z
|
|
286
312
|
.object({
|
|
@@ -296,6 +322,10 @@ export const frontendImplementationContractSchema = z
|
|
|
296
322
|
implementationTargets: z.array(safePath),
|
|
297
323
|
verificationTargetIds: z.array(z.string().min(1)),
|
|
298
324
|
evidenceGap: gap.optional(),
|
|
325
|
+
// Source fidelity ledger binding (AC-005/AC-006): 每个 requirement
|
|
326
|
+
// 携带来源 fragment 证据,prewrite 门禁校验其真实性与覆盖。
|
|
327
|
+
sourceFragmentIds: z.array(z.string().min(1)).optional(),
|
|
328
|
+
sourceRefs: z.array(safePath).optional(),
|
|
299
329
|
})
|
|
300
330
|
.strict()).min(1),
|
|
301
331
|
uiStates: z.array(z
|
|
@@ -432,6 +462,30 @@ export const frontendImplementationContractSchema = z
|
|
|
432
462
|
message: `unknown verification target ${targetId}`,
|
|
433
463
|
path: ["requirements"],
|
|
434
464
|
});
|
|
465
|
+
// Source fidelity ledger binding (AC-005/AC-006): 绑定携带
|
|
466
|
+
// requirementToFragments(ledger v2)时,每个 requirement 必须携带
|
|
467
|
+
// 有效 sourceFragmentIds/sourceRefs,否则 fail closed(防引用伪造/缺失)。
|
|
468
|
+
const boundFragments = value.sourceBinding.requirementToFragments?.[requirement.id];
|
|
469
|
+
if (value.sourceBinding.ledgerPath &&
|
|
470
|
+
(!requirement.sourceFragmentIds ||
|
|
471
|
+
requirement.sourceFragmentIds.length === 0 ||
|
|
472
|
+
!requirement.sourceRefs ||
|
|
473
|
+
requirement.sourceRefs.length === 0)) {
|
|
474
|
+
ctx.addIssue({
|
|
475
|
+
code: "custom",
|
|
476
|
+
message: `requirement ${requirement.id} must carry sourceFragmentIds/sourceRefs when a source fidelity ledger is bound`,
|
|
477
|
+
path: ["requirements", requirement.id, "sourceFragmentIds"],
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
else if (boundFragments !== undefined &&
|
|
481
|
+
(!requirement.sourceFragmentIds ||
|
|
482
|
+
requirement.sourceFragmentIds.length === 0)) {
|
|
483
|
+
ctx.addIssue({
|
|
484
|
+
code: "custom",
|
|
485
|
+
message: `requirement ${requirement.id} is bound to ledger fragments ${boundFragments.join(", ")} but declares none`,
|
|
486
|
+
path: ["requirements", requirement.id, "sourceFragmentIds"],
|
|
487
|
+
});
|
|
488
|
+
}
|
|
435
489
|
}
|
|
436
490
|
for (const state of value.uiStates) {
|
|
437
491
|
if (state.applicable &&
|
|
@@ -568,6 +622,29 @@ export async function assertFrontendSourceBindingFresh(input) {
|
|
|
568
622
|
throw new Error(`frontend source binding is stale: ${source.path}`);
|
|
569
623
|
}
|
|
570
624
|
}
|
|
625
|
+
// Source binding v2 ledger 新鲜度(AC-FIDELITY-REPAIR-001/002):v2 绑定携带的
|
|
626
|
+
// ledger 文件必须存在且 sha256 与绑定一致,否则 fail closed;v1 绑定无此路径。
|
|
627
|
+
if (isDagSourceBindingV2(input.binding)) {
|
|
628
|
+
const absolute = resolveDagTaskSourcePath({
|
|
629
|
+
workspaceRoot: input.workspaceRoot,
|
|
630
|
+
taskId: input.binding.taskId,
|
|
631
|
+
sourcePath: input.binding.ledgerPath,
|
|
632
|
+
});
|
|
633
|
+
let content;
|
|
634
|
+
try {
|
|
635
|
+
content = await readFile(absolute);
|
|
636
|
+
const info = await stat(absolute);
|
|
637
|
+
if (!info.isFile())
|
|
638
|
+
throw new Error("not a regular file");
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
throw new Error(`frontend source fidelity ledger unavailable: ${input.binding.ledgerPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
642
|
+
}
|
|
643
|
+
const actual = createHash("sha256").update(content).digest("hex");
|
|
644
|
+
if (actual !== input.binding.ledgerSha256) {
|
|
645
|
+
throw new Error(`frontend source fidelity ledger is stale or tampered: ${input.binding.ledgerPath} (bound ${input.binding.ledgerSha256}, observed ${actual})`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
571
648
|
}
|
|
572
649
|
const SECRET_KEY = /(?:password|passwd|secret|token|api[_-]?key|private[_-]?key|credential|authorization)/i;
|
|
573
650
|
const SECRET_VALUE = /(?:-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|ghp|github_pat|xox[baprs]|AKIA)[-_A-Za-z0-9]{12,}\b)/;
|
|
@@ -742,7 +819,7 @@ export function canonicalFrontendContractSourceBinding(binding) {
|
|
|
742
819
|
const requirement = binding.sources.find((source) => source.kind === "requirement");
|
|
743
820
|
if (!requirement)
|
|
744
821
|
throw new Error("frontend implementation contract gate requires a requirement source in DAG sourceBinding");
|
|
745
|
-
|
|
822
|
+
const sourceBinding = {
|
|
746
823
|
taskId: binding.taskId,
|
|
747
824
|
requirementPath: requirement.path,
|
|
748
825
|
requirementSha256: requirement.sha256,
|
|
@@ -752,6 +829,15 @@ export function canonicalFrontendContractSourceBinding(binding) {
|
|
|
752
829
|
.sort(),
|
|
753
830
|
requirementIds: binding.requirementIds.map(canonicalizeRequirementId),
|
|
754
831
|
};
|
|
832
|
+
// Source binding v2(schemaVersion=2 强制字段):透传 ledger 指针与
|
|
833
|
+
// requirement→fragment 映射。v1 绑定不得以 optional 字段冒充 v2。
|
|
834
|
+
if (isDagSourceBindingV2(binding)) {
|
|
835
|
+
sourceBinding.ledgerPath = binding.ledgerPath;
|
|
836
|
+
sourceBinding.ledgerSha256 = binding.ledgerSha256;
|
|
837
|
+
sourceBinding.inputDigest = binding.inputDigest;
|
|
838
|
+
sourceBinding.requirementToFragments = binding.requirementToFragments;
|
|
839
|
+
}
|
|
840
|
+
return sourceBinding;
|
|
755
841
|
}
|
|
756
842
|
function asRecord(value) {
|
|
757
843
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
@@ -47,6 +47,27 @@ function componentSelection(contract) {
|
|
|
47
47
|
})
|
|
48
48
|
.join("\n");
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* 有界 source fidelity ledger 指针(AC-007 / AC-FIDELITY-007):长原文不重新
|
|
52
|
+
* 全量内联到节点提示词;只注入紧凑 canonical contract 与 bound ledger 指针
|
|
53
|
+
* (路径/哈希/inputDigest),ledger coverage 证明长尾未丢失。
|
|
54
|
+
*/
|
|
55
|
+
function sourceFidelityLedger(contract) {
|
|
56
|
+
const binding = contract.sourceBinding;
|
|
57
|
+
if (!binding.ledgerPath)
|
|
58
|
+
return "_(not bound — v1 source binding)_";
|
|
59
|
+
const lines = [
|
|
60
|
+
`- ledger: ${binding.ledgerPath}`,
|
|
61
|
+
`- ledgerSha256: ${binding.ledgerSha256 ?? "(missing)"}`,
|
|
62
|
+
`- inputDigest: ${binding.inputDigest ?? "(missing)"}`,
|
|
63
|
+
"- 语义权威:原始 requirement/acceptance 文档与 requirement-ledger.json;本计划只消费派生摘要与 bound ledger 指针,长原文不内联。",
|
|
64
|
+
];
|
|
65
|
+
const boundCount = Object.keys(binding.requirementToFragments ?? {}).length;
|
|
66
|
+
if (boundCount > 0) {
|
|
67
|
+
lines.push(`- requirement→fragment bindings: ${boundCount}`);
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
50
71
|
export function renderFrontendPlanMarkdown(contract) {
|
|
51
72
|
return [
|
|
52
73
|
"# Frontend Implementation Plan",
|
|
@@ -54,6 +75,9 @@ export function renderFrontendPlanMarkdown(contract) {
|
|
|
54
75
|
"## Requirement Coverage",
|
|
55
76
|
requirementsCoverage(contract),
|
|
56
77
|
"",
|
|
78
|
+
"## Source Fidelity Ledger",
|
|
79
|
+
sourceFidelityLedger(contract),
|
|
80
|
+
"",
|
|
57
81
|
"## Implementation Steps",
|
|
58
82
|
bulletList(contract.implementationSteps) || "_(not specified)_",
|
|
59
83
|
"",
|
|
@@ -2,9 +2,12 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile, stat } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { isDagSourceBindingV2 } from "./types.js";
|
|
5
6
|
import { isOpenspecSpecFilePath } from "../../shared/openspec-spec.js";
|
|
6
7
|
import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-store.js";
|
|
7
8
|
import { countExcludedGovernanceSpecFiles, resolveOpenspecGovernanceRoot, } from "../../task/frontend-project-capability.js";
|
|
9
|
+
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
10
|
+
import { parseLedgerJson, validateRequirementLedger, } from "../../task/source-prepare/ledger.js";
|
|
8
11
|
import { analyzeFrontendImplementationContract, writeFrontendImplementationContractArtifact, writeDeterministicJsonArtifact, assertFrontendSourceBindingFresh, deterministicSha256, sha256Hex, serializeDeterministicJson, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, FrontendContractFailure, frontendNormalizationActionSchema, } from "./frontend-implementation-contract.js";
|
|
9
12
|
import { renderFrontendPlanMarkdown } from "./frontend-plan-render.js";
|
|
10
13
|
import { captureFrontendWorktreeBaseline } from "./frontend-worktree-diff.js";
|
|
@@ -42,6 +45,14 @@ export const frontendPrewriteFailureCodeSchema = z.enum([
|
|
|
42
45
|
"component-choices-missing",
|
|
43
46
|
"contract-too-large",
|
|
44
47
|
"revision-patch-unmergeable",
|
|
48
|
+
// Source fidelity ledger 类目(SOURCE_FIDELITY_*/source-* 稳定错误码)。
|
|
49
|
+
"source-fidelity-ledger-missing",
|
|
50
|
+
"source-fidelity-ledger-stale",
|
|
51
|
+
"source-fidelity-unresolved",
|
|
52
|
+
"source-fidelity-conflict",
|
|
53
|
+
"source-fidelity-high-risk-uncovered",
|
|
54
|
+
"source-fidelity-requirement-not-covered",
|
|
55
|
+
"source-fidelity-source-ref-invalid",
|
|
45
56
|
]);
|
|
46
57
|
export const frontendPrewriteResultV1Schema = z
|
|
47
58
|
.object({
|
|
@@ -442,6 +453,162 @@ async function finalizePrewrite(input, pending) {
|
|
|
442
453
|
openspecPolicy: pending.openspecPolicy,
|
|
443
454
|
};
|
|
444
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Source fidelity ledger 门禁检查(AC-FIDELITY-002/003/006):绑定携带 ledger 指针时,
|
|
458
|
+
* 依次核验 ledger 存在、绑定 hash 一致、inputDigest 新鲜、无 unresolved/conflict/
|
|
459
|
+
* 高风险未覆盖、canonical requirement 全覆盖、来源引用有效且非伪造。任一失败 fail closed。
|
|
460
|
+
*/
|
|
461
|
+
export async function checkFrontendSourceFidelityLedger(input) {
|
|
462
|
+
const { binding } = input;
|
|
463
|
+
// v1 绑定不携带 ledger 指针:非 frontend task / 无可解析引用 → 无 v2 校验路径。
|
|
464
|
+
if (!isDagSourceBindingV2(binding)) {
|
|
465
|
+
return { ok: true, failureCode: null, failureReason: null, ledger: null };
|
|
466
|
+
}
|
|
467
|
+
const absolute = resolveDagTaskSourcePath({
|
|
468
|
+
workspaceRoot: input.workspaceRoot,
|
|
469
|
+
taskId: binding.taskId,
|
|
470
|
+
sourcePath: binding.ledgerPath,
|
|
471
|
+
});
|
|
472
|
+
let raw;
|
|
473
|
+
try {
|
|
474
|
+
raw = await readFile(absolute, "utf8");
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
return {
|
|
478
|
+
ok: false,
|
|
479
|
+
failureCode: "source-fidelity-ledger-missing",
|
|
480
|
+
failureReason: `frontend source fidelity ledger missing: ${binding.ledgerPath} (${error instanceof Error ? error.message : String(error)})`,
|
|
481
|
+
ledger: null,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const actualSha = createHash("sha256").update(raw).digest("hex");
|
|
485
|
+
if (actualSha !== binding.ledgerSha256) {
|
|
486
|
+
return {
|
|
487
|
+
ok: false,
|
|
488
|
+
failureCode: "source-fidelity-ledger-stale",
|
|
489
|
+
failureReason: `frontend source fidelity ledger stale or tampered: ${binding.ledgerPath} (bound ${binding.ledgerSha256}, observed ${actualSha})`,
|
|
490
|
+
ledger: null,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
let ledger;
|
|
494
|
+
try {
|
|
495
|
+
ledger = parseLedgerJson(raw);
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
failureCode: "source-fidelity-ledger-stale",
|
|
501
|
+
failureReason: `frontend source fidelity ledger is not a valid v1 ledger: ${error instanceof Error ? error.message : String(error)}`,
|
|
502
|
+
ledger: null,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
if (ledger.inputDigest !== binding.inputDigest) {
|
|
506
|
+
return {
|
|
507
|
+
ok: false,
|
|
508
|
+
failureCode: "source-fidelity-ledger-stale",
|
|
509
|
+
failureReason: `frontend source fidelity ledger inputDigest mismatch: bound ${binding.inputDigest}, ledger ${ledger.inputDigest}`,
|
|
510
|
+
ledger,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const validation = validateRequirementLedger(ledger);
|
|
514
|
+
const firstError = (code) => validation.find((error) => error.code === code)?.message ?? null;
|
|
515
|
+
if (ledger.stats.unresolvedCount > 0 || validation.some((e) => e.code === "SOURCE_FIDELITY_UNRESOLVED")) {
|
|
516
|
+
return {
|
|
517
|
+
ok: false,
|
|
518
|
+
failureCode: "source-fidelity-unresolved",
|
|
519
|
+
failureReason: firstError("SOURCE_FIDELITY_UNRESOLVED") ??
|
|
520
|
+
`frontend source fidelity ledger has ${ledger.stats.unresolvedCount} unresolved fragment(s)`,
|
|
521
|
+
ledger,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (ledger.stats.conflictCount > 0 || validation.some((e) => e.code === "SOURCE_FIDELITY_CONFLICT")) {
|
|
525
|
+
return {
|
|
526
|
+
ok: false,
|
|
527
|
+
failureCode: "source-fidelity-conflict",
|
|
528
|
+
failureReason: firstError("SOURCE_FIDELITY_CONFLICT") ??
|
|
529
|
+
`frontend source fidelity ledger has ${ledger.stats.conflictCount} conflict(s)`,
|
|
530
|
+
ledger,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
if (ledger.stats.highRiskUncoveredCount > 0 ||
|
|
534
|
+
validation.some((e) => e.code === "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED")) {
|
|
535
|
+
return {
|
|
536
|
+
ok: false,
|
|
537
|
+
failureCode: "source-fidelity-high-risk-uncovered",
|
|
538
|
+
failureReason: firstError("SOURCE_FIDELITY_HIGH_RISK_UNCOVERED") ??
|
|
539
|
+
`frontend source fidelity ledger has ${ledger.stats.highRiskUncoveredCount} high-risk uncovered fragment(s)`,
|
|
540
|
+
ledger,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
// AC-BLOCK-001:canonical requirement 缺少 fragment/sourceRef 证据(ledger 侧
|
|
544
|
+
// REQUIREMENT_NOT_COVERED)与来源引用/片段无效(SOURCE_REF_INVALID /
|
|
545
|
+
// FRAGMENT_INVALID)均为阻断性失败,结构化失败结果不得产生可继续执行的成功。
|
|
546
|
+
const notCoveredError = validation.find((e) => e.code === "SOURCE_FIDELITY_REQUIREMENT_NOT_COVERED");
|
|
547
|
+
if (notCoveredError) {
|
|
548
|
+
return {
|
|
549
|
+
ok: false,
|
|
550
|
+
failureCode: "source-fidelity-requirement-not-covered",
|
|
551
|
+
failureReason: notCoveredError.message,
|
|
552
|
+
ledger,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
const refInvalidError = validation.find((e) => e.code === "SOURCE_FIDELITY_SOURCE_REF_INVALID" ||
|
|
556
|
+
e.code === "SOURCE_FIDELITY_FRAGMENT_INVALID");
|
|
557
|
+
if (refInvalidError) {
|
|
558
|
+
return {
|
|
559
|
+
ok: false,
|
|
560
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
561
|
+
failureReason: refInvalidError.message,
|
|
562
|
+
ledger,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
const ledgerIds = new Set(ledger.canonicalRequirements.map((requirement) => requirement.id));
|
|
566
|
+
const contractIds = new Set(input.contractRequirements.map((r) => r.id));
|
|
567
|
+
const uncoveredRequirements = [...ledgerIds].filter((id) => !contractIds.has(id));
|
|
568
|
+
if (uncoveredRequirements.length > 0) {
|
|
569
|
+
return {
|
|
570
|
+
ok: false,
|
|
571
|
+
failureCode: "source-fidelity-requirement-not-covered",
|
|
572
|
+
failureReason: `frontend contract requirements do not cover ledger canonical requirement ids: ${uncoveredRequirements.join(", ")}`,
|
|
573
|
+
ledger,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const fragmentById = new Map(ledger.fragments.map((fragment) => [fragment.id, fragment]));
|
|
577
|
+
for (const requirement of input.contractRequirements) {
|
|
578
|
+
if (!ledgerIds.has(requirement.id))
|
|
579
|
+
continue;
|
|
580
|
+
const declared = requirement.sourceFragmentIds ?? [];
|
|
581
|
+
if (declared.length === 0) {
|
|
582
|
+
return {
|
|
583
|
+
ok: false,
|
|
584
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
585
|
+
failureReason: `frontend contract requirement ${requirement.id} lacks sourceFragmentIds for the bound ledger`,
|
|
586
|
+
ledger,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const boundFragments = new Set(ledger.canonicalRequirements.find((r) => r.id === requirement.id)
|
|
590
|
+
?.sourceFragmentIds ?? []);
|
|
591
|
+
for (const fragmentId of declared) {
|
|
592
|
+
if (!fragmentById.has(fragmentId)) {
|
|
593
|
+
return {
|
|
594
|
+
ok: false,
|
|
595
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
596
|
+
failureReason: `frontend contract requirement ${requirement.id} references unknown fragment ${fragmentId}`,
|
|
597
|
+
ledger,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
if (!boundFragments.has(fragmentId)) {
|
|
601
|
+
return {
|
|
602
|
+
ok: false,
|
|
603
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
604
|
+
failureReason: `frontend contract requirement ${requirement.id} declares fabricated fragment reference ${fragmentId} (not bound in ledger)`,
|
|
605
|
+
ledger,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return { ok: true, failureCode: null, failureReason: null, ledger };
|
|
611
|
+
}
|
|
445
612
|
export async function runFrontendPrewriteGate(input) {
|
|
446
613
|
const workspaceRoot = input.workspaceRoot ?? input.repoRoot;
|
|
447
614
|
// selectNode throws for a scheduling defect (no FINISHED plan/review). That
|
|
@@ -589,6 +756,33 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
589
756
|
}
|
|
590
757
|
throw error;
|
|
591
758
|
}
|
|
759
|
+
// Source fidelity ledger gate (20260824-frontend-source-fidelity-ledger-v1):
|
|
760
|
+
// 在 verdict/missing-requirement-ids/source-binding-not-fresh 之后插入 ledger
|
|
761
|
+
// 检查。缺失即 fail closed 且 writer 不获授权;所有检查通过才继续物化契约。
|
|
762
|
+
if (input.sourceBinding) {
|
|
763
|
+
const ledgerCheck = await checkFrontendSourceFidelityLedger({
|
|
764
|
+
workspaceRoot: workspaceRoot ?? "",
|
|
765
|
+
binding: input.sourceBinding,
|
|
766
|
+
contractRequirements: analysis.canonical.requirements.map((requirement) => ({
|
|
767
|
+
id: requirement.id,
|
|
768
|
+
sourceFragmentIds: requirement.sourceFragmentIds,
|
|
769
|
+
sourceRefs: requirement.sourceRefs,
|
|
770
|
+
})),
|
|
771
|
+
});
|
|
772
|
+
if (!ledgerCheck.ok && ledgerCheck.failureCode) {
|
|
773
|
+
return finalizePrewrite(input, {
|
|
774
|
+
...basePending,
|
|
775
|
+
verdict,
|
|
776
|
+
candidateJsonSha256: analysis.candidateJsonSha256,
|
|
777
|
+
normalizationActions: [],
|
|
778
|
+
mockStrategy: analysis.canonical.mockApi.strategy,
|
|
779
|
+
classification: "blocked",
|
|
780
|
+
failureReason: ledgerCheck.failureReason ??
|
|
781
|
+
"frontend source fidelity ledger gate failed",
|
|
782
|
+
failureCode: ledgerCheck.failureCode,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}
|
|
592
786
|
// Size guard: a canonical contract above 64KB is a retryable-invalid defect
|
|
593
787
|
// (compress the contract) rather than a writer authorization failure.
|
|
594
788
|
const contractSizeBytes = Buffer.byteLength(serializeDeterministicJson(analysis.canonical), "utf8");
|
|
@@ -947,6 +1141,9 @@ export function formatFrontendPrewriteGateStdout(result) {
|
|
|
947
1141
|
}
|
|
948
1142
|
lines.push(`Prewrite result: ${result.prewriteResult.path}`);
|
|
949
1143
|
lines.push(`Openspec policy: ${result.openspecPolicy}`);
|
|
1144
|
+
if (result.failureCode?.startsWith("source-fidelity-")) {
|
|
1145
|
+
lines.push(`Source fidelity ledger: blocked (${result.failureCode})`);
|
|
1146
|
+
}
|
|
950
1147
|
if (result.failureReason)
|
|
951
1148
|
lines.push(`Failure reason: ${result.failureReason}`);
|
|
952
1149
|
if (result.openspecReadPaths.length > 0) {
|