@agent-delivery-harness/cli 0.1.0 → 0.5.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.
Files changed (40) hide show
  1. package/LICENSE +104 -201
  2. package/NOTICE +1 -1
  3. package/package.json +10 -5
  4. package/src/boundary.ts +302 -9
  5. package/src/command-activity.ts +75 -0
  6. package/src/commands/admit.ts +106 -0
  7. package/src/commands/check.ts +13 -2
  8. package/src/commands/emit-review-evidence.ts +38 -0
  9. package/src/commands/emit.ts +419 -0
  10. package/src/commands/gate.ts +127 -18
  11. package/src/commands/maintain.ts +203 -0
  12. package/src/commands/managed.ts +478 -0
  13. package/src/commands/prepare.ts +123 -15
  14. package/src/commands/record.ts +100 -8
  15. package/src/commands/resume.ts +53 -0
  16. package/src/commands/review-context.ts +19 -0
  17. package/src/commands/runs.ts +640 -0
  18. package/src/commands/save-context.ts +51 -0
  19. package/src/commands/submit-evidence.ts +46 -7
  20. package/src/commands/verify.ts +173 -5
  21. package/src/declared-checks.ts +50 -0
  22. package/src/index.ts +40 -4
  23. package/src/main.ts +38 -42
  24. package/src/ordinary-context.ts +99 -0
  25. package/src/provider-rails.ts +24 -0
  26. package/src/record-retention.ts +325 -0
  27. package/src/review-evidence.ts +284 -0
  28. package/src/run-archive-commands.ts +164 -0
  29. package/src/run-archive.ts +127 -0
  30. package/src/run-artifact-commands.ts +163 -0
  31. package/src/run-attachments.ts +112 -0
  32. package/src/run-export.ts +164 -0
  33. package/src/run-live.ts +102 -0
  34. package/src/run-projection.ts +396 -0
  35. package/src/run-server.ts +905 -0
  36. package/src/run-surface.ts +334 -0
  37. package/src/run-view-command.ts +77 -0
  38. package/src/run-view-html.ts +138 -0
  39. package/src/run-view-record.ts +119 -0
  40. package/src/run-view.ts +343 -0
@@ -0,0 +1,119 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { readArchiveFile } from "./run-archive-commands.ts";
4
+ /** Explicit retained-record reads only: no executable configuration or verification. */
5
+ import {
6
+ isSafeRelativePath,
7
+ isInsideResolved,
8
+ parseDeliveryRecord,
9
+ digestCanonical,
10
+ } from "@agent-delivery-harness/kernel";
11
+ import type { RunView, RunViewItem } from "./run-view.ts";
12
+ export async function withRetainedRecord(
13
+ view: RunView,
14
+ root: string,
15
+ relativePath?: string,
16
+ ): Promise<RunView> {
17
+ if (relativePath === undefined || view.historical) return view;
18
+ const fields: { label: string; value: string }[] = [];
19
+ const add = (label: string, value: string) => fields.push({ label, value });
20
+ add("Source", relativePath);
21
+ add(
22
+ "Applicability",
23
+ "Unknown — no current identity comparison or fresh verification is performed",
24
+ );
25
+ add(
26
+ "Original verification time",
27
+ "Unavailable — delivery records do not store a verification timestamp",
28
+ );
29
+ try {
30
+ let status = "path_refused";
31
+ let contents: string | null = null;
32
+ if (isSafeRelativePath(relativePath)) {
33
+ const resolvedRoot = await realpath(root);
34
+ const resolvedFile = await realpath(
35
+ path.join(resolvedRoot, relativePath),
36
+ );
37
+ if (!isInsideResolved(resolvedRoot, resolvedFile))
38
+ status = "outside_run_root";
39
+ else {
40
+ contents = await readArchiveFile(resolvedFile);
41
+ status = "readable";
42
+ }
43
+ }
44
+ const read = { status, contents };
45
+ if (read.status !== "readable" || read.contents === null) {
46
+ add("Status", `Unavailable — ${read.status}`);
47
+ } else {
48
+ const parsed = parseDeliveryRecord(read.contents);
49
+ if (!parsed.ok)
50
+ add("Status", "Corrupt — retained record grammar is invalid");
51
+ else {
52
+ const { integrityDigest, ...unsigned } = parsed.record;
53
+ if (parsed.record.version !== "delivery-record/2")
54
+ add(
55
+ "Status",
56
+ "Legacy record — integrity unavailable; no accepted evidence inferred",
57
+ );
58
+ else if (integrityDigest !== digestCanonical(unsigned))
59
+ add("Status", "Corrupt — retained record digest does not match");
60
+ else {
61
+ add(
62
+ "Status",
63
+ "Recorded for the candidate below — retained self-attested record, not current approval",
64
+ );
65
+ add("Candidate", parsed.record.candidateBinding.treeSha);
66
+ add(
67
+ "Deliverable digest",
68
+ parsed.record.candidateBinding.deliverableDigest,
69
+ );
70
+ for (const claim of parsed.record.claims) {
71
+ add(`Recorded claim · ${claim.obligationId}`, claim.outcome);
72
+ for (const evidence of [
73
+ claim.evidence,
74
+ ...(claim.supportingEvidence ?? []),
75
+ ]) {
76
+ if (evidence?.resolution.kind !== "evidence") continue;
77
+ const manifest = evidence.resolution.portable?.manifest;
78
+ if (typeof manifest !== "object" || manifest === null) continue;
79
+ if (
80
+ digestCanonical(manifest) !== evidence.resolution.manifestDigest
81
+ ) {
82
+ add(
83
+ "Retained manifest",
84
+ "Corrupt — digest does not match; timestamp unavailable",
85
+ );
86
+ continue;
87
+ }
88
+ const at = (manifest as Record<string, unknown>)["recordedAt"];
89
+ add(
90
+ `Provider-reported recording time · ${evidence.resolution.providerId}`,
91
+ typeof at === "string" ? at : "Unavailable",
92
+ );
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ } catch (error) {
99
+ add(
100
+ "Status",
101
+ (error as NodeJS.ErrnoException).code === "ENOENT"
102
+ ? "Unavailable — missing"
103
+ : "Unavailable — bounded retained record read refused",
104
+ );
105
+ }
106
+ const retained: RunViewItem = {
107
+ id: "retained-record",
108
+ label: "Retained delivery record",
109
+ fields,
110
+ };
111
+ return {
112
+ ...view,
113
+ sections: view.sections.map((section) =>
114
+ section.id === "evidence"
115
+ ? { ...section, items: [retained, ...section.items] }
116
+ : section,
117
+ ),
118
+ };
119
+ }
@@ -0,0 +1,343 @@
1
+ /** One operational readout for terminal, JSON and HTML; no admission decisions. */
2
+ import type { RunEvent } from "@agent-delivery-harness/kernel";
3
+ import {
4
+ projectRunProgress,
5
+ projectCosts,
6
+ costLabel,
7
+ } from "./run-projection.ts";
8
+ export interface RunViewItem {
9
+ readonly id: string;
10
+ readonly label: string;
11
+ readonly fields: readonly { label: string; value: string }[];
12
+ readonly artifactId?: string;
13
+ }
14
+ export interface RunViewSection {
15
+ readonly id: string;
16
+ readonly title: string;
17
+ readonly empty: string;
18
+ readonly items: readonly RunViewItem[];
19
+ }
20
+ export interface RunView {
21
+ readonly spec: "run-view/1";
22
+ readonly historical: boolean;
23
+ readonly asOf: string;
24
+ readonly authority: string;
25
+ readonly sections: readonly RunViewSection[];
26
+ }
27
+ const value = (v: unknown): string =>
28
+ v === undefined
29
+ ? "Unreported"
30
+ : typeof v === "string"
31
+ ? v
32
+ : JSON.stringify(v);
33
+ const item = (
34
+ id: string,
35
+ label: string,
36
+ fields: Record<string, unknown>,
37
+ artifactId?: string,
38
+ ): RunViewItem => ({
39
+ id,
40
+ label,
41
+ fields: Object.entries(fields).map(([label, v]) => ({
42
+ label,
43
+ value: value(v),
44
+ })),
45
+ ...(artifactId === undefined ? {} : { artifactId }),
46
+ });
47
+ export function projectRunView(
48
+ events: readonly RunEvent[],
49
+ options: {
50
+ readonly now: string;
51
+ readonly historical?: boolean;
52
+ readonly freshnessWindowMs?: number;
53
+ },
54
+ ): RunView {
55
+ const p = projectRunProgress(events, options.now, options.freshnessWindowMs);
56
+ const historical = options.historical === true;
57
+ const costs = projectCosts(events);
58
+ const runCost = costs.run as Record<string, unknown>;
59
+ const attempts = p.activities.flatMap((a) => a.attempts);
60
+ const current = attempts.filter((a) => !a.superseded);
61
+ const sections: RunViewSection[] = [
62
+ {
63
+ id: "waiting",
64
+ title: "Waiting and required action",
65
+ empty: "No current waiting observation. Unreported waits remain unknown.",
66
+ items: p.waits
67
+ .filter((w) => w.current && !w.resolvedAt)
68
+ .map((w) =>
69
+ item(w.waitId, w.reason, {
70
+ Owner: w.owner,
71
+ "Human action required":
72
+ w.waitingOn === "human"
73
+ ? "Yes — human"
74
+ : w.waitingOn === "unknown"
75
+ ? "Unknown"
76
+ : `No — ${w.waitingOn}`,
77
+ "Next action": w.nextAction,
78
+ Scope: w.scope,
79
+ Attempt: w.attemptId,
80
+ Observed: w.startedAt,
81
+ Freshness: historical
82
+ ? "Historical observation"
83
+ : !Number.isFinite(Date.parse(w.startedAt))
84
+ ? "unknown"
85
+ : Date.parse(options.now) - Date.parse(w.startedAt) >
86
+ (options.freshnessWindowMs ?? 300000)
87
+ ? "stale"
88
+ : "recent",
89
+ Candidate: w.candidateTreeSha,
90
+ }),
91
+ ),
92
+ },
93
+ {
94
+ id: "work",
95
+ title: "Current work",
96
+ empty: attempts.length === 0
97
+ ? "No activity observations; execution status unknown."
98
+ : "No active work in the latest observations. Retained attempts appear in history.",
99
+ items: current
100
+ .filter(
101
+ (a) => !["completed", "failed", "interrupted"].includes(a.state),
102
+ )
103
+ .map((a) =>
104
+ item(a.attemptId, a.activityId, {
105
+ Owner: a.owner,
106
+ Phase: a.phase,
107
+ State: `${a.state} (reported)`,
108
+ Freshness: historical ? "Historical observation" : a.freshness,
109
+ "Last observed": a.lastObservedAt,
110
+ "Elapsed since start": a.startedAt
111
+ ? `${Math.max(0, Math.floor((Date.parse(options.now) - Date.parse(a.startedAt)) / 1000))}s`
112
+ : "Unknown — start not observed",
113
+ "Next step": a.nextStep,
114
+ Candidate: a.candidateTreeSha,
115
+ "Lifecycle history": a.lifecycleIncomplete
116
+ ? "Incomplete"
117
+ : "Observed",
118
+ }),
119
+ ),
120
+ },
121
+ {
122
+ id: "reviews",
123
+ title: "Reviewer attempts and history",
124
+ empty: "No reviewer attempt observations.",
125
+ items: attempts
126
+ .filter((a) => a.lensId !== undefined || a.phase === "review")
127
+ .map((a) =>
128
+ item(a.attemptId, a.lensId ?? a.activityId, {
129
+ Attempt: a.attemptId,
130
+ Round: a.round,
131
+ "Round ID": a.roundId,
132
+ Owner: a.owner,
133
+ State: a.state,
134
+ History: a.superseded
135
+ ? "Superseded attempt"
136
+ : "Latest observed attempt",
137
+ Verdict: a.verdict,
138
+ Cost: costLabel(a.cost),
139
+ Candidate: a.candidateTreeSha,
140
+ }),
141
+ ),
142
+ },
143
+ {
144
+ id: "findings",
145
+ title: "Current unresolved findings",
146
+ empty:
147
+ p.findingsCoverage === "unreported"
148
+ ? "Unknown — detailed finding observations were not recorded."
149
+ : "No unresolved findings in latest reported observations; this is not approval.",
150
+ items: p.currentFindings
151
+ .filter((f) => f.payload["state"] === "unresolved")
152
+ .map((f) =>
153
+ item(String(f.payload["findingId"]), String(f.payload["findingId"]), {
154
+ Severity: f.payload["severity"],
155
+ State: "Unresolved (reported)",
156
+ Report: f.payload["reportId"],
157
+ Attempt: f.payload["attemptId"],
158
+ Candidate: f.payload["candidateTreeSha"],
159
+ }),
160
+ ),
161
+ },
162
+ {
163
+ id: "evidence",
164
+ title: "Evidence and candidate",
165
+ empty: "No evidence observations. Applicability unknown.",
166
+ items: events
167
+ .filter((e) =>
168
+ [
169
+ "gate.reported",
170
+ "command.completed",
171
+ "review.round.closed",
172
+ ].includes(e.kind),
173
+ )
174
+ .map((e) =>
175
+ item(String(e.seq), e.kind, {
176
+ "Reported at": e.at,
177
+ Candidate: e.candidateTreeSha ?? e.payload["candidateTreeSha"],
178
+ Observation: e.payload,
179
+ Authority:
180
+ "Reported only — Applicability unknown; no verification is run by this view",
181
+ }),
182
+ ),
183
+ },
184
+ {
185
+ id: "reports",
186
+ title: "Retained reports",
187
+ empty: "No report references recorded.",
188
+ items: p.reports.map((r) =>
189
+ item(
190
+ String(r.payload["reportId"]),
191
+ String(r.payload["role"]),
192
+ {
193
+ Report: r.payload["reportId"],
194
+ Availability: r.payload["availability"],
195
+ Reason: r.payload["reason"],
196
+ "Originating report": r.payload["originatingReportId"],
197
+ Lens: r.payload["lensId"],
198
+ Attempt: r.payload["attemptId"],
199
+ Candidate: r.payload["candidateTreeSha"],
200
+ History: r.current
201
+ ? "Latest observed attempt"
202
+ : "Historical or incomplete binding",
203
+ },
204
+ typeof r.payload["artifactId"] === "string"
205
+ ? r.payload["artifactId"]
206
+ : undefined,
207
+ ),
208
+ ),
209
+ },
210
+ {
211
+ id: "finish",
212
+ title: "Declared finish line",
213
+ empty: "Finish steps unreported; delivery completion unknown.",
214
+ items: [],
215
+ },
216
+ {
217
+ id: "cost",
218
+ title: "Reported cost",
219
+ empty: "Cost unreported.",
220
+ items: [
221
+ {
222
+ id: "review-cost",
223
+ label: "Review totals",
224
+ fields: [
225
+ ...item("review-cost", "Review totals", {
226
+ Coverage: costs.review.coverage,
227
+ "Unreported round entries": costs.review.unreportedEntries,
228
+ ...(costs.review.totals.length === 0
229
+ ? { Measurement: "Unreported" }
230
+ : {}),
231
+ }).fields,
232
+ ...costs.review.totals.map((total) => ({
233
+ label: `${total.reportedBy} · ${total.unit}`,
234
+ value:
235
+ total.total === null
236
+ ? "Unavailable — reported sum exceeds numeric range"
237
+ : `${total.total} ${total.unit}`,
238
+ })),
239
+ ],
240
+ },
241
+ item("run-cost", "Run total", {
242
+ Coverage: runCost["coverage"],
243
+ Measurement: costLabel(runCost),
244
+ "Reported by": runCost["reportedBy"],
245
+ Accounting:
246
+ "Run totals can include review and attempt costs. These totals are shown separately and are not added together.",
247
+ }),
248
+ ...attempts.filter((a) => a.cost !== undefined).map((a) => {
249
+ const cost = a.cost as Record<string, unknown>;
250
+ return item(`attempt-cost-${a.attemptId}`, `Attempt cost · ${a.activityId}`, {
251
+ Attempt: a.attemptId,
252
+ Owner: a.owner,
253
+ Phase: a.phase,
254
+ State: `${a.state} (reported)`,
255
+ History: a.superseded ? "Superseded attempt" : "Latest observed attempt",
256
+ Candidate: a.candidateTreeSha,
257
+ Coverage: cost["coverage"],
258
+ Measurement: costLabel(a.cost),
259
+ "Reported by": cost["reportedBy"],
260
+ Accounting: "Reported attempt measurement; not added to run or review totals, which may overlap.",
261
+ });
262
+ }),
263
+ ],
264
+ },
265
+ {
266
+ id: "activity-history",
267
+ title: "Activity history",
268
+ empty: "No completed, failed, interrupted or superseded activity observations.",
269
+ items: attempts
270
+ .filter((a) => a.superseded || ["completed", "failed", "interrupted"].includes(a.state))
271
+ .map((a) => item(a.attemptId, a.activityId, {
272
+ Attempt: a.attemptId,
273
+ Owner: a.owner,
274
+ Phase: a.phase,
275
+ State: `${a.state} (reported)`,
276
+ History: a.superseded ? "Superseded attempt" : "Latest observed attempt",
277
+ Freshness: historical ? "Historical observation" : a.freshness,
278
+ "Last observed": a.lastObservedAt,
279
+ Candidate: a.candidateTreeSha,
280
+ "Lifecycle history": a.lifecycleIncomplete ? "Incomplete" : "Observed",
281
+ Cost: costLabel(a.cost),
282
+ })),
283
+ },
284
+ {
285
+ id: "finding-history",
286
+ title: "Finding history",
287
+ empty: "No detailed finding history.",
288
+ items: p.findings.map((f) =>
289
+ item(String(f.seq), String(f.payload["findingId"]), {
290
+ State: f.payload["state"],
291
+ Severity: f.payload["severity"],
292
+ Attempt: f.payload["attemptId"],
293
+ Candidate: f.payload["candidateTreeSha"],
294
+ "Deferred issue": f.payload["deferredIssueId"],
295
+ Observed: f.at,
296
+ }),
297
+ ),
298
+ },
299
+ ];
300
+ const reviewSection = sections.find((s) => s.id === "reviews")!;
301
+ sections[2] = {
302
+ ...reviewSection,
303
+ items: [
304
+ ...events
305
+ .filter((e) => e.kind === "review.round.opened")
306
+ .map((e) =>
307
+ item(`round-${e.seq}`, "Declared review round", {
308
+ Round: e.payload["round"],
309
+ "Round ID": e.payload["roundId"],
310
+ Bound: e.payload["bound"],
311
+ Grace: e.payload["grace"],
312
+ Reopens: e.payload["reopensRoundId"],
313
+ Candidate: e.payload["candidateTreeSha"],
314
+ }),
315
+ ),
316
+ ...reviewSection.items,
317
+ ],
318
+ };
319
+ const finish = new Map<string, (typeof p.finishSteps)[number]>();
320
+ for (const step of p.finishSteps)
321
+ finish.set(String(step.payload["stepId"]), step);
322
+ sections[6] = {
323
+ id: "finish",
324
+ title: "Declared finish line",
325
+ empty: "Finish steps unreported; delivery completion unknown.",
326
+ items: [...finish.values()].map((s) =>
327
+ item(String(s.payload["stepId"]), String(s.payload["name"]), {
328
+ State: s.payload["state"],
329
+ Owner: s.payload["owner"],
330
+ Reason: s.payload["reason"],
331
+ Candidate: s.payload["candidateTreeSha"],
332
+ }),
333
+ ),
334
+ };
335
+ return {
336
+ spec: "run-view/1",
337
+ historical,
338
+ asOf: options.now,
339
+ authority:
340
+ "Self-attested observations. Applicability unknown; no permission or candidate approval is granted.",
341
+ sections,
342
+ };
343
+ }