@tangle-network/agent-app 0.45.25 → 0.45.27
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/dist/assistant/index.js +5 -5
- package/dist/chat-routes/index.js +21 -21
- package/dist/chat-store/index.js +2 -2
- package/dist/{chunk-B2UEW62O.js → chunk-5ZTFZBS6.js} +4 -4
- package/dist/chunk-N3G3FSM4.js +509 -0
- package/dist/chunk-N3G3FSM4.js.map +1 -0
- package/dist/chunk-NEJ7OMQS.js +351 -0
- package/dist/chunk-NEJ7OMQS.js.map +1 -0
- package/dist/{chunk-KOMP6NDW.js → chunk-ODYE4A7L.js} +6 -6
- package/dist/{chunk-7V2I3JGZ.js → chunk-OTLEYHOG.js} +50 -12
- package/dist/chunk-OTLEYHOG.js.map +1 -0
- package/dist/chunk-WD2B6HGY.js +1046 -0
- package/dist/chunk-WD2B6HGY.js.map +1 -0
- package/dist/design-canvas-react/index.js +4 -4
- package/dist/run-tEsZUhAf.d.ts +40 -0
- package/dist/sandbox/index.d.ts +53 -1
- package/dist/sandbox/index.js +1 -1
- package/dist/signoff/cli.d.ts +12 -0
- package/dist/signoff/cli.js +166 -0
- package/dist/signoff/cli.js.map +1 -0
- package/dist/signoff/index.d.ts +430 -0
- package/dist/signoff/index.js +63 -0
- package/dist/signoff/index.js.map +1 -0
- package/dist/signoff/proof-cli.d.ts +1 -0
- package/dist/signoff/proof-cli.js +92 -0
- package/dist/signoff/proof-cli.js.map +1 -0
- package/dist/signoff/proof.d.ts +515 -0
- package/dist/signoff/proof.js +148 -0
- package/dist/signoff/proof.js.map +1 -0
- package/dist/spend/cli.d.ts +1 -0
- package/dist/spend/cli.js +119 -0
- package/dist/spend/cli.js.map +1 -0
- package/dist/spend/index.d.ts +640 -0
- package/dist/spend/index.js +196 -0
- package/dist/spend/index.js.map +1 -0
- package/dist/stream/index.js +14 -14
- package/dist/teams/index.js +9 -9
- package/dist/teams/invitations-api.js +7 -7
- package/dist/types-U7Nz-txa.d.ts +233 -0
- package/dist/web-react/index.js +11 -11
- package/package.json +22 -3
- package/dist/chunk-7V2I3JGZ.js.map +0 -1
- /package/dist/{chunk-B2UEW62O.js.map → chunk-5ZTFZBS6.js.map} +0 -0
- /package/dist/{chunk-KOMP6NDW.js.map → chunk-ODYE4A7L.js.map} +0 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { b as SignoffReport } from '../types-U7Nz-txa.js';
|
|
3
|
+
|
|
4
|
+
/** Bumped when the canonical body shape changes; a verifier refuses versions it does not know. */
|
|
5
|
+
declare const SIGNOFF_PROOF_VERSION = 1;
|
|
6
|
+
/**
|
|
7
|
+
* The runner's step outcomes, restated as proof vocabulary.
|
|
8
|
+
*
|
|
9
|
+
* A status is carried alongside the exit code because they answer different
|
|
10
|
+
* questions and only one of them is safe on its own: a step that was `skipped`,
|
|
11
|
+
* `cancelled` or `blocked` never produced an exit code at all, and defaulting
|
|
12
|
+
* that to `0` is precisely how a run that did not happen reads as a pass.
|
|
13
|
+
*
|
|
14
|
+
* The `satisfies` and the `Exclude` below pin this list to the runner's own
|
|
15
|
+
* union in BOTH directions, so a status added there fails this file's typecheck
|
|
16
|
+
* instead of quietly arriving as an unmodelled string.
|
|
17
|
+
*/
|
|
18
|
+
declare const SIGNOFF_STEP_STATUSES: readonly ["passed", "failed", "skipped", "cancelled", "blocked"];
|
|
19
|
+
declare const signoffProofStepSchema: z.ZodObject<{
|
|
20
|
+
id: z.ZodString;
|
|
21
|
+
command: z.ZodString;
|
|
22
|
+
cwd: z.ZodString;
|
|
23
|
+
status: z.ZodEnum<{
|
|
24
|
+
passed: "passed";
|
|
25
|
+
failed: "failed";
|
|
26
|
+
skipped: "skipped";
|
|
27
|
+
cancelled: "cancelled";
|
|
28
|
+
blocked: "blocked";
|
|
29
|
+
}>;
|
|
30
|
+
exitCode: z.ZodNumber;
|
|
31
|
+
durationMs: z.ZodNumber;
|
|
32
|
+
startedAt: z.ZodString;
|
|
33
|
+
outputSha256: z.ZodString;
|
|
34
|
+
}, z.core.$strip>;
|
|
35
|
+
declare const signoffProofPeerSchema: z.ZodObject<{
|
|
36
|
+
name: z.ZodString;
|
|
37
|
+
version: z.ZodNullable<z.ZodString>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
declare const signoffProofSubjectSchema: z.ZodObject<{
|
|
40
|
+
repo: z.ZodString;
|
|
41
|
+
commit: z.ZodString;
|
|
42
|
+
tree: z.ZodString;
|
|
43
|
+
commitTree: z.ZodString;
|
|
44
|
+
parents: z.ZodArray<z.ZodString>;
|
|
45
|
+
committedAt: z.ZodString;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
declare const signoffProofBodySchema: z.ZodObject<{
|
|
48
|
+
proofVersion: z.ZodNumber;
|
|
49
|
+
subject: z.ZodObject<{
|
|
50
|
+
repo: z.ZodString;
|
|
51
|
+
commit: z.ZodString;
|
|
52
|
+
tree: z.ZodString;
|
|
53
|
+
commitTree: z.ZodString;
|
|
54
|
+
parents: z.ZodArray<z.ZodString>;
|
|
55
|
+
committedAt: z.ZodString;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
signedAt: z.ZodString;
|
|
58
|
+
host: z.ZodObject<{
|
|
59
|
+
hostname: z.ZodString;
|
|
60
|
+
platform: z.ZodString;
|
|
61
|
+
arch: z.ZodString;
|
|
62
|
+
user: z.ZodString;
|
|
63
|
+
}, z.core.$strip>;
|
|
64
|
+
tooling: z.ZodObject<{
|
|
65
|
+
node: z.ZodString;
|
|
66
|
+
pnpm: z.ZodNullable<z.ZodString>;
|
|
67
|
+
peers: z.ZodArray<z.ZodObject<{
|
|
68
|
+
name: z.ZodString;
|
|
69
|
+
version: z.ZodNullable<z.ZodString>;
|
|
70
|
+
}, z.core.$strip>>;
|
|
71
|
+
}, z.core.$strip>;
|
|
72
|
+
wallClockMs: z.ZodNumber;
|
|
73
|
+
seeds: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
74
|
+
declaredRequired: z.ZodArray<z.ZodString>;
|
|
75
|
+
steps: z.ZodArray<z.ZodObject<{
|
|
76
|
+
id: z.ZodString;
|
|
77
|
+
command: z.ZodString;
|
|
78
|
+
cwd: z.ZodString;
|
|
79
|
+
status: z.ZodEnum<{
|
|
80
|
+
passed: "passed";
|
|
81
|
+
failed: "failed";
|
|
82
|
+
skipped: "skipped";
|
|
83
|
+
cancelled: "cancelled";
|
|
84
|
+
blocked: "blocked";
|
|
85
|
+
}>;
|
|
86
|
+
exitCode: z.ZodNumber;
|
|
87
|
+
durationMs: z.ZodNumber;
|
|
88
|
+
startedAt: z.ZodString;
|
|
89
|
+
outputSha256: z.ZodString;
|
|
90
|
+
}, z.core.$strip>>;
|
|
91
|
+
verdict: z.ZodEnum<{
|
|
92
|
+
pass: "pass";
|
|
93
|
+
fail: "fail";
|
|
94
|
+
}>;
|
|
95
|
+
}, z.core.$strip>;
|
|
96
|
+
declare const signoffProofSealSchema: z.ZodObject<{
|
|
97
|
+
algorithm: z.ZodEnum<{
|
|
98
|
+
sha256: "sha256";
|
|
99
|
+
"hmac-sha256": "hmac-sha256";
|
|
100
|
+
}>;
|
|
101
|
+
bodySha256: z.ZodString;
|
|
102
|
+
keyId: z.ZodNullable<z.ZodString>;
|
|
103
|
+
mac: z.ZodNullable<z.ZodString>;
|
|
104
|
+
}, z.core.$strip>;
|
|
105
|
+
declare const signoffProofSchema: z.ZodObject<{
|
|
106
|
+
body: z.ZodObject<{
|
|
107
|
+
proofVersion: z.ZodNumber;
|
|
108
|
+
subject: z.ZodObject<{
|
|
109
|
+
repo: z.ZodString;
|
|
110
|
+
commit: z.ZodString;
|
|
111
|
+
tree: z.ZodString;
|
|
112
|
+
commitTree: z.ZodString;
|
|
113
|
+
parents: z.ZodArray<z.ZodString>;
|
|
114
|
+
committedAt: z.ZodString;
|
|
115
|
+
}, z.core.$strip>;
|
|
116
|
+
signedAt: z.ZodString;
|
|
117
|
+
host: z.ZodObject<{
|
|
118
|
+
hostname: z.ZodString;
|
|
119
|
+
platform: z.ZodString;
|
|
120
|
+
arch: z.ZodString;
|
|
121
|
+
user: z.ZodString;
|
|
122
|
+
}, z.core.$strip>;
|
|
123
|
+
tooling: z.ZodObject<{
|
|
124
|
+
node: z.ZodString;
|
|
125
|
+
pnpm: z.ZodNullable<z.ZodString>;
|
|
126
|
+
peers: z.ZodArray<z.ZodObject<{
|
|
127
|
+
name: z.ZodString;
|
|
128
|
+
version: z.ZodNullable<z.ZodString>;
|
|
129
|
+
}, z.core.$strip>>;
|
|
130
|
+
}, z.core.$strip>;
|
|
131
|
+
wallClockMs: z.ZodNumber;
|
|
132
|
+
seeds: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
133
|
+
declaredRequired: z.ZodArray<z.ZodString>;
|
|
134
|
+
steps: z.ZodArray<z.ZodObject<{
|
|
135
|
+
id: z.ZodString;
|
|
136
|
+
command: z.ZodString;
|
|
137
|
+
cwd: z.ZodString;
|
|
138
|
+
status: z.ZodEnum<{
|
|
139
|
+
passed: "passed";
|
|
140
|
+
failed: "failed";
|
|
141
|
+
skipped: "skipped";
|
|
142
|
+
cancelled: "cancelled";
|
|
143
|
+
blocked: "blocked";
|
|
144
|
+
}>;
|
|
145
|
+
exitCode: z.ZodNumber;
|
|
146
|
+
durationMs: z.ZodNumber;
|
|
147
|
+
startedAt: z.ZodString;
|
|
148
|
+
outputSha256: z.ZodString;
|
|
149
|
+
}, z.core.$strip>>;
|
|
150
|
+
verdict: z.ZodEnum<{
|
|
151
|
+
pass: "pass";
|
|
152
|
+
fail: "fail";
|
|
153
|
+
}>;
|
|
154
|
+
}, z.core.$strip>;
|
|
155
|
+
seal: z.ZodObject<{
|
|
156
|
+
algorithm: z.ZodEnum<{
|
|
157
|
+
sha256: "sha256";
|
|
158
|
+
"hmac-sha256": "hmac-sha256";
|
|
159
|
+
}>;
|
|
160
|
+
bodySha256: z.ZodString;
|
|
161
|
+
keyId: z.ZodNullable<z.ZodString>;
|
|
162
|
+
mac: z.ZodNullable<z.ZodString>;
|
|
163
|
+
}, z.core.$strip>;
|
|
164
|
+
}, z.core.$strip>;
|
|
165
|
+
type SignoffProofStep = z.infer<typeof signoffProofStepSchema>;
|
|
166
|
+
type SignoffProofPeer = z.infer<typeof signoffProofPeerSchema>;
|
|
167
|
+
type SignoffProofSubject = z.infer<typeof signoffProofSubjectSchema>;
|
|
168
|
+
type SignoffProofBody = z.infer<typeof signoffProofBodySchema>;
|
|
169
|
+
type SignoffProofSeal = z.infer<typeof signoffProofSealSchema>;
|
|
170
|
+
type SignoffProof = z.infer<typeof signoffProofSchema>;
|
|
171
|
+
type CanonicalValue = string | number | boolean | null | readonly CanonicalValue[] | {
|
|
172
|
+
readonly [key: string]: CanonicalValue;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Deterministic JSON: object keys sorted, array order preserved, no whitespace.
|
|
176
|
+
*
|
|
177
|
+
* The seal is a hash over this string, so two readers must produce byte-identical
|
|
178
|
+
* bytes from the same record. `undefined` throws rather than vanishing — a field
|
|
179
|
+
* that silently disappears is a field the hash stops covering.
|
|
180
|
+
*/
|
|
181
|
+
declare function canonicalJson(value: CanonicalValue): string;
|
|
182
|
+
declare function canonicalizeProofBody(body: SignoffProofBody): string;
|
|
183
|
+
declare function hashProofBody(body: SignoffProofBody): string;
|
|
184
|
+
/** The digest a runner records for a step's combined output. Shared so both halves agree. */
|
|
185
|
+
declare function hashStepOutput(output: string): string;
|
|
186
|
+
declare function signoffKeyId(key: Uint8Array): string;
|
|
187
|
+
/** Read the local sign-off key. Throws when absent — an unreadable key is never a silent downgrade to unsealed. */
|
|
188
|
+
declare function readSignoffKey(path: string): Uint8Array;
|
|
189
|
+
declare function sealProof(body: SignoffProofBody, key?: Uint8Array): SignoffProof;
|
|
190
|
+
/** Constant-time comparison of two hex digests of equal length. */
|
|
191
|
+
declare function macMatches(expected: string, actual: string): boolean;
|
|
192
|
+
interface ToolingFactsInput {
|
|
193
|
+
readonly repoDir: string;
|
|
194
|
+
/** Package names whose resolved on-disk version belongs in the proof. */
|
|
195
|
+
readonly peerNames: readonly string[];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The versions that actually resolved on disk — the field CI's clean install
|
|
199
|
+
* makes trustworthy and a warm local `node_modules` does not. A below-floor peer
|
|
200
|
+
* is invisible to typecheck and to a green suite (it fails at the wire call), so
|
|
201
|
+
* the proof records what was really there rather than what the manifest asks for.
|
|
202
|
+
*/
|
|
203
|
+
declare function collectToolingFacts(input: ToolingFactsInput): SignoffProofBody['tooling'];
|
|
204
|
+
/** Resolved version from the consumer's own `node_modules`, or `null` when the package is not there. */
|
|
205
|
+
declare function readInstalledVersion(repoDir: string, packageName: string): string | null;
|
|
206
|
+
/** Every `@tangle-network/*` name the repo declares as a peer or a dependency. */
|
|
207
|
+
declare function tangleDependencyNames(repoDir: string): readonly string[];
|
|
208
|
+
interface BuildSignoffProofInput {
|
|
209
|
+
readonly repoDir: string;
|
|
210
|
+
/** Repo identity the verifier's required-step table is keyed on. */
|
|
211
|
+
readonly repo: string;
|
|
212
|
+
/** Revision the sign-off is for; defaults to `HEAD`. */
|
|
213
|
+
readonly rev?: string;
|
|
214
|
+
readonly steps: readonly SignoffProofStep[];
|
|
215
|
+
/** Measured elapsed time for the run. Required — it is not derivable from the steps. */
|
|
216
|
+
readonly wallClockMs: number;
|
|
217
|
+
/** Step ids this run treated as required. Checked against the verifier's table. */
|
|
218
|
+
readonly declaredRequired: readonly string[];
|
|
219
|
+
readonly seeds: Readonly<Record<string, string | number>>;
|
|
220
|
+
readonly peerNames?: readonly string[];
|
|
221
|
+
readonly key?: Uint8Array;
|
|
222
|
+
readonly now?: Date;
|
|
223
|
+
}
|
|
224
|
+
declare function buildSignoffProof(input: BuildSignoffProofInput): SignoffProof;
|
|
225
|
+
/** Parse an untrusted proof document, failing loud on any shape the verifier cannot reason about. */
|
|
226
|
+
declare function parseSignoffProof(json: string): SignoffProof;
|
|
227
|
+
declare function serializeSignoffProof(proof: SignoffProof): string;
|
|
228
|
+
/**
|
|
229
|
+
* The single line an operator pastes into a PR or a merge commit: how many steps
|
|
230
|
+
* ran, how long it took, the seeds, and the verdict.
|
|
231
|
+
*/
|
|
232
|
+
declare function formatSignoffSummary(proof: SignoffProof): string;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The join between the runner and the proof: one sign-off run produces one
|
|
236
|
+
* report, and this turns that report into the signed, attachable record.
|
|
237
|
+
*
|
|
238
|
+
* Nothing here re-runs or re-judges anything. The runner owns what happened; the
|
|
239
|
+
* proof owns what is provable about it later. Keeping the two in one direction
|
|
240
|
+
* is what stops a second, drifting idea of "which steps ran" from appearing.
|
|
241
|
+
*/
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* A step that never executed has no exit code, so it is recorded as one no
|
|
245
|
+
* process can produce. `status` is what a reader and the verifier judge on;
|
|
246
|
+
* this exists so the field is never a plausible-looking `0`.
|
|
247
|
+
*/
|
|
248
|
+
declare const NO_EXIT_CODE = -1;
|
|
249
|
+
/** The `command` of a step that never executed. A skipped step had no command line. */
|
|
250
|
+
declare const NOT_EXECUTED_COMMAND = "(never executed)";
|
|
251
|
+
interface ProofFromReportInput {
|
|
252
|
+
readonly report: SignoffReport;
|
|
253
|
+
/** Repo identity the verifier's required-step table is keyed on. */
|
|
254
|
+
readonly repo: string;
|
|
255
|
+
/** Source repository. Defaults to the root the run recorded. */
|
|
256
|
+
readonly repoDir?: string;
|
|
257
|
+
/** Step ids the run treated as required. Defaults to every step the report carries. */
|
|
258
|
+
readonly declaredRequired?: readonly string[];
|
|
259
|
+
readonly key?: Uint8Array;
|
|
260
|
+
readonly now?: Date;
|
|
261
|
+
}
|
|
262
|
+
/** Every seed the run used, keyed so a reader can replay one step rather than the whole run. */
|
|
263
|
+
declare function seedsFromReport(report: SignoffReport): Record<string, string | number>;
|
|
264
|
+
declare function proofFromSignoffReport(input: ProofFromReportInput): SignoffProof;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Attaching a proof to the commit it verified.
|
|
268
|
+
*
|
|
269
|
+
* ## Why `git notes`, and what the two rejected carriers cost
|
|
270
|
+
*
|
|
271
|
+
* **A commit-message trailer** cannot work for the field that matters. The
|
|
272
|
+
* trailer is part of the commit object, so writing it changes the SHA the proof
|
|
273
|
+
* claims to cover — the proof would have to be produced before the commit it
|
|
274
|
+
* names exists. Amending re-writes the SHA again, and GitHub's squash-merge
|
|
275
|
+
* rebuilds the message from the PR body, so the trailer does not survive the one
|
|
276
|
+
* transition it would need to.
|
|
277
|
+
*
|
|
278
|
+
* **A committed artifact directory** (`.signoff/<sha>.json`) changes the tree,
|
|
279
|
+
* so the tree hash inside the proof can never cover the file carrying it. It
|
|
280
|
+
* also lands in every diff, and it is exactly the "file someone can forget"
|
|
281
|
+
* shape the requirement rules out — deleting it is a normal-looking change.
|
|
282
|
+
*
|
|
283
|
+
* **`git notes` wins because it is the only carrier that is addressed BY the
|
|
284
|
+
* SHA without being part of it.** The note is written after the commit exists,
|
|
285
|
+
* it changes neither the SHA nor the tree, and `git notes show <sha>` is a
|
|
286
|
+
* definite yes/no for any commit anyone can name.
|
|
287
|
+
*
|
|
288
|
+
* ## The tradeoff `git notes` really carries, and how this module answers it
|
|
289
|
+
*
|
|
290
|
+
* Notes are attached to a commit id, and a rebase or a squash-merge produces a
|
|
291
|
+
* NEW commit id, so the note does not follow by itself. Two mechanisms close
|
|
292
|
+
* that gap, and both are needed:
|
|
293
|
+
*
|
|
294
|
+
* 1. `notes.rewriteRef` (see `SIGNOFF_NOTES_GIT_CONFIG`) makes local rebase and
|
|
295
|
+
* amend copy the note forward automatically.
|
|
296
|
+
* 2. `resolveSignoffProof` falls back to a **tree scan**: it reads every proof
|
|
297
|
+
* under the notes ref and matches on the TREE the commit carries. A rebase
|
|
298
|
+
* or a squash of one branch onto an unchanged base preserves the tree, so
|
|
299
|
+
* the content that was verified is still provably the content that merged,
|
|
300
|
+
* even though the SHA changed. The verifier reports which binding it used —
|
|
301
|
+
* `exact` or `tree-equivalent` — and never conflates them.
|
|
302
|
+
*
|
|
303
|
+
* Notes are neither pushed nor fetched by default; `SIGNOFF_NOTES_GIT_CONFIG`
|
|
304
|
+
* carries the refspecs that fix that, and a missing note is reported as
|
|
305
|
+
* `not-found`, never as a pass.
|
|
306
|
+
*/
|
|
307
|
+
|
|
308
|
+
declare const SIGNOFF_NOTES_REF = "refs/notes/signoff";
|
|
309
|
+
/**
|
|
310
|
+
* The git configuration a repo needs for notes to travel. Emitted by the CLI
|
|
311
|
+
* when a proof cannot be found, because the usual cause is an unconfigured repo
|
|
312
|
+
* rather than an unsigned commit.
|
|
313
|
+
*/
|
|
314
|
+
declare const SIGNOFF_NOTES_GIT_CONFIG: readonly string[];
|
|
315
|
+
interface AttachSignoffProofInput {
|
|
316
|
+
readonly repoDir: string;
|
|
317
|
+
readonly proof: SignoffProof;
|
|
318
|
+
/** Revision to annotate; defaults to the commit the proof names. */
|
|
319
|
+
readonly rev?: string;
|
|
320
|
+
/** Replace an existing note. Off by default so a second sign-off cannot quietly overwrite the first. */
|
|
321
|
+
readonly overwrite?: boolean;
|
|
322
|
+
}
|
|
323
|
+
interface AttachedSignoffProof {
|
|
324
|
+
readonly commit: string;
|
|
325
|
+
readonly ref: string;
|
|
326
|
+
}
|
|
327
|
+
declare function attachSignoffProof(input: AttachSignoffProofInput): AttachedSignoffProof;
|
|
328
|
+
type SignoffProofLookup = {
|
|
329
|
+
readonly found: true;
|
|
330
|
+
readonly proof: SignoffProof;
|
|
331
|
+
readonly commit: string;
|
|
332
|
+
readonly binding: 'exact' | 'tree-equivalent';
|
|
333
|
+
} | {
|
|
334
|
+
readonly found: false;
|
|
335
|
+
readonly commit: string;
|
|
336
|
+
};
|
|
337
|
+
/** The note attached to exactly this commit, or `found: false`. Never searches. */
|
|
338
|
+
declare function readSignoffProofNote(repoDir: string, rev: string): SignoffProofLookup;
|
|
339
|
+
/** Every proof stored under the notes ref, paired with the commit it annotates. */
|
|
340
|
+
declare function listSignoffProofs(repoDir: string): readonly {
|
|
341
|
+
readonly commit: string;
|
|
342
|
+
readonly proof: SignoffProof;
|
|
343
|
+
}[];
|
|
344
|
+
/**
|
|
345
|
+
* Answer "was this SHA signed off" for any commit, including one produced by a
|
|
346
|
+
* rebase or a squash-merge of the branch that was actually verified.
|
|
347
|
+
*
|
|
348
|
+
* Exact note first. Then the tree scan — a proof whose `commitTree` equals this
|
|
349
|
+
* commit's tree verified byte-identical content, which is the strongest claim
|
|
350
|
+
* available once the SHA has been rewritten. The binding is returned so a caller
|
|
351
|
+
* can tell the two apart; nothing here treats them as the same thing.
|
|
352
|
+
*/
|
|
353
|
+
declare function resolveSignoffProof(repoDir: string, rev: string): SignoffProofLookup;
|
|
354
|
+
|
|
355
|
+
/** A git invocation that exited non-zero, carrying stderr so the caller can act. */
|
|
356
|
+
declare class SignoffGitError extends Error {
|
|
357
|
+
readonly args: readonly string[];
|
|
358
|
+
readonly status: number | null;
|
|
359
|
+
readonly stderr: string;
|
|
360
|
+
constructor(args: readonly string[], status: number | null, stderr: string);
|
|
361
|
+
}
|
|
362
|
+
interface GitResult {
|
|
363
|
+
readonly status: number | null;
|
|
364
|
+
readonly stdout: string;
|
|
365
|
+
readonly stderr: string;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Run git and hand back the raw result. `GIT_OPTIONAL_LOCKS=0` keeps a read
|
|
369
|
+
* from touching the index while another agent works the same worktree — this
|
|
370
|
+
* repo is shared by concurrent sessions.
|
|
371
|
+
*/
|
|
372
|
+
declare function runGit(repoDir: string, args: readonly string[], options?: {
|
|
373
|
+
readonly input?: string;
|
|
374
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
375
|
+
}): GitResult;
|
|
376
|
+
/** Run git, or throw. Trailing newline is stripped — every caller wants the value, not the line. */
|
|
377
|
+
declare function gitText(repoDir: string, args: readonly string[], options?: {
|
|
378
|
+
readonly input?: string;
|
|
379
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
380
|
+
}): string;
|
|
381
|
+
/**
|
|
382
|
+
* Is `ancestor` reachable from `descendant`? A port rather than a direct call so
|
|
383
|
+
* the verifier stays a pure function over facts a caller supplies.
|
|
384
|
+
*/
|
|
385
|
+
type IsAncestorFn = (ancestor: string, descendant: string) => boolean;
|
|
386
|
+
declare function gitIsAncestor(repoDir: string): IsAncestorFn;
|
|
387
|
+
interface CommitFacts {
|
|
388
|
+
/** 40-hex commit id. */
|
|
389
|
+
readonly commit: string;
|
|
390
|
+
/** 40-hex id of the tree the COMMIT points at. */
|
|
391
|
+
readonly commitTree: string;
|
|
392
|
+
readonly parents: readonly string[];
|
|
393
|
+
/** Committer date, UTC ISO-8601 with a `Z` suffix. */
|
|
394
|
+
readonly committedAt: string;
|
|
395
|
+
}
|
|
396
|
+
/** Resolve a revision to the commit it names, failing loud on an unknown rev. */
|
|
397
|
+
declare function resolveCommit(repoDir: string, rev: string): string;
|
|
398
|
+
declare function readCommitFacts(repoDir: string, rev: string): CommitFacts;
|
|
399
|
+
/**
|
|
400
|
+
* Hash the tree the checks actually ran against, including uncommitted and
|
|
401
|
+
* untracked (non-ignored) files.
|
|
402
|
+
*
|
|
403
|
+
* This is the field that makes drift detectable. A sign-off that ran over an
|
|
404
|
+
* edited worktree produces a tree id no commit carries, so it cannot verify
|
|
405
|
+
* against the commit it claims — which is the intended outcome, not a bug.
|
|
406
|
+
*
|
|
407
|
+
* Written through a throwaway index (`GIT_INDEX_FILE`) so a concurrent agent's
|
|
408
|
+
* staged work in the real index is neither read nor disturbed. `git add -A`
|
|
409
|
+
* honours `.gitignore`, so `node_modules` / `dist` stay out.
|
|
410
|
+
*/
|
|
411
|
+
declare function computeWorktreeTree(repoDir: string): string;
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* `verify-proof` — the half a reader runs, on a machine that did not produce
|
|
415
|
+
* the proof, to decide whether a commit was really signed off.
|
|
416
|
+
*
|
|
417
|
+
* The verifier never believes the document. Every claim it can re-derive, it
|
|
418
|
+
* re-derives from git and compares; every claim it cannot re-derive is reported
|
|
419
|
+
* as recorded, not as checked. The one field the proof is deliberately not
|
|
420
|
+
* allowed to own is **which steps were required**: a run that declares its own
|
|
421
|
+
* bar can declare an empty one, so the authoritative table lives here and a
|
|
422
|
+
* proof declaring fewer requirements than its repo's table fails as
|
|
423
|
+
* `lowered-bar`.
|
|
424
|
+
*/
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* The steps a repo's sign-off MUST cover, transcribed from each repo's CI job.
|
|
428
|
+
* A runner is free to run more; it may never run fewer.
|
|
429
|
+
*
|
|
430
|
+
* agent-app .github/workflows/ci.yml
|
|
431
|
+
* tax-agent .github/workflows/deploy.yml (the `ci` job)
|
|
432
|
+
* legal-agent .github/workflows/deploy.yml (the `ci` job)
|
|
433
|
+
*/
|
|
434
|
+
declare const SIGNOFF_REQUIRED_STEPS: Readonly<Record<string, readonly string[]>>;
|
|
435
|
+
/** Throws for a repo with no table — an unrecognised repo has no bar, and no bar is not a pass. */
|
|
436
|
+
declare function requiredStepsFor(repo: string): readonly string[];
|
|
437
|
+
type SignoffFailureCode = 'unsupported-version' | 'body-tampered' | 'mac-missing' | 'mac-invalid' | 'unknown-repo' | 'repo-mismatch' | 'tree-mismatch' | 'dirty-worktree' | 'commit-unbound' | 'missing-required-step' | 'lowered-bar' | 'duplicate-step' | 'step-failed' | 'verdict-fail' | 'stale-proof';
|
|
438
|
+
interface SignoffFailure {
|
|
439
|
+
readonly code: SignoffFailureCode;
|
|
440
|
+
readonly detail: string;
|
|
441
|
+
}
|
|
442
|
+
/** How the proof attaches to the commit that was asked about. */
|
|
443
|
+
type SignoffCommitBinding =
|
|
444
|
+
/** The proof names this exact commit. */
|
|
445
|
+
'exact'
|
|
446
|
+
/** A different commit id, but byte-identical content — a rebase or a squash of what was verified. */
|
|
447
|
+
| 'tree-equivalent'
|
|
448
|
+
/** Neither. The proof does not describe this commit. */
|
|
449
|
+
| 'none';
|
|
450
|
+
interface SignoffVerification {
|
|
451
|
+
readonly ok: boolean;
|
|
452
|
+
readonly commitBinding: SignoffCommitBinding;
|
|
453
|
+
/** True only when a key was supplied AND the mac over the canonical body matched. */
|
|
454
|
+
readonly macChecked: boolean;
|
|
455
|
+
readonly failures: readonly SignoffFailure[];
|
|
456
|
+
readonly proof: SignoffProof;
|
|
457
|
+
readonly target: CommitFacts;
|
|
458
|
+
readonly requiredSteps: readonly string[];
|
|
459
|
+
}
|
|
460
|
+
interface VerifySignoffProofOptions {
|
|
461
|
+
/** The commit the reader is asking about, read from git — never from the proof. */
|
|
462
|
+
readonly target: CommitFacts;
|
|
463
|
+
/**
|
|
464
|
+
* Reachability in the target's history. Required, because the clock cannot
|
|
465
|
+
* answer staleness on its own: git records committer time to the SECOND, so a
|
|
466
|
+
* proof and the commit it is being replayed onto routinely share a timestamp.
|
|
467
|
+
* Ancestry is exact and clock-free.
|
|
468
|
+
*/
|
|
469
|
+
readonly isAncestor: IsAncestorFn;
|
|
470
|
+
/** Local HMAC key. Omitted, the mac is reported unchecked rather than assumed good. */
|
|
471
|
+
readonly key?: Uint8Array;
|
|
472
|
+
/** Overrides the built-in table. Supplying `[]` is a deliberate no-bar check, and says so. */
|
|
473
|
+
readonly requiredSteps?: readonly string[];
|
|
474
|
+
/** Repo identity the caller expects; a mismatch against the proof is a failure, not a rename. */
|
|
475
|
+
readonly expectRepo?: string;
|
|
476
|
+
}
|
|
477
|
+
declare function verifySignoffProof(proof: SignoffProof, options: VerifySignoffProofOptions): SignoffVerification;
|
|
478
|
+
interface VerifySignoffAtRevInput {
|
|
479
|
+
readonly repoDir: string;
|
|
480
|
+
readonly rev: string;
|
|
481
|
+
readonly key?: Uint8Array;
|
|
482
|
+
readonly requiredSteps?: readonly string[];
|
|
483
|
+
readonly expectRepo?: string;
|
|
484
|
+
}
|
|
485
|
+
type SignoffLookupFailure = {
|
|
486
|
+
readonly found: false;
|
|
487
|
+
readonly commit: string;
|
|
488
|
+
readonly hint: readonly string[];
|
|
489
|
+
};
|
|
490
|
+
type SignoffVerifyOutcome = ({
|
|
491
|
+
readonly found: true;
|
|
492
|
+
} & SignoffVerification) | SignoffLookupFailure;
|
|
493
|
+
/** Verify by revision: find the proof attached to that SHA (or to its content), then check it. */
|
|
494
|
+
declare function verifySignoffAtRev(input: VerifySignoffAtRevInput): SignoffVerifyOutcome;
|
|
495
|
+
interface VerifySignoffFileInput {
|
|
496
|
+
readonly repoDir: string;
|
|
497
|
+
readonly file: string;
|
|
498
|
+
/**
|
|
499
|
+
* The commit to check the file against. REQUIRED, and deliberately not
|
|
500
|
+
* defaulted to the commit the proof names: a proof checked against its own
|
|
501
|
+
* subject can never fail the commit binding, which is a check that reads as
|
|
502
|
+
* strong and cannot catch anything. The reader always states what they are
|
|
503
|
+
* asking about.
|
|
504
|
+
*/
|
|
505
|
+
readonly rev: string;
|
|
506
|
+
readonly key?: Uint8Array;
|
|
507
|
+
readonly requiredSteps?: readonly string[];
|
|
508
|
+
readonly expectRepo?: string;
|
|
509
|
+
}
|
|
510
|
+
/** Verify a proof document on disk. The repo is still required — "matches the tree it claims" is not answerable without it. */
|
|
511
|
+
declare function verifySignoffProofFile(input: VerifySignoffFileInput): SignoffVerification;
|
|
512
|
+
/** One line per failure, prefixed by its code, in the order the checks ran. */
|
|
513
|
+
declare function formatSignoffVerification(result: SignoffVerification): string;
|
|
514
|
+
|
|
515
|
+
export { type AttachSignoffProofInput, type AttachedSignoffProof, type BuildSignoffProofInput, type CommitFacts, type GitResult, type IsAncestorFn, NOT_EXECUTED_COMMAND, NO_EXIT_CODE, type ProofFromReportInput, SIGNOFF_NOTES_GIT_CONFIG, SIGNOFF_NOTES_REF, SIGNOFF_PROOF_VERSION, SIGNOFF_REQUIRED_STEPS, SIGNOFF_STEP_STATUSES, type SignoffCommitBinding, type SignoffFailure, type SignoffFailureCode, SignoffGitError, type SignoffLookupFailure, type SignoffProof, type SignoffProofBody, type SignoffProofLookup, type SignoffProofPeer, type SignoffProofSeal, type SignoffProofStep, type SignoffProofSubject, type SignoffVerification, type SignoffVerifyOutcome, type ToolingFactsInput, type VerifySignoffAtRevInput, type VerifySignoffFileInput, type VerifySignoffProofOptions, attachSignoffProof, buildSignoffProof, canonicalJson, canonicalizeProofBody, collectToolingFacts, computeWorktreeTree, formatSignoffSummary, formatSignoffVerification, gitIsAncestor, gitText, hashProofBody, hashStepOutput, listSignoffProofs, macMatches, parseSignoffProof, proofFromSignoffReport, readCommitFacts, readInstalledVersion, readSignoffKey, readSignoffProofNote, requiredStepsFor, resolveCommit, resolveSignoffProof, runGit, sealProof, seedsFromReport, serializeSignoffProof, signoffKeyId, signoffProofBodySchema, signoffProofSchema, signoffProofSealSchema, signoffProofStepSchema, tangleDependencyNames, verifySignoffAtRev, verifySignoffProof, verifySignoffProofFile };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SIGNOFF_NOTES_GIT_CONFIG,
|
|
3
|
+
SIGNOFF_NOTES_REF,
|
|
4
|
+
SIGNOFF_PROOF_VERSION,
|
|
5
|
+
SIGNOFF_REQUIRED_STEPS,
|
|
6
|
+
SIGNOFF_STEP_STATUSES,
|
|
7
|
+
SignoffGitError,
|
|
8
|
+
attachSignoffProof,
|
|
9
|
+
buildSignoffProof,
|
|
10
|
+
canonicalJson,
|
|
11
|
+
canonicalizeProofBody,
|
|
12
|
+
collectToolingFacts,
|
|
13
|
+
computeWorktreeTree,
|
|
14
|
+
formatSignoffSummary,
|
|
15
|
+
formatSignoffVerification,
|
|
16
|
+
gitIsAncestor,
|
|
17
|
+
gitText,
|
|
18
|
+
hashProofBody,
|
|
19
|
+
hashStepOutput,
|
|
20
|
+
listSignoffProofs,
|
|
21
|
+
macMatches,
|
|
22
|
+
parseSignoffProof,
|
|
23
|
+
readCommitFacts,
|
|
24
|
+
readInstalledVersion,
|
|
25
|
+
readSignoffKey,
|
|
26
|
+
readSignoffProofNote,
|
|
27
|
+
requiredStepsFor,
|
|
28
|
+
resolveCommit,
|
|
29
|
+
resolveSignoffProof,
|
|
30
|
+
runGit,
|
|
31
|
+
sealProof,
|
|
32
|
+
serializeSignoffProof,
|
|
33
|
+
signoffKeyId,
|
|
34
|
+
signoffProofBodySchema,
|
|
35
|
+
signoffProofSchema,
|
|
36
|
+
signoffProofSealSchema,
|
|
37
|
+
signoffProofStepSchema,
|
|
38
|
+
tangleDependencyNames,
|
|
39
|
+
verifySignoffAtRev,
|
|
40
|
+
verifySignoffProof,
|
|
41
|
+
verifySignoffProofFile
|
|
42
|
+
} from "../chunk-N3G3FSM4.js";
|
|
43
|
+
|
|
44
|
+
// src/signoff/proof-from-report.ts
|
|
45
|
+
var NO_EXIT_CODE = -1;
|
|
46
|
+
var NOT_EXECUTED_COMMAND = "(never executed)";
|
|
47
|
+
function stepFromResult(result, startedAtEpochMs) {
|
|
48
|
+
const attempts = result.attempts;
|
|
49
|
+
const worst = attempts.reduce((worstSoFar, attempt) => attempt.exitCode !== 0 ? attempt.exitCode : worstSoFar, 0);
|
|
50
|
+
return {
|
|
51
|
+
id: result.name,
|
|
52
|
+
command: attempts[0]?.command ?? NOT_EXECUTED_COMMAND,
|
|
53
|
+
cwd: ".",
|
|
54
|
+
status: result.status,
|
|
55
|
+
exitCode: attempts.length === 0 ? NO_EXIT_CODE : worst,
|
|
56
|
+
durationMs: Math.max(0, Math.round(result.durationMs)),
|
|
57
|
+
startedAt: new Date(startedAtEpochMs + (result.startedAtMs ?? 0)).toISOString(),
|
|
58
|
+
// Every attempt's output, in order — a shuffled step that failed on the
|
|
59
|
+
// second seed must not digest to the same value as one that passed twice.
|
|
60
|
+
outputSha256: hashStepOutput(attempts.map((attempt) => attempt.output).join("\n"))
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function seedsFromReport(report) {
|
|
64
|
+
const seeds = { base: report.seedBase };
|
|
65
|
+
for (const step of report.steps) {
|
|
66
|
+
step.attempts.forEach((attempt, index) => {
|
|
67
|
+
if (attempt.seed !== null) seeds[`${step.name}#${index}`] = attempt.seed;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return seeds;
|
|
71
|
+
}
|
|
72
|
+
function proofFromSignoffReport(input) {
|
|
73
|
+
const { report } = input;
|
|
74
|
+
const repoDir = input.repoDir ?? report.repo.root;
|
|
75
|
+
const facts = readCommitFacts(repoDir, "HEAD");
|
|
76
|
+
if (facts.commit !== report.repo.head) {
|
|
77
|
+
throw new Error(`HEAD moved during the sign-off: the run verified ${report.repo.head}, ${repoDir} is now at ${facts.commit}`);
|
|
78
|
+
}
|
|
79
|
+
const startedAtEpochMs = Date.parse(report.startedAt);
|
|
80
|
+
const install = {
|
|
81
|
+
id: "install",
|
|
82
|
+
command: report.install.command,
|
|
83
|
+
cwd: report.workspace,
|
|
84
|
+
status: report.install.exitCode === 0 ? "passed" : "failed",
|
|
85
|
+
exitCode: report.install.exitCode,
|
|
86
|
+
durationMs: Math.max(0, Math.round(report.install.durationMs)),
|
|
87
|
+
startedAt: new Date(startedAtEpochMs).toISOString(),
|
|
88
|
+
outputSha256: hashStepOutput(report.install.output)
|
|
89
|
+
};
|
|
90
|
+
const steps = [install, ...report.steps.map((step) => stepFromResult(step, startedAtEpochMs))];
|
|
91
|
+
return buildSignoffProof({
|
|
92
|
+
repoDir,
|
|
93
|
+
repo: input.repo,
|
|
94
|
+
steps,
|
|
95
|
+
wallClockMs: report.wallClockMs,
|
|
96
|
+
declaredRequired: input.declaredRequired ?? steps.map((step) => step.id),
|
|
97
|
+
seeds: seedsFromReport(report),
|
|
98
|
+
key: input.key,
|
|
99
|
+
now: input.now
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
NOT_EXECUTED_COMMAND,
|
|
104
|
+
NO_EXIT_CODE,
|
|
105
|
+
SIGNOFF_NOTES_GIT_CONFIG,
|
|
106
|
+
SIGNOFF_NOTES_REF,
|
|
107
|
+
SIGNOFF_PROOF_VERSION,
|
|
108
|
+
SIGNOFF_REQUIRED_STEPS,
|
|
109
|
+
SIGNOFF_STEP_STATUSES,
|
|
110
|
+
SignoffGitError,
|
|
111
|
+
attachSignoffProof,
|
|
112
|
+
buildSignoffProof,
|
|
113
|
+
canonicalJson,
|
|
114
|
+
canonicalizeProofBody,
|
|
115
|
+
collectToolingFacts,
|
|
116
|
+
computeWorktreeTree,
|
|
117
|
+
formatSignoffSummary,
|
|
118
|
+
formatSignoffVerification,
|
|
119
|
+
gitIsAncestor,
|
|
120
|
+
gitText,
|
|
121
|
+
hashProofBody,
|
|
122
|
+
hashStepOutput,
|
|
123
|
+
listSignoffProofs,
|
|
124
|
+
macMatches,
|
|
125
|
+
parseSignoffProof,
|
|
126
|
+
proofFromSignoffReport,
|
|
127
|
+
readCommitFacts,
|
|
128
|
+
readInstalledVersion,
|
|
129
|
+
readSignoffKey,
|
|
130
|
+
readSignoffProofNote,
|
|
131
|
+
requiredStepsFor,
|
|
132
|
+
resolveCommit,
|
|
133
|
+
resolveSignoffProof,
|
|
134
|
+
runGit,
|
|
135
|
+
sealProof,
|
|
136
|
+
seedsFromReport,
|
|
137
|
+
serializeSignoffProof,
|
|
138
|
+
signoffKeyId,
|
|
139
|
+
signoffProofBodySchema,
|
|
140
|
+
signoffProofSchema,
|
|
141
|
+
signoffProofSealSchema,
|
|
142
|
+
signoffProofStepSchema,
|
|
143
|
+
tangleDependencyNames,
|
|
144
|
+
verifySignoffAtRev,
|
|
145
|
+
verifySignoffProof,
|
|
146
|
+
verifySignoffProofFile
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=proof.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/signoff/proof-from-report.ts"],"sourcesContent":["/**\n * The join between the runner and the proof: one sign-off run produces one\n * report, and this turns that report into the signed, attachable record.\n *\n * Nothing here re-runs or re-judges anything. The runner owns what happened; the\n * proof owns what is provable about it later. Keeping the two in one direction\n * is what stops a second, drifting idea of \"which steps ran\" from appearing.\n */\nimport { hashStepOutput, buildSignoffProof, type SignoffProof, type SignoffProofStep } from './proof-record'\nimport { readCommitFacts } from './proof-git'\nimport type { SignoffReport, SignoffStepResult } from './types'\n\n/**\n * A step that never executed has no exit code, so it is recorded as one no\n * process can produce. `status` is what a reader and the verifier judge on;\n * this exists so the field is never a plausible-looking `0`.\n */\nexport const NO_EXIT_CODE = -1\n\n/** The `command` of a step that never executed. A skipped step had no command line. */\nexport const NOT_EXECUTED_COMMAND = '(never executed)'\n\nexport interface ProofFromReportInput {\n readonly report: SignoffReport\n /** Repo identity the verifier's required-step table is keyed on. */\n readonly repo: string\n /** Source repository. Defaults to the root the run recorded. */\n readonly repoDir?: string\n /** Step ids the run treated as required. Defaults to every step the report carries. */\n readonly declaredRequired?: readonly string[]\n readonly key?: Uint8Array\n readonly now?: Date\n}\n\nfunction stepFromResult(result: SignoffStepResult, startedAtEpochMs: number): SignoffProofStep {\n const attempts = result.attempts\n const worst = attempts.reduce<number>((worstSoFar, attempt) => (attempt.exitCode !== 0 ? attempt.exitCode : worstSoFar), 0)\n return {\n id: result.name,\n command: attempts[0]?.command ?? NOT_EXECUTED_COMMAND,\n cwd: '.',\n status: result.status,\n exitCode: attempts.length === 0 ? NO_EXIT_CODE : worst,\n durationMs: Math.max(0, Math.round(result.durationMs)),\n startedAt: new Date(startedAtEpochMs + (result.startedAtMs ?? 0)).toISOString(),\n // Every attempt's output, in order — a shuffled step that failed on the\n // second seed must not digest to the same value as one that passed twice.\n outputSha256: hashStepOutput(attempts.map((attempt) => attempt.output).join('\\n')),\n }\n}\n\n/** Every seed the run used, keyed so a reader can replay one step rather than the whole run. */\nexport function seedsFromReport(report: SignoffReport): Record<string, string | number> {\n const seeds: Record<string, string | number> = { base: report.seedBase }\n for (const step of report.steps) {\n step.attempts.forEach((attempt, index) => {\n if (attempt.seed !== null) seeds[`${step.name}#${index}`] = attempt.seed\n })\n }\n return seeds\n}\n\nexport function proofFromSignoffReport(input: ProofFromReportInput): SignoffProof {\n const { report } = input\n const repoDir = input.repoDir ?? report.repo.root\n\n // HEAD moving during a run makes every later claim ambiguous — the checks ran\n // over one commit and the proof would name another. Refused, not reconciled.\n const facts = readCommitFacts(repoDir, 'HEAD')\n if (facts.commit !== report.repo.head) {\n throw new Error(`HEAD moved during the sign-off: the run verified ${report.repo.head}, ${repoDir} is now at ${facts.commit}`)\n }\n\n const startedAtEpochMs = Date.parse(report.startedAt)\n const install: SignoffProofStep = {\n id: 'install',\n command: report.install.command,\n cwd: report.workspace,\n status: report.install.exitCode === 0 ? 'passed' : 'failed',\n exitCode: report.install.exitCode,\n durationMs: Math.max(0, Math.round(report.install.durationMs)),\n startedAt: new Date(startedAtEpochMs).toISOString(),\n outputSha256: hashStepOutput(report.install.output),\n }\n const steps = [install, ...report.steps.map((step) => stepFromResult(step, startedAtEpochMs))]\n\n return buildSignoffProof({\n repoDir,\n repo: input.repo,\n steps,\n wallClockMs: report.wallClockMs,\n declaredRequired: input.declaredRequired ?? steps.map((step) => step.id),\n seeds: seedsFromReport(report),\n key: input.key,\n now: input.now,\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBO,IAAM,eAAe;AAGrB,IAAM,uBAAuB;AAcpC,SAAS,eAAe,QAA2B,kBAA4C;AAC7F,QAAM,WAAW,OAAO;AACxB,QAAM,QAAQ,SAAS,OAAe,CAAC,YAAY,YAAa,QAAQ,aAAa,IAAI,QAAQ,WAAW,YAAa,CAAC;AAC1H,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,SAAS,SAAS,CAAC,GAAG,WAAW;AAAA,IACjC,KAAK;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,UAAU,SAAS,WAAW,IAAI,eAAe;AAAA,IACjD,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,UAAU,CAAC;AAAA,IACrD,WAAW,IAAI,KAAK,oBAAoB,OAAO,eAAe,EAAE,EAAE,YAAY;AAAA;AAAA;AAAA,IAG9E,cAAc,eAAe,SAAS,IAAI,CAAC,YAAY,QAAQ,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA,EACnF;AACF;AAGO,SAAS,gBAAgB,QAAwD;AACtF,QAAM,QAAyC,EAAE,MAAM,OAAO,SAAS;AACvE,aAAW,QAAQ,OAAO,OAAO;AAC/B,SAAK,SAAS,QAAQ,CAAC,SAAS,UAAU;AACxC,UAAI,QAAQ,SAAS,KAAM,OAAM,GAAG,KAAK,IAAI,IAAI,KAAK,EAAE,IAAI,QAAQ;AAAA,IACtE,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEO,SAAS,uBAAuB,OAA2C;AAChF,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,UAAU,MAAM,WAAW,OAAO,KAAK;AAI7C,QAAM,QAAQ,gBAAgB,SAAS,MAAM;AAC7C,MAAI,MAAM,WAAW,OAAO,KAAK,MAAM;AACrC,UAAM,IAAI,MAAM,oDAAoD,OAAO,KAAK,IAAI,KAAK,OAAO,cAAc,MAAM,MAAM,EAAE;AAAA,EAC9H;AAEA,QAAM,mBAAmB,KAAK,MAAM,OAAO,SAAS;AACpD,QAAM,UAA4B;AAAA,IAChC,IAAI;AAAA,IACJ,SAAS,OAAO,QAAQ;AAAA,IACxB,KAAK,OAAO;AAAA,IACZ,QAAQ,OAAO,QAAQ,aAAa,IAAI,WAAW;AAAA,IACnD,UAAU,OAAO,QAAQ;AAAA,IACzB,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,QAAQ,UAAU,CAAC;AAAA,IAC7D,WAAW,IAAI,KAAK,gBAAgB,EAAE,YAAY;AAAA,IAClD,cAAc,eAAe,OAAO,QAAQ,MAAM;AAAA,EACpD;AACA,QAAM,QAAQ,CAAC,SAAS,GAAG,OAAO,MAAM,IAAI,CAAC,SAAS,eAAe,MAAM,gBAAgB,CAAC,CAAC;AAE7F,SAAO,kBAAkB;AAAA,IACvB;AAAA,IACA,MAAM,MAAM;AAAA,IACZ;AAAA,IACA,aAAa,OAAO;AAAA,IACpB,kBAAkB,MAAM,oBAAoB,MAAM,IAAI,CAAC,SAAS,KAAK,EAAE;AAAA,IACvE,OAAO,gBAAgB,MAAM;AAAA,IAC7B,KAAK,MAAM;AAAA,IACX,KAAK,MAAM;AAAA,EACb,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|