@tea-agent/loop-agent 0.33.6 → 0.33.7-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 +23 -23
- package/dist/application/task-lifecycle/advance.js +4 -254
- package/dist/application/task-lifecycle/gates.js +0 -50
- package/dist/application/task-lifecycle/observe.js +2 -11
- package/dist/commands/init-upgrade.js +1 -32
- package/dist/commands/init.js +3 -94
- package/dist/executors/shell-executor.js +91 -4
- package/dist/executors/shell-write-guard.js +8 -26
- package/dist/shared/operator/capabilities.js +42 -72
- package/dist/task/source-prepare/index.js +0 -2
- package/dist/task/source-prepare/parse-intent.js +10 -58
- package/dist/task/source-prepare/prepare.js +16 -180
- package/dist/task/source-prepare/reference-integrity.js +2 -18
- package/dist/worker/console/app-data.js +0 -2
- package/dist/worker/console/chat/chat-event-store.js +25 -190
- package/dist/worker/console/chat/instruction-skills.js +217 -0
- package/dist/worker/console/chat/pi-console-config.js +32 -250
- package/dist/worker/console/chat/pi-runtime.js +71 -625
- package/dist/worker/console/chat/resource-loader.js +4 -5
- package/dist/worker/console/chat/routes.js +146 -324
- package/dist/worker/console/chat/runtime-context.js +12 -48
- package/dist/worker/console/chat/runtime-selection.js +0 -59
- package/dist/worker/console/chat/shortcuts.js +0 -1
- package/dist/worker/console/chat/tool-adapter.js +3 -9
- package/dist/worker/console/chat/tools.js +1 -5
- package/dist/worker/console/operator-actions.js +68 -559
- package/dist/worker/console/server.js +15 -8
- package/dist/worker/console/static/assets/index-CnUXAqxG.css +1 -0
- package/dist/worker/console/static/assets/index-CteJFFL2.js +29 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +8 -45
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -9
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +0 -16
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +184 -210
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +5 -49
- package/dist/worker/console/static-src/operator-chat/useComposer.js +0 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +74 -225
- package/dist/worker/delivery/final-verification.js +5 -13
- package/dist/worker/delivery/package.js +19 -31
- package/dist/worker/delivery/verification-bundle.js +4 -6
- package/dist/worker/observe/static/operator-chrome.css +2 -5
- package/dist/worker/observe/static/operator-chrome.js +1 -6
- package/dist/worker/observe/static/styles.css +9 -39
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +462 -33
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +25 -1
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +269 -82
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/frontend-worktree-diff.js +27 -12
- package/dist/workflows/dag/init-hybrid.js +46 -34
- package/dist/workflows/dag/types.js +7 -0
- package/dist/workflows/dag/workspace-checkpoint.js +27 -8
- package/docs/templates/backend-test-dag.json +32 -29
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -3
- package/skills/loop-agent/references/source-and-plan-practice.md +0 -13
- package/skills/loop-agent/references/task-workflow.md +0 -4
- package/dist/shared/resilient-git.js +0 -133
- package/dist/task/source-prepare/artifact-meta.js +0 -137
- package/dist/task/source-prepare/semantic-intake.js +0 -404
- package/dist/worker/console/dag-execution-receipt.js +0 -380
- package/dist/worker/console/static/assets/index-BUOLppPr.js +0 -28
- package/dist/worker/console/static/assets/index-C1KzazY5.css +0 -1
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +0 -257
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +0 -196
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DAG execution receipt — session-bound, TTL, single-use autonomous DAG
|
|
3
|
-
* execution (2026-08-11 design §5 / §8.3).
|
|
4
|
-
*
|
|
5
|
-
* `prepareDagExecution` issues an execution receipt ONLY when the server-side
|
|
6
|
-
* G2 autonomous-execution assessment passes (bounded writeSet ⊆ allowedPaths,
|
|
7
|
-
* no forbidden overlap, no broad/destructive risk, structured verification
|
|
8
|
-
* present, valid bindings). The receipt carries no human-gate token — the
|
|
9
|
-
* browser Human Gate is not part of this flow.
|
|
10
|
-
*
|
|
11
|
-
* `runDag` consumes the receipt exactly once. Consumption re-reads the staged
|
|
12
|
-
* DAG bytes/hash and re-validates every binding (dag hash, source binding,
|
|
13
|
-
* task contract binding, controller fingerprint, validation result); any
|
|
14
|
-
* expiry, cross-session use, replay, or drift fails closed. Error codes reuse
|
|
15
|
-
* the existing confirmation lifecycle codes (CONFIRMATION_* /
|
|
16
|
-
* HUMAN_CONFIRMATION_REQUIRED / NOT_FOUND) because registry error codes are
|
|
17
|
-
* immutable in this task.
|
|
18
|
-
*/
|
|
19
|
-
import { randomBytes } from "node:crypto";
|
|
20
|
-
import { readFile } from "node:fs/promises";
|
|
21
|
-
import path from "node:path";
|
|
22
|
-
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
23
|
-
import { readJsonIfExists, sha256Utf8, writeSecureJson, } from "./app-data.js";
|
|
24
|
-
import { hashDagBytes, } from "./dag-confirmation.js";
|
|
25
|
-
export const DEFAULT_EXECUTION_RECEIPT_TTL_MS = 30 * 60 * 1000;
|
|
26
|
-
function normalizePathEntry(entry) {
|
|
27
|
-
return entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
28
|
-
}
|
|
29
|
-
function isBroadWriteSetEntry(entry) {
|
|
30
|
-
const normalized = normalizePathEntry(entry);
|
|
31
|
-
if (!normalized || normalized === "." || normalized === "./")
|
|
32
|
-
return true;
|
|
33
|
-
if (normalized === "**")
|
|
34
|
-
return true;
|
|
35
|
-
// Placeholder marker (`REPLACE/WITH`) means the writeSet is not concrete yet.
|
|
36
|
-
return normalized.includes("REPLACE/WITH");
|
|
37
|
-
}
|
|
38
|
-
function isContainedInAllowed(candidate, allowedPaths) {
|
|
39
|
-
const normalized = normalizePathEntry(candidate);
|
|
40
|
-
if (!normalized)
|
|
41
|
-
return false;
|
|
42
|
-
return allowedPaths.some((allowed) => {
|
|
43
|
-
const pattern = normalizePathEntry(allowed);
|
|
44
|
-
if (!pattern || pattern === "." || pattern === "./")
|
|
45
|
-
return false;
|
|
46
|
-
return pathMatchesPattern(normalized, pattern);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
function overlapsForbidden(candidate, forbiddenPaths) {
|
|
50
|
-
const normalized = normalizePathEntry(candidate);
|
|
51
|
-
if (!normalized)
|
|
52
|
-
return true;
|
|
53
|
-
return forbiddenPaths.some((forbidden) => {
|
|
54
|
-
const pattern = normalizePathEntry(forbidden);
|
|
55
|
-
if (!pattern)
|
|
56
|
-
return false;
|
|
57
|
-
return (pathMatchesPattern(normalized, pattern) ||
|
|
58
|
-
pathMatchesPattern(pattern, normalized));
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* G2 deterministic autonomous-execution assessment (design §5.2). Pure and
|
|
63
|
-
* exported so the dispatcher and unit tests share the exact same code path.
|
|
64
|
-
* Every condition must pass — any missing fact fails closed.
|
|
65
|
-
*/
|
|
66
|
-
export function assessAutonomousExecutionEligibility(input) {
|
|
67
|
-
const dangerous = input.dangerousMarkers ?? {};
|
|
68
|
-
if (dangerous.credentials || dangerous.destructive || dangerous.irreversible) {
|
|
69
|
-
return {
|
|
70
|
-
ok: false,
|
|
71
|
-
reason: "DAG involves credentials, destructive operations, or irreversible migration; human authorization required",
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
if (!input.hasStructuredVerification) {
|
|
75
|
-
return {
|
|
76
|
-
ok: false,
|
|
77
|
-
reason: "DAG has no structured shell verification; autonomous execution not permitted",
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
if (input.broadWriteSetRisk) {
|
|
81
|
-
return {
|
|
82
|
-
ok: false,
|
|
83
|
-
reason: "writeSet contains broad/placeholder entries; autonomous execution not permitted",
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
if (input.forbiddenOverlapRisk) {
|
|
87
|
-
return {
|
|
88
|
-
ok: false,
|
|
89
|
-
reason: "writeSet overlaps forbidden paths; autonomous execution not permitted",
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
if (!Array.isArray(input.allowedPaths) || !Array.isArray(input.forbiddenPaths)) {
|
|
93
|
-
return {
|
|
94
|
-
ok: false,
|
|
95
|
-
reason: "task boundary (allowedPaths/forbiddenPaths) unavailable; autonomous execution not permitted",
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
for (const writer of input.writersWriteSets ?? []) {
|
|
99
|
-
if (!Array.isArray(writer.writeSet))
|
|
100
|
-
continue;
|
|
101
|
-
for (const entry of writer.writeSet) {
|
|
102
|
-
if (!isContainedInAllowed(entry, input.allowedPaths)) {
|
|
103
|
-
return {
|
|
104
|
-
ok: false,
|
|
105
|
-
reason: `writer ${writer.nodeId} writeSet entry "${entry}" is outside allowedPaths`,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
if (overlapsForbidden(entry, input.forbiddenPaths)) {
|
|
109
|
-
return {
|
|
110
|
-
ok: false,
|
|
111
|
-
reason: `writer ${writer.nodeId} writeSet entry "${entry}" overlaps forbiddenPaths`,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return { ok: true };
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Extract the deterministic G2 facts from a generated DAG spec (staged bytes).
|
|
120
|
-
* Mirrors the CLI review-packet semantics (generate-task-dag.ts) so the
|
|
121
|
-
* console never trusts client-supplied claims. Missing facts fail closed.
|
|
122
|
-
*/
|
|
123
|
-
export function deriveEligibilityFactsFromDagSpec(input) {
|
|
124
|
-
const spec = (input.dagSpec ?? {});
|
|
125
|
-
const tasks = Array.isArray(spec.tasks) ? spec.tasks : [];
|
|
126
|
-
const defaultWritePolicy = spec.defaults?.writePolicy;
|
|
127
|
-
const resolvePolicy = (task) => task.writePolicy ?? defaultWritePolicy ?? "none";
|
|
128
|
-
const stringList = (value) => Array.isArray(value)
|
|
129
|
-
? value.filter((v) => typeof v === "string")
|
|
130
|
-
: [];
|
|
131
|
-
const writers = tasks
|
|
132
|
-
.filter((task) => resolvePolicy(task) === "exclusive" &&
|
|
133
|
-
stringList(task.writeSet).length > 0)
|
|
134
|
-
.map((task) => ({
|
|
135
|
-
nodeId: task.id ?? "unknown",
|
|
136
|
-
writeSet: stringList(task.writeSet),
|
|
137
|
-
}));
|
|
138
|
-
const allWriteSetEntries = writers.flatMap((writer) => writer.writeSet);
|
|
139
|
-
const broadWriteSetRisk = allWriteSetEntries.some(isBroadWriteSetEntry);
|
|
140
|
-
const forbiddenOverlapRisk = tasks.some((task) => stringList(task.writeSet).some((entry) => [...stringList(task.forbiddenPaths), ...input.forbiddenPaths].some((forbidden) => pathMatchesPattern(entry, forbidden) ||
|
|
141
|
-
pathMatchesPattern(forbidden, entry))));
|
|
142
|
-
const hasStructuredVerification = tasks.some((task) => {
|
|
143
|
-
if (task.executor !== "shell" || !task.shell)
|
|
144
|
-
return false;
|
|
145
|
-
if (/verify|verification/i.test(task.id ?? ""))
|
|
146
|
-
return true;
|
|
147
|
-
const commands = stringList(task.shell.commands);
|
|
148
|
-
return commands.some((command) => /(vitest|npm run (lint|typecheck|test)|check-repo\.sh|loop-agent-standard-verify)/.test(command));
|
|
149
|
-
});
|
|
150
|
-
return {
|
|
151
|
-
writersWriteSets: writers,
|
|
152
|
-
allowedPaths: input.allowedPaths,
|
|
153
|
-
forbiddenPaths: input.forbiddenPaths,
|
|
154
|
-
broadWriteSetRisk,
|
|
155
|
-
forbiddenOverlapRisk,
|
|
156
|
-
hasStructuredVerification,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
function receiptPath(appData, executionId) {
|
|
160
|
-
const dir = appData.dagReceipts ??
|
|
161
|
-
path.join(path.dirname(appData.confirmations), "dag-receipts");
|
|
162
|
-
return path.join(dir, `${executionId}.json`);
|
|
163
|
-
}
|
|
164
|
-
export function newExecutionId() {
|
|
165
|
-
return `exec_${randomBytes(12).toString("hex")}`;
|
|
166
|
-
}
|
|
167
|
-
export class DagExecutionReceiptStore {
|
|
168
|
-
appData;
|
|
169
|
-
constructor(appData) {
|
|
170
|
-
this.appData = appData;
|
|
171
|
-
}
|
|
172
|
-
async get(executionId) {
|
|
173
|
-
const rec = await readJsonIfExists(receiptPath(this.appData, executionId));
|
|
174
|
-
if (!rec)
|
|
175
|
-
return undefined;
|
|
176
|
-
return this.materializeExpiry(rec);
|
|
177
|
-
}
|
|
178
|
-
async materializeExpiry(rec, now = Date.now()) {
|
|
179
|
-
if (rec.state === "prepared" && Date.parse(rec.expiresAt) <= now) {
|
|
180
|
-
const expired = {
|
|
181
|
-
...rec,
|
|
182
|
-
state: "expired",
|
|
183
|
-
staleReason: rec.staleReason ?? "ttl exceeded (30min default)",
|
|
184
|
-
};
|
|
185
|
-
await writeSecureJson(receiptPath(this.appData, rec.executionId), expired);
|
|
186
|
-
return expired;
|
|
187
|
-
}
|
|
188
|
-
return rec;
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Issue a single-use execution receipt. G2 eligibility is re-checked here
|
|
192
|
-
* (fail closed) in addition to the dispatcher's pre-check.
|
|
193
|
-
*/
|
|
194
|
-
async prepare(input) {
|
|
195
|
-
const assessment = assessAutonomousExecutionEligibility(input.eligibility);
|
|
196
|
-
if (!assessment.ok) {
|
|
197
|
-
return {
|
|
198
|
-
ok: false,
|
|
199
|
-
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
200
|
-
reason: assessment.reason,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
if (!input.dagBytes ||
|
|
204
|
-
!input.dagBytesPath ||
|
|
205
|
-
!input.sourceBindingSha256 ||
|
|
206
|
-
!input.controllerFingerprint ||
|
|
207
|
-
!input.validationResultSha256 ||
|
|
208
|
-
!input.taskContractBinding?.taskId) {
|
|
209
|
-
return {
|
|
210
|
-
ok: false,
|
|
211
|
-
code: "INVALID_INPUT",
|
|
212
|
-
reason: "missing server-derived DAG/binding facts for execution receipt",
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
const now = input.now ?? new Date();
|
|
216
|
-
const ttl = input.ttlMs ?? DEFAULT_EXECUTION_RECEIPT_TTL_MS;
|
|
217
|
-
const executionId = newExecutionId();
|
|
218
|
-
const rec = {
|
|
219
|
-
schemaVersion: 1,
|
|
220
|
-
executionId,
|
|
221
|
-
state: "prepared",
|
|
222
|
-
preparedAt: now.toISOString(),
|
|
223
|
-
expiresAt: new Date(now.getTime() + ttl).toISOString(),
|
|
224
|
-
operatorSessionId: input.operatorSessionId,
|
|
225
|
-
taskId: input.taskId,
|
|
226
|
-
dagHandle: input.dagHandle,
|
|
227
|
-
dagSha256: hashDagBytes(input.dagBytes),
|
|
228
|
-
dagBytesPath: input.dagBytesPath,
|
|
229
|
-
sourceBindingSha256: input.sourceBindingSha256,
|
|
230
|
-
taskContractBinding: input.taskContractBinding,
|
|
231
|
-
controllerFingerprint: input.controllerFingerprint,
|
|
232
|
-
validationResultSha256: input.validationResultSha256,
|
|
233
|
-
reviewPacket: input.reviewPacket,
|
|
234
|
-
};
|
|
235
|
-
await writeSecureJson(receiptPath(this.appData, executionId), rec);
|
|
236
|
-
return { ok: true, receipt: rec };
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Atomic single-use consume for runDag. Re-reads the staged DAG bytes and
|
|
240
|
-
* re-validates every binding; any drift/expiry/session/replay fails closed.
|
|
241
|
-
*/
|
|
242
|
-
async consume(input) {
|
|
243
|
-
const rec = await this.get(input.executionId);
|
|
244
|
-
if (!rec) {
|
|
245
|
-
return {
|
|
246
|
-
ok: false,
|
|
247
|
-
code: "NOT_FOUND",
|
|
248
|
-
message: `execution receipt not found: ${input.executionId}`,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
if (rec.state === "expired") {
|
|
252
|
-
return {
|
|
253
|
-
ok: false,
|
|
254
|
-
code: "CONFIRMATION_EXPIRED",
|
|
255
|
-
message: "execution receipt expired",
|
|
256
|
-
receipt: rec,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
if (rec.state === "consumed") {
|
|
260
|
-
return {
|
|
261
|
-
ok: false,
|
|
262
|
-
code: "CONFIRMATION_CONSUMED",
|
|
263
|
-
message: "execution receipt already consumed (single-use)",
|
|
264
|
-
receipt: rec,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
if (rec.state === "stale") {
|
|
268
|
-
return {
|
|
269
|
-
ok: false,
|
|
270
|
-
code: "CONFIRMATION_STALE",
|
|
271
|
-
message: rec.staleReason ?? "execution receipt is stale",
|
|
272
|
-
receipt: rec,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
if (rec.state !== "prepared") {
|
|
276
|
-
return {
|
|
277
|
-
ok: false,
|
|
278
|
-
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
279
|
-
message: `execution receipt is ${rec.state}; re-prepare required`,
|
|
280
|
-
receipt: rec,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
if (rec.operatorSessionId !== input.operatorSessionId) {
|
|
284
|
-
return {
|
|
285
|
-
ok: false,
|
|
286
|
-
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
287
|
-
message: "session mismatch; execution receipt cannot be consumed across operator sessions",
|
|
288
|
-
receipt: rec,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
// Re-read staged DAG bytes and re-hash (fail closed on any drift).
|
|
292
|
-
let dagBytes;
|
|
293
|
-
try {
|
|
294
|
-
dagBytes = await readFile(rec.dagBytesPath, "utf8");
|
|
295
|
-
}
|
|
296
|
-
catch (error) {
|
|
297
|
-
const stale = await this.markStale(rec.executionId, `cannot re-read staged DAG bytes: ${error instanceof Error ? error.message : String(error)}`);
|
|
298
|
-
return {
|
|
299
|
-
ok: false,
|
|
300
|
-
code: "CONFIRMATION_STALE",
|
|
301
|
-
message: "cannot re-read staged DAG bytes",
|
|
302
|
-
receipt: stale,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
const dagSha256 = hashDagBytes(dagBytes);
|
|
306
|
-
if (dagSha256 !== rec.dagSha256) {
|
|
307
|
-
const stale = await this.markStale(rec.executionId, "staged DAG bytes drift (hash mismatch on consume)");
|
|
308
|
-
return {
|
|
309
|
-
ok: false,
|
|
310
|
-
code: "CONFIRMATION_STALE",
|
|
311
|
-
message: "staged DAG bytes drift (hash mismatch on consume)",
|
|
312
|
-
receipt: stale,
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
const drift = detectReceiptBindingDrift(rec, {
|
|
316
|
-
sourceBindingSha256: input.sourceBindingSha256,
|
|
317
|
-
taskContractBinding: input.taskContractBinding,
|
|
318
|
-
controllerFingerprint: input.controllerFingerprint,
|
|
319
|
-
validationResultSha256: input.validationResultSha256,
|
|
320
|
-
});
|
|
321
|
-
if (drift) {
|
|
322
|
-
const stale = await this.markStale(rec.executionId, drift);
|
|
323
|
-
return {
|
|
324
|
-
ok: false,
|
|
325
|
-
code: "CONFIRMATION_STALE",
|
|
326
|
-
message: drift,
|
|
327
|
-
receipt: stale,
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
const now = input.now ?? new Date();
|
|
331
|
-
const consumed = {
|
|
332
|
-
...rec,
|
|
333
|
-
state: "consumed",
|
|
334
|
-
consumedAt: now.toISOString(),
|
|
335
|
-
};
|
|
336
|
-
await writeSecureJson(receiptPath(this.appData, rec.executionId), consumed);
|
|
337
|
-
return { ok: true, receipt: consumed };
|
|
338
|
-
}
|
|
339
|
-
/** Record the operation id that consumed this receipt (post-accept). */
|
|
340
|
-
async markDispatched(executionId, operationId) {
|
|
341
|
-
const rec = await this.get(executionId);
|
|
342
|
-
if (!rec || rec.state !== "consumed")
|
|
343
|
-
return rec;
|
|
344
|
-
const dispatched = {
|
|
345
|
-
...rec,
|
|
346
|
-
consumedOperationId: operationId,
|
|
347
|
-
};
|
|
348
|
-
await writeSecureJson(receiptPath(this.appData, executionId), dispatched);
|
|
349
|
-
return dispatched;
|
|
350
|
-
}
|
|
351
|
-
async markStale(executionId, reason) {
|
|
352
|
-
const rec = await this.get(executionId);
|
|
353
|
-
if (!rec) {
|
|
354
|
-
throw new Error(`execution receipt not found: ${executionId}`);
|
|
355
|
-
}
|
|
356
|
-
const stale = {
|
|
357
|
-
...rec,
|
|
358
|
-
state: "stale",
|
|
359
|
-
staleReason: reason,
|
|
360
|
-
};
|
|
361
|
-
await writeSecureJson(receiptPath(this.appData, executionId), stale);
|
|
362
|
-
return stale;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
function detectReceiptBindingDrift(rec, current) {
|
|
366
|
-
if (current.sourceBindingSha256 !== rec.sourceBindingSha256) {
|
|
367
|
-
return "sourceBindingSha256 binding drift";
|
|
368
|
-
}
|
|
369
|
-
if (current.controllerFingerprint !== rec.controllerFingerprint) {
|
|
370
|
-
return "controllerFingerprint binding drift";
|
|
371
|
-
}
|
|
372
|
-
if (current.validationResultSha256 !== rec.validationResultSha256) {
|
|
373
|
-
return "validationResultSha256 binding drift";
|
|
374
|
-
}
|
|
375
|
-
const a = sha256Utf8(JSON.stringify(rec.taskContractBinding));
|
|
376
|
-
const b = sha256Utf8(JSON.stringify(current.taskContractBinding));
|
|
377
|
-
if (a !== b)
|
|
378
|
-
return "taskContractBinding drift";
|
|
379
|
-
return undefined;
|
|
380
|
-
}
|