@tea-agent/loop-agent 0.41.0 → 0.42.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -14
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -0
- package/dist/cli/program.js +23 -1
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/executors/dag-pi-executor.js +39 -12
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/{worker → infrastructure}/console/operation-store.js +28 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/operator/capabilities.js +14 -0
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +2 -1
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +2 -2
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +1 -1
- package/dist/worker/console/chat/session-store.js +1 -1
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -3
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +104 -3
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.js +57 -0
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +6 -3
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DwVJxUxz.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DqfCcXdu.js → arc-D6PvaVd-.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-qMVtggRv.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-ECrV38dh.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DBaaLqQv.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BvfNHkAz.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5StiIbc.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-HabGiJkS.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BiTg127i.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CZsXBPuR.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D-r1MxLK.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DQxb9plr.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMndGvCT.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-ByL4BgOJ.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVe1b1gL.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BAyGzN8j.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-C5yVYU7E.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-x-MwsEm9.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CT5Tle5F.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BARERhlS.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BwfUz0VM.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Mk4r2WvJ.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-KUrxuBs0.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CjZOceZn.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-rnwUCiEI.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DM4j23FM.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DD5HdDEs.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CIAGSF47.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DbwFbVEL.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
- package/dist/worker/console/static/assets/{index-Bih9w9h9.js → index-xwu9GxEc.js} +80 -80
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-B-Pb0T66.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B9Unk-YX.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C2F3JkPt.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Bl8-Xcoz.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
- package/dist/worker/console/static/assets/{linear-yK4uZmC5.js → linear-CfUiDDB3.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Bpa6PmuE.js → mermaid.core-BQe6fpqj.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BfGRoCSe.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Bz6_HtST.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-i0Umbsby.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DaVQBobe.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DMTGnSq7.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-2ao_DaSq.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-2SG1lTB9.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BQYOLybR.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C8g3yjuI.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-q_F8nAQG.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-8LrWpH2s.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BQoY3qzl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BDu1UjSp.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-C48M7Y0a.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YU6FTl_F.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/workspace-context.js +28 -2
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1037 -126
- package/dist/worker/observe/static/views/dag-inspector.js +1215 -58
- package/dist/worker/observe/static/views/session-timeline.js +75 -10
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +146 -0
- package/dist/workflows/dag/node-execution.js +112 -27
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +29 -4
- package/dist/workflows/dag/rerun-plan.js +555 -1
- package/dist/workflows/dag/rerun-run.js +601 -15
- package/dist/workflows/dag/runner.js +20 -1
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +183 -7
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/templates/agent-dag.schema.json +32 -0
- package/docs/templates/product-line/task.yaml +1 -1
- package/package.json +3 -1
- package/skills/loop-agent/references/command-reference.md +6 -0
- 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/stateDiagram-v2-UEYNNEHI-DrxGBDJu.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BhpDxzAf.js +0 -8
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
5
|
+
import { effectiveNodeContextV1Schema, } from "../../shared/context-transfer.js";
|
|
6
|
+
import { buildArtifactBindingGraph, } from "../../workflows/dag/artifact-bindings.js";
|
|
7
|
+
import { dagSpecSchema } from "../../workflows/dag/types.js";
|
|
8
|
+
import { isSafeObservabilityIdentifier } from "../observability/event-store.js";
|
|
9
|
+
import { resolveDagRunArtifact, resolveDagRunJson, } from "./dag-run-artifacts.js";
|
|
10
|
+
export const NODE_TRANSPARENCY_SCHEMA_VERSION = 1;
|
|
11
|
+
export const DAG_ARTIFACT_PREVIEW_MAX_BYTES = 64 * 1024;
|
|
12
|
+
function failureForResolution(reason) {
|
|
13
|
+
if (reason === "invalid-id" || reason === "unsafe-path") {
|
|
14
|
+
return {
|
|
15
|
+
status: 400,
|
|
16
|
+
body: { error: "Invalid or unsafe DAG run identifier", reason },
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (reason === "ambiguous") {
|
|
20
|
+
return {
|
|
21
|
+
status: 409,
|
|
22
|
+
body: { error: "Ambiguous DAG run lifecycle", reason },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return { status: 404, body: { error: "DAG run not found", reason } };
|
|
26
|
+
}
|
|
27
|
+
async function readJsonObject(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(await readFile(filePath, "utf8"));
|
|
30
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
31
|
+
? parsed
|
|
32
|
+
: null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function loadRunFacts(repoRoot, dagRunId) {
|
|
39
|
+
const resolved = resolveDagRunJson(repoRoot, dagRunId);
|
|
40
|
+
if (!resolved.ok) {
|
|
41
|
+
return { ok: false, failure: failureForResolution(resolved.reason) };
|
|
42
|
+
}
|
|
43
|
+
const raw = await readJsonObject(resolved.result.absolutePath);
|
|
44
|
+
if (!raw) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
failure: { status: 422, body: { error: "run.json is unreadable" } },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const parsed = dagSpecSchema.safeParse(raw);
|
|
51
|
+
if (!parsed.success) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
failure: {
|
|
55
|
+
status: 422,
|
|
56
|
+
body: { error: "run.json does not satisfy the frozen DAG schema" },
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const state = await readJsonObject(`${resolved.result.runDir}/state.json`);
|
|
61
|
+
return {
|
|
62
|
+
ok: true,
|
|
63
|
+
facts: {
|
|
64
|
+
lifecycle: resolved.result.lifecycle,
|
|
65
|
+
runDir: resolved.result.runDir,
|
|
66
|
+
spec: parsed.data,
|
|
67
|
+
state,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function stateNode(state, nodeId) {
|
|
72
|
+
const nodes = state?.nodes;
|
|
73
|
+
if (!nodes || typeof nodes !== "object" || Array.isArray(nodes))
|
|
74
|
+
return null;
|
|
75
|
+
const node = nodes[nodeId];
|
|
76
|
+
return node && typeof node === "object" && !Array.isArray(node)
|
|
77
|
+
? node
|
|
78
|
+
: null;
|
|
79
|
+
}
|
|
80
|
+
function normalizedAttempt(rawAttempt, node) {
|
|
81
|
+
if (Number.isSafeInteger(rawAttempt) && (rawAttempt ?? 0) > 0)
|
|
82
|
+
return rawAttempt;
|
|
83
|
+
const current = node?.currentAttempt;
|
|
84
|
+
if (typeof current === "number" &&
|
|
85
|
+
Number.isSafeInteger(current) &&
|
|
86
|
+
current > 0) {
|
|
87
|
+
return current;
|
|
88
|
+
}
|
|
89
|
+
const attempts = node?.attempts;
|
|
90
|
+
return Array.isArray(attempts) && attempts.length > 0 ? attempts.length : 1;
|
|
91
|
+
}
|
|
92
|
+
function nodeStatus(node) {
|
|
93
|
+
return typeof node?.status === "string" ? node.status : null;
|
|
94
|
+
}
|
|
95
|
+
function wasDispatched(node) {
|
|
96
|
+
const status = nodeStatus(node)?.toUpperCase();
|
|
97
|
+
return Boolean((status && !["PENDING", "READY", "BLOCKED"].includes(status)) ||
|
|
98
|
+
typeof node?.startedAt === "string" ||
|
|
99
|
+
typeof node?.currentAttempt === "number");
|
|
100
|
+
}
|
|
101
|
+
export async function loadDagNodeContextSummary(repoRoot, dagRunId, nodeId, attempt) {
|
|
102
|
+
if (!isSafeObservabilityIdentifier(nodeId)) {
|
|
103
|
+
return {
|
|
104
|
+
status: 400,
|
|
105
|
+
body: { error: "Invalid node identifier", reason: "invalid-id" },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const loaded = await loadRunFacts(repoRoot, dagRunId);
|
|
109
|
+
if (!loaded.ok)
|
|
110
|
+
return loaded.failure;
|
|
111
|
+
const task = loaded.facts.spec.tasks.find((candidate) => candidate.id === nodeId);
|
|
112
|
+
if (!task) {
|
|
113
|
+
return {
|
|
114
|
+
status: 404,
|
|
115
|
+
body: { error: "DAG node not found", reason: "not-found" },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const runtimeNode = stateNode(loaded.facts.state, nodeId);
|
|
119
|
+
const selectedAttempt = normalizedAttempt(attempt, runtimeNode);
|
|
120
|
+
const receiptSegments = [
|
|
121
|
+
nodeId,
|
|
122
|
+
"context-receipts",
|
|
123
|
+
`attempt-${selectedAttempt}.json`,
|
|
124
|
+
];
|
|
125
|
+
const receiptArtifact = receiptSegments.join("/");
|
|
126
|
+
const receiptResolved = resolveDagRunArtifact(repoRoot, dagRunId, receiptSegments);
|
|
127
|
+
let effectiveContext;
|
|
128
|
+
if (!receiptResolved.ok) {
|
|
129
|
+
effectiveContext = {
|
|
130
|
+
status: "not-captured",
|
|
131
|
+
reasonCode: "CONTEXT_RECEIPT_MISSING",
|
|
132
|
+
receiptArtifact,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const rawReceipt = await readJsonObject(receiptResolved.result.absolutePath);
|
|
137
|
+
const parsedReceipt = effectiveNodeContextV1Schema.safeParse(rawReceipt);
|
|
138
|
+
if (!parsedReceipt.success ||
|
|
139
|
+
parsedReceipt.data.identity.runId !== dagRunId ||
|
|
140
|
+
parsedReceipt.data.identity.nodeId !== nodeId ||
|
|
141
|
+
parsedReceipt.data.identity.attempt !== selectedAttempt) {
|
|
142
|
+
effectiveContext = {
|
|
143
|
+
status: "invalid",
|
|
144
|
+
reasonCode: "CONTEXT_COMPONENT_UNAVAILABLE",
|
|
145
|
+
receiptArtifact,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const receipt = parsedReceipt.data;
|
|
150
|
+
const componentKinds = {};
|
|
151
|
+
const inclusionCounts = {};
|
|
152
|
+
for (const component of receipt.orderedComponents) {
|
|
153
|
+
componentKinds[component.kind] =
|
|
154
|
+
(componentKinds[component.kind] ?? 0) + 1;
|
|
155
|
+
inclusionCounts[component.inclusion] =
|
|
156
|
+
(inclusionCounts[component.inclusion] ?? 0) + 1;
|
|
157
|
+
}
|
|
158
|
+
effectiveContext = {
|
|
159
|
+
status: "captured",
|
|
160
|
+
contextDigest: receipt.contextDigest,
|
|
161
|
+
capturedAt: receipt.identity.capturedAt,
|
|
162
|
+
componentCount: receipt.orderedComponents.length,
|
|
163
|
+
componentKinds,
|
|
164
|
+
inclusionCounts,
|
|
165
|
+
truncationCount: receipt.promptAssembly.truncations.length,
|
|
166
|
+
runtime: {
|
|
167
|
+
executor: receipt.runtime.executor,
|
|
168
|
+
model: receipt.runtime.resolvedModel ?? receipt.runtime.modelHint ?? null,
|
|
169
|
+
skillCount: receipt.runtime.skills.length,
|
|
170
|
+
extensionCount: receipt.runtime.extensions.length,
|
|
171
|
+
toolCount: receipt.runtime.activeTools.length,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
status: 200,
|
|
178
|
+
body: {
|
|
179
|
+
schemaVersion: NODE_TRANSPARENCY_SCHEMA_VERSION,
|
|
180
|
+
dagRunId,
|
|
181
|
+
nodeId,
|
|
182
|
+
attempt: selectedAttempt,
|
|
183
|
+
declared: { status: "available", source: "frozen-run-spec" },
|
|
184
|
+
dispatched: {
|
|
185
|
+
status: wasDispatched(runtimeNode) ? "dispatched" : "not-dispatched",
|
|
186
|
+
nodeStatus: nodeStatus(runtimeNode),
|
|
187
|
+
},
|
|
188
|
+
effectiveContext,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async function loadNodeRecord(repoRoot, dagRunId, nodeId) {
|
|
193
|
+
const resolved = resolveDagRunArtifact(repoRoot, dagRunId, [
|
|
194
|
+
`${nodeId}.json`,
|
|
195
|
+
]);
|
|
196
|
+
return resolved.ok ? readJsonObject(resolved.result.absolutePath) : null;
|
|
197
|
+
}
|
|
198
|
+
function declaredArtifactFacts(record) {
|
|
199
|
+
const facts = new Map();
|
|
200
|
+
for (const value of Array.isArray(record?.declaredArtifacts)
|
|
201
|
+
? record.declaredArtifacts
|
|
202
|
+
: []) {
|
|
203
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
204
|
+
continue;
|
|
205
|
+
const fact = value;
|
|
206
|
+
if (typeof fact.artifactId === "string")
|
|
207
|
+
facts.set(fact.artifactId, fact);
|
|
208
|
+
}
|
|
209
|
+
return facts;
|
|
210
|
+
}
|
|
211
|
+
function projectOrigin(record, runtimeNode) {
|
|
212
|
+
const raw = record?.origin ?? runtimeNode?.origin;
|
|
213
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
214
|
+
const origin = raw;
|
|
215
|
+
if (origin.kind === "imported") {
|
|
216
|
+
return {
|
|
217
|
+
kind: "imported",
|
|
218
|
+
...(typeof origin.parentRunId === "string"
|
|
219
|
+
? { parentRunId: origin.parentRunId }
|
|
220
|
+
: {}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (origin.kind === "executed")
|
|
224
|
+
return { kind: "executed" };
|
|
225
|
+
}
|
|
226
|
+
if (!record && !wasDispatched(runtimeNode))
|
|
227
|
+
return { kind: "pending" };
|
|
228
|
+
return { kind: "unknown" };
|
|
229
|
+
}
|
|
230
|
+
export async function loadDagNodeArtifactInventory(repoRoot, dagRunId, nodeId) {
|
|
231
|
+
if (!isSafeObservabilityIdentifier(nodeId)) {
|
|
232
|
+
return {
|
|
233
|
+
status: 400,
|
|
234
|
+
body: { error: "Invalid node identifier", reason: "invalid-id" },
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const loaded = await loadRunFacts(repoRoot, dagRunId);
|
|
238
|
+
if (!loaded.ok)
|
|
239
|
+
return loaded.failure;
|
|
240
|
+
const task = loaded.facts.spec.tasks.find((candidate) => candidate.id === nodeId);
|
|
241
|
+
if (!task) {
|
|
242
|
+
return {
|
|
243
|
+
status: 404,
|
|
244
|
+
body: { error: "DAG node not found", reason: "not-found" },
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const graph = buildArtifactBindingGraph(loaded.facts.spec);
|
|
248
|
+
const byId = new Map(graph.artifacts.map((artifact) => [artifact.artifactId, artifact]));
|
|
249
|
+
const record = await loadNodeRecord(repoRoot, dagRunId, nodeId);
|
|
250
|
+
const materialized = declaredArtifactFacts(record);
|
|
251
|
+
const produces = graph.artifacts
|
|
252
|
+
.filter((artifact) => artifact.producerNodeId === nodeId)
|
|
253
|
+
.map((artifact) => {
|
|
254
|
+
const fact = materialized.get(artifact.artifactId);
|
|
255
|
+
const status = fact?.status === "materialized" ||
|
|
256
|
+
fact?.status === "missing" ||
|
|
257
|
+
fact?.status === "mismatch"
|
|
258
|
+
? fact.status
|
|
259
|
+
: "not-recorded";
|
|
260
|
+
const runtimeEligible = status === "materialized" && fact?.revisionEligible === true;
|
|
261
|
+
const factOrigin = fact?.origin && typeof fact.origin === "object" && !Array.isArray(fact.origin)
|
|
262
|
+
? fact.origin
|
|
263
|
+
: undefined;
|
|
264
|
+
return {
|
|
265
|
+
artifactId: artifact.artifactId,
|
|
266
|
+
kind: artifact.kind,
|
|
267
|
+
path: artifact.path,
|
|
268
|
+
mediaType: artifact.mediaType,
|
|
269
|
+
...(artifact.schemaId ? { schemaId: artifact.schemaId } : {}),
|
|
270
|
+
revisionPolicy: artifact.revisionPolicy,
|
|
271
|
+
directConsumerNodeIds: artifact.directConsumerNodeIds,
|
|
272
|
+
materialization: {
|
|
273
|
+
status,
|
|
274
|
+
...(typeof fact?.sha256 === "string" ? { sha256: fact.sha256 } : {}),
|
|
275
|
+
...(typeof fact?.bytes === "number" ? { bytes: fact.bytes } : {}),
|
|
276
|
+
...(typeof fact?.reasonCode === "string"
|
|
277
|
+
? { reasonCode: fact.reasonCode }
|
|
278
|
+
: {}),
|
|
279
|
+
},
|
|
280
|
+
revision: {
|
|
281
|
+
eligible: runtimeEligible,
|
|
282
|
+
...(!runtimeEligible
|
|
283
|
+
? {
|
|
284
|
+
reasonCode: artifact.revisionPolicy !== "replace-file"
|
|
285
|
+
? "ARTIFACT_NOT_REVISION_ELIGIBLE"
|
|
286
|
+
: typeof fact?.reasonCode === "string"
|
|
287
|
+
? fact.reasonCode
|
|
288
|
+
: "ARTIFACT_NOT_MATERIALIZED",
|
|
289
|
+
}
|
|
290
|
+
: {}),
|
|
291
|
+
},
|
|
292
|
+
...(factOrigin && ["executed", "imported", "overridden"].includes(String(factOrigin.kind))
|
|
293
|
+
? {
|
|
294
|
+
origin: {
|
|
295
|
+
kind: factOrigin.kind,
|
|
296
|
+
...(typeof factOrigin.parentRunId === "string"
|
|
297
|
+
? { parentRunId: factOrigin.parentRunId }
|
|
298
|
+
: {}),
|
|
299
|
+
...(typeof factOrigin.revisionId === "string"
|
|
300
|
+
? { revisionId: factOrigin.revisionId }
|
|
301
|
+
: {}),
|
|
302
|
+
...(typeof factOrigin.supersedes === "string"
|
|
303
|
+
? { supersedes: factOrigin.supersedes }
|
|
304
|
+
: {}),
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
: {}),
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
const consumes = (task.consumesArtifacts ?? []).flatMap((binding) => {
|
|
311
|
+
const artifact = byId.get(binding.artifactId);
|
|
312
|
+
return artifact
|
|
313
|
+
? [
|
|
314
|
+
{
|
|
315
|
+
artifactId: binding.artifactId,
|
|
316
|
+
required: binding.required,
|
|
317
|
+
producerNodeId: artifact.producerNodeId,
|
|
318
|
+
path: artifact.path,
|
|
319
|
+
mediaType: artifact.mediaType,
|
|
320
|
+
...(artifact.schemaId ? { schemaId: artifact.schemaId } : {}),
|
|
321
|
+
},
|
|
322
|
+
]
|
|
323
|
+
: [];
|
|
324
|
+
});
|
|
325
|
+
return {
|
|
326
|
+
status: 200,
|
|
327
|
+
body: {
|
|
328
|
+
schemaVersion: NODE_TRANSPARENCY_SCHEMA_VERSION,
|
|
329
|
+
dagRunId,
|
|
330
|
+
nodeId,
|
|
331
|
+
binding: {
|
|
332
|
+
available: graph.available,
|
|
333
|
+
...(graph.reasonCode ? { reasonCode: graph.reasonCode } : {}),
|
|
334
|
+
artifactGraphSha256: graph.artifactGraphSha256,
|
|
335
|
+
},
|
|
336
|
+
origin: projectOrigin(record, stateNode(loaded.facts.state, nodeId)),
|
|
337
|
+
produces,
|
|
338
|
+
consumes,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function isTextPreviewMediaType(mediaType) {
|
|
343
|
+
const normalized = mediaType.toLowerCase().split(";", 1)[0]?.trim() ?? "";
|
|
344
|
+
return normalized.startsWith("text/") || [
|
|
345
|
+
"application/json",
|
|
346
|
+
"application/ld+json",
|
|
347
|
+
"application/x-ndjson",
|
|
348
|
+
"application/xml",
|
|
349
|
+
"application/yaml",
|
|
350
|
+
"application/x-yaml",
|
|
351
|
+
"application/toml",
|
|
352
|
+
"application/javascript",
|
|
353
|
+
"application/sql",
|
|
354
|
+
].includes(normalized) || normalized.endsWith("+json") || normalized.endsWith("+xml");
|
|
355
|
+
}
|
|
356
|
+
function isInsideDirectory(root, candidate) {
|
|
357
|
+
const normalizedRoot = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
|
|
358
|
+
return candidate.startsWith(normalizedRoot);
|
|
359
|
+
}
|
|
360
|
+
export async function loadDagNodeArtifactPreview(repoRoot, dagRunId, nodeId, artifactId) {
|
|
361
|
+
if (!isSafeObservabilityIdentifier(nodeId) || !isSafeObservabilityIdentifier(artifactId)) {
|
|
362
|
+
return {
|
|
363
|
+
status: 400,
|
|
364
|
+
body: { error: "Invalid node or artifact identifier", reason: "invalid-id" },
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
const loaded = await loadRunFacts(repoRoot, dagRunId);
|
|
368
|
+
if (!loaded.ok)
|
|
369
|
+
return loaded.failure;
|
|
370
|
+
const task = loaded.facts.spec.tasks.find((candidate) => candidate.id === nodeId);
|
|
371
|
+
if (!task) {
|
|
372
|
+
return {
|
|
373
|
+
status: 404,
|
|
374
|
+
body: { error: "DAG node not found", reason: "not-found" },
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const graph = buildArtifactBindingGraph(loaded.facts.spec);
|
|
378
|
+
if (!graph.available) {
|
|
379
|
+
return {
|
|
380
|
+
status: 422,
|
|
381
|
+
body: {
|
|
382
|
+
error: "Artifact preview requires frozen artifact bindings",
|
|
383
|
+
reason: graph.reasonCode ?? "ARTIFACT_BINDING_UNAVAILABLE",
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const artifact = graph.artifacts.find((candidate) => candidate.artifactId === artifactId);
|
|
388
|
+
const isVisibleToNode = artifact && (artifact.producerNodeId === nodeId ||
|
|
389
|
+
(task.consumesArtifacts ?? []).some((binding) => binding.artifactId === artifactId));
|
|
390
|
+
if (!artifact || !isVisibleToNode) {
|
|
391
|
+
return {
|
|
392
|
+
status: 404,
|
|
393
|
+
body: { error: "Artifact is not bound to this node", reason: "not-found" },
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const producerRecord = await loadNodeRecord(repoRoot, dagRunId, artifact.producerNodeId);
|
|
397
|
+
const fact = declaredArtifactFacts(producerRecord).get(artifactId);
|
|
398
|
+
if (fact?.status !== "materialized" || typeof fact.sha256 !== "string") {
|
|
399
|
+
return {
|
|
400
|
+
status: 404,
|
|
401
|
+
body: {
|
|
402
|
+
error: "Artifact file has not been materialized",
|
|
403
|
+
reason: typeof fact?.reasonCode === "string"
|
|
404
|
+
? fact.reasonCode
|
|
405
|
+
: "ARTIFACT_NOT_MATERIALIZED",
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
const resolved = resolveDagRunArtifact(repoRoot, dagRunId, artifact.path.split("/").filter(Boolean));
|
|
410
|
+
if (!resolved.ok)
|
|
411
|
+
return failureForResolution(resolved.reason);
|
|
412
|
+
try {
|
|
413
|
+
const [runRoot, artifactRealPath, fileStat] = await Promise.all([
|
|
414
|
+
realpath(resolved.result.runDir),
|
|
415
|
+
realpath(resolved.result.absolutePath),
|
|
416
|
+
lstat(resolved.result.absolutePath),
|
|
417
|
+
]);
|
|
418
|
+
if (!isInsideDirectory(runRoot, artifactRealPath) ||
|
|
419
|
+
!fileStat.isFile() ||
|
|
420
|
+
fileStat.isSymbolicLink() ||
|
|
421
|
+
fileStat.nlink !== 1) {
|
|
422
|
+
return {
|
|
423
|
+
status: 422,
|
|
424
|
+
body: { error: "Artifact is not a safe regular file", reason: "unsafe-path" },
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
const bytes = await readFile(artifactRealPath);
|
|
428
|
+
const actualSha256 = createHash("sha256").update(bytes).digest("hex");
|
|
429
|
+
if (actualSha256 !== fact.sha256 ||
|
|
430
|
+
(typeof fact.bytes === "number" && fact.bytes !== bytes.byteLength)) {
|
|
431
|
+
return {
|
|
432
|
+
status: 422,
|
|
433
|
+
body: {
|
|
434
|
+
error: "Artifact bytes no longer match the recorded materialization",
|
|
435
|
+
reason: "ARTIFACT_HASH_MISMATCH",
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
if (!isTextPreviewMediaType(artifact.mediaType)) {
|
|
440
|
+
return {
|
|
441
|
+
status: 200,
|
|
442
|
+
body: {
|
|
443
|
+
schemaVersion: NODE_TRANSPARENCY_SCHEMA_VERSION,
|
|
444
|
+
dagRunId,
|
|
445
|
+
nodeId,
|
|
446
|
+
producerNodeId: artifact.producerNodeId,
|
|
447
|
+
artifactId,
|
|
448
|
+
path: artifact.path,
|
|
449
|
+
mediaType: artifact.mediaType,
|
|
450
|
+
kind: artifact.kind,
|
|
451
|
+
sha256: actualSha256,
|
|
452
|
+
sourceBytes: bytes.byteLength,
|
|
453
|
+
maxBytes: DAG_ARTIFACT_PREVIEW_MAX_BYTES,
|
|
454
|
+
previewKind: "unsupported",
|
|
455
|
+
content: null,
|
|
456
|
+
truncated: false,
|
|
457
|
+
redacted: false,
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const raw = bytes.toString("utf8");
|
|
462
|
+
const redacted = redactSecrets(raw);
|
|
463
|
+
const content = truncateUtf8Preview(redacted, DAG_ARTIFACT_PREVIEW_MAX_BYTES);
|
|
464
|
+
return {
|
|
465
|
+
status: 200,
|
|
466
|
+
body: {
|
|
467
|
+
schemaVersion: NODE_TRANSPARENCY_SCHEMA_VERSION,
|
|
468
|
+
dagRunId,
|
|
469
|
+
nodeId,
|
|
470
|
+
producerNodeId: artifact.producerNodeId,
|
|
471
|
+
artifactId,
|
|
472
|
+
path: artifact.path,
|
|
473
|
+
mediaType: artifact.mediaType,
|
|
474
|
+
kind: artifact.kind,
|
|
475
|
+
sha256: actualSha256,
|
|
476
|
+
sourceBytes: bytes.byteLength,
|
|
477
|
+
maxBytes: DAG_ARTIFACT_PREVIEW_MAX_BYTES,
|
|
478
|
+
previewKind: "text",
|
|
479
|
+
content,
|
|
480
|
+
truncated: content !== redacted,
|
|
481
|
+
redacted: redacted !== raw,
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
return {
|
|
487
|
+
status: 404,
|
|
488
|
+
body: { error: "Artifact file is unavailable", reason: "not-found" },
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function stringField(value, key) {
|
|
493
|
+
return typeof value?.[key] === "string" ? value[key] : undefined;
|
|
494
|
+
}
|
|
495
|
+
export async function loadDagRunLineage(repoRoot, dagRunId) {
|
|
496
|
+
const loaded = await loadRunFacts(repoRoot, dagRunId);
|
|
497
|
+
if (!loaded.ok)
|
|
498
|
+
return loaded.failure;
|
|
499
|
+
const continuationRaw = loaded.facts.state?.continuation;
|
|
500
|
+
const continuation = continuationRaw &&
|
|
501
|
+
typeof continuationRaw === "object" &&
|
|
502
|
+
!Array.isArray(continuationRaw)
|
|
503
|
+
? continuationRaw
|
|
504
|
+
: null;
|
|
505
|
+
const importResolved = resolveDagRunArtifact(repoRoot, dagRunId, [
|
|
506
|
+
".runtime",
|
|
507
|
+
"import-manifest.json",
|
|
508
|
+
]);
|
|
509
|
+
const importManifest = importResolved.ok
|
|
510
|
+
? await readJsonObject(importResolved.result.absolutePath)
|
|
511
|
+
: null;
|
|
512
|
+
const importedNodeIds = (Array.isArray(importManifest?.nodes) ? importManifest.nodes : [])
|
|
513
|
+
.flatMap((entry) => entry &&
|
|
514
|
+
typeof entry === "object" &&
|
|
515
|
+
!Array.isArray(entry) &&
|
|
516
|
+
typeof entry.nodeId === "string"
|
|
517
|
+
? [entry.nodeId]
|
|
518
|
+
: [])
|
|
519
|
+
.sort();
|
|
520
|
+
const rerunResolved = resolveDagRunArtifact(repoRoot, dagRunId, [
|
|
521
|
+
".runtime",
|
|
522
|
+
"rerun-plan.json",
|
|
523
|
+
]);
|
|
524
|
+
const rerunPlan = rerunResolved.ok
|
|
525
|
+
? await readJsonObject(rerunResolved.result.absolutePath)
|
|
526
|
+
: null;
|
|
527
|
+
const promptOverriddenNodeIds = (Array.isArray(rerunPlan?.promptOverrides) ? rerunPlan.promptOverrides : [])
|
|
528
|
+
.flatMap((entry) => entry &&
|
|
529
|
+
typeof entry === "object" &&
|
|
530
|
+
!Array.isArray(entry) &&
|
|
531
|
+
typeof entry.nodeId === "string"
|
|
532
|
+
? [entry.nodeId]
|
|
533
|
+
: [])
|
|
534
|
+
.sort();
|
|
535
|
+
const artifactOverriddenNodeIds = (Array.isArray(importManifest?.overrides) ? importManifest.overrides : []).flatMap((entry) => entry &&
|
|
536
|
+
typeof entry === "object" &&
|
|
537
|
+
!Array.isArray(entry) &&
|
|
538
|
+
typeof entry.producerNodeId === "string"
|
|
539
|
+
? [entry.producerNodeId]
|
|
540
|
+
: []);
|
|
541
|
+
const overriddenNodeIds = [
|
|
542
|
+
...new Set([...promptOverriddenNodeIds, ...artifactOverriddenNodeIds]),
|
|
543
|
+
].sort();
|
|
544
|
+
const parentRunId = stringField(continuation, "parentRunId");
|
|
545
|
+
return {
|
|
546
|
+
status: 200,
|
|
547
|
+
body: {
|
|
548
|
+
schemaVersion: NODE_TRANSPARENCY_SCHEMA_VERSION,
|
|
549
|
+
dagRunId,
|
|
550
|
+
kind: parentRunId ? "continuation" : "root",
|
|
551
|
+
...(parentRunId ? { parentRunId } : {}),
|
|
552
|
+
...(stringField(rerunPlan, "trigger")
|
|
553
|
+
? { trigger: stringField(rerunPlan, "trigger") }
|
|
554
|
+
: {}),
|
|
555
|
+
...(stringField(continuation, "operatorSelectedNodeId")
|
|
556
|
+
? {
|
|
557
|
+
operatorSelectedNodeId: stringField(continuation, "operatorSelectedNodeId"),
|
|
558
|
+
}
|
|
559
|
+
: {}),
|
|
560
|
+
...(stringField(continuation, "effectiveFromNodeId")
|
|
561
|
+
? {
|
|
562
|
+
effectiveFromNodeId: stringField(continuation, "effectiveFromNodeId"),
|
|
563
|
+
}
|
|
564
|
+
: {}),
|
|
565
|
+
...(stringField(rerunPlan, "planHash")
|
|
566
|
+
? { planHash: stringField(rerunPlan, "planHash") }
|
|
567
|
+
: {}),
|
|
568
|
+
importedNodeIds,
|
|
569
|
+
overriddenNodeIds,
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
}
|
|
@@ -14,13 +14,14 @@ import { dagSourceBindingSchema } from "../../workflows/dag/types.js";
|
|
|
14
14
|
import { getTaskPaths } from "../../task/runtime.js";
|
|
15
15
|
import { classifyLedgerGate, computeLedgerInputDigest, LedgerParseError, parseLedgerJson, recoverLedgerInputContract, summarizeLedger, } from "../../task/source-prepare/ledger.js";
|
|
16
16
|
import { getTaskPoolRoot } from "../pool/run-store.js";
|
|
17
|
-
import { resolveDagRunArtifact
|
|
17
|
+
import { resolveDagRunArtifact } from "./dag-run-artifacts.js";
|
|
18
18
|
import { frontendPrewriteResultV1Schema } from "../../workflows/dag/frontend-prewrite-gate.js";
|
|
19
19
|
import { isAllowedArtifactTextPath, resolveArtifactPath, resolveRepoFilePreview, toRepoRelativeArtifactPath, } from "./paths.js";
|
|
20
20
|
import { extractSpecEvidence, extractSpecReadContent, } from "./spec-evidence.js";
|
|
21
21
|
import { buildDagNodeInput } from "./node-input.js";
|
|
22
22
|
import { loadDagNodeExecutionOutput } from "./dag-node-execution-output.js";
|
|
23
23
|
import { loadDagNodeProcess } from "./node-process.js";
|
|
24
|
+
import { loadDagNodeArtifactInventory, loadDagNodeArtifactPreview, loadDagNodeContextSummary, loadDagRunLineage, } from "./node-transparency.js";
|
|
24
25
|
import { buildObserveHealthV1 } from "./health.js";
|
|
25
26
|
import { buildNightJobDetail, buildNightJobsDoctor, buildNightJobsList, buildNightJobsMorning, } from "./night-jobs.js";
|
|
26
27
|
const ARTIFACT_PREVIEW_MAX_BYTES = 64 * 1024;
|
|
@@ -109,6 +110,21 @@ export const ROUTES = [
|
|
|
109
110
|
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/input$/,
|
|
110
111
|
handler: handleDagNodeInput,
|
|
111
112
|
},
|
|
113
|
+
{
|
|
114
|
+
method: "GET",
|
|
115
|
+
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/context$/,
|
|
116
|
+
handler: handleDagNodeContext,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
method: "GET",
|
|
120
|
+
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/artifacts\/preview$/,
|
|
121
|
+
handler: handleDagNodeArtifactPreview,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
method: "GET",
|
|
125
|
+
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/artifacts$/,
|
|
126
|
+
handler: handleDagNodeArtifacts,
|
|
127
|
+
},
|
|
112
128
|
{
|
|
113
129
|
method: "GET",
|
|
114
130
|
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/execution-output$/,
|
|
@@ -139,6 +155,11 @@ export const ROUTES = [
|
|
|
139
155
|
pattern: /^\/api\/dag-runs\/([^/]+)\/prewrite-result$/,
|
|
140
156
|
handler: handleDagRunPrewriteResult,
|
|
141
157
|
},
|
|
158
|
+
{
|
|
159
|
+
method: "GET",
|
|
160
|
+
pattern: /^\/api\/dag-runs\/([^/]+)\/lineage$/,
|
|
161
|
+
handler: handleDagRunLineage,
|
|
162
|
+
},
|
|
142
163
|
{
|
|
143
164
|
method: "GET",
|
|
144
165
|
pattern: /^\/api\/dag-runs\/([^/]+)$/,
|
|
@@ -754,6 +775,35 @@ async function handleDagNodeInput(_req, res, match, ctx) {
|
|
|
754
775
|
const result = await buildDagNodeInput(ctx.repoRoot, dagRunId, nodeId);
|
|
755
776
|
sendJson(res, result.status, result.body);
|
|
756
777
|
}
|
|
778
|
+
async function handleDagNodeContext(_req, res, match, ctx) {
|
|
779
|
+
const attemptRaw = match.query.get("attempt");
|
|
780
|
+
let attempt;
|
|
781
|
+
if (attemptRaw != null) {
|
|
782
|
+
attempt = Number(attemptRaw);
|
|
783
|
+
if (!Number.isSafeInteger(attempt) || attempt < 1) {
|
|
784
|
+
sendJson(res, 400, {
|
|
785
|
+
error: "attempt must be a positive integer",
|
|
786
|
+
reason: "invalid-attempt",
|
|
787
|
+
});
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const result = await loadDagNodeContextSummary(ctx.repoRoot, match.params.id, match.params.sub, attempt);
|
|
792
|
+
sendJson(res, result.status, result.body);
|
|
793
|
+
}
|
|
794
|
+
async function handleDagNodeArtifacts(_req, res, match, ctx) {
|
|
795
|
+
const result = await loadDagNodeArtifactInventory(ctx.repoRoot, match.params.id, match.params.sub);
|
|
796
|
+
sendJson(res, result.status, result.body);
|
|
797
|
+
}
|
|
798
|
+
async function handleDagNodeArtifactPreview(_req, res, match, ctx) {
|
|
799
|
+
const artifactId = match.query.get("artifactId") ?? "";
|
|
800
|
+
const result = await loadDagNodeArtifactPreview(ctx.repoRoot, match.params.id, match.params.sub, artifactId);
|
|
801
|
+
sendJson(res, result.status, result.body);
|
|
802
|
+
}
|
|
803
|
+
async function handleDagRunLineage(_req, res, match, ctx) {
|
|
804
|
+
const result = await loadDagRunLineage(ctx.repoRoot, match.params.id);
|
|
805
|
+
sendJson(res, result.status, result.body);
|
|
806
|
+
}
|
|
757
807
|
/**
|
|
758
808
|
* Read-only execution body by attempt-N / pass-N key only.
|
|
759
809
|
* Clients must never submit artifact paths; server maps key → run-dir segments.
|