@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.
Files changed (44) hide show
  1. package/dist/assistant/index.js +5 -5
  2. package/dist/chat-routes/index.js +21 -21
  3. package/dist/chat-store/index.js +2 -2
  4. package/dist/{chunk-B2UEW62O.js → chunk-5ZTFZBS6.js} +4 -4
  5. package/dist/chunk-N3G3FSM4.js +509 -0
  6. package/dist/chunk-N3G3FSM4.js.map +1 -0
  7. package/dist/chunk-NEJ7OMQS.js +351 -0
  8. package/dist/chunk-NEJ7OMQS.js.map +1 -0
  9. package/dist/{chunk-KOMP6NDW.js → chunk-ODYE4A7L.js} +6 -6
  10. package/dist/{chunk-7V2I3JGZ.js → chunk-OTLEYHOG.js} +50 -12
  11. package/dist/chunk-OTLEYHOG.js.map +1 -0
  12. package/dist/chunk-WD2B6HGY.js +1046 -0
  13. package/dist/chunk-WD2B6HGY.js.map +1 -0
  14. package/dist/design-canvas-react/index.js +4 -4
  15. package/dist/run-tEsZUhAf.d.ts +40 -0
  16. package/dist/sandbox/index.d.ts +53 -1
  17. package/dist/sandbox/index.js +1 -1
  18. package/dist/signoff/cli.d.ts +12 -0
  19. package/dist/signoff/cli.js +166 -0
  20. package/dist/signoff/cli.js.map +1 -0
  21. package/dist/signoff/index.d.ts +430 -0
  22. package/dist/signoff/index.js +63 -0
  23. package/dist/signoff/index.js.map +1 -0
  24. package/dist/signoff/proof-cli.d.ts +1 -0
  25. package/dist/signoff/proof-cli.js +92 -0
  26. package/dist/signoff/proof-cli.js.map +1 -0
  27. package/dist/signoff/proof.d.ts +515 -0
  28. package/dist/signoff/proof.js +148 -0
  29. package/dist/signoff/proof.js.map +1 -0
  30. package/dist/spend/cli.d.ts +1 -0
  31. package/dist/spend/cli.js +119 -0
  32. package/dist/spend/cli.js.map +1 -0
  33. package/dist/spend/index.d.ts +640 -0
  34. package/dist/spend/index.js +196 -0
  35. package/dist/spend/index.js.map +1 -0
  36. package/dist/stream/index.js +14 -14
  37. package/dist/teams/index.js +9 -9
  38. package/dist/teams/invitations-api.js +7 -7
  39. package/dist/types-U7Nz-txa.d.ts +233 -0
  40. package/dist/web-react/index.js +11 -11
  41. package/package.json +22 -3
  42. package/dist/chunk-7V2I3JGZ.js.map +0 -1
  43. /package/dist/{chunk-B2UEW62O.js.map → chunk-5ZTFZBS6.js.map} +0 -0
  44. /package/dist/{chunk-KOMP6NDW.js.map → chunk-ODYE4A7L.js.map} +0 -0
@@ -6,18 +6,18 @@ import {
6
6
  ChatMessages,
7
7
  ModelPicker,
8
8
  ProviderLogo
9
- } from "../chunk-KOMP6NDW.js";
9
+ } from "../chunk-ODYE4A7L.js";
10
10
  import "../chunk-FBVLEGEG.js";
11
+ import "../chunk-HZ4WCA37.js";
12
+ import "../chunk-GEYACSFW.js";
11
13
  import "../chunk-E4DHYENH.js";
12
14
  import "../chunk-BATKJP3P.js";
13
15
  import "../chunk-PC2WYTK7.js";
14
- import "../chunk-HZ4WCA37.js";
15
- import "../chunk-GEYACSFW.js";
16
16
  import "../chunk-QY4BRKRJ.js";
17
17
  import "../chunk-CCVG2TL6.js";
18
- import "../chunk-B2UEW62O.js";
19
- import "../chunk-KWXUBMXU.js";
18
+ import "../chunk-5ZTFZBS6.js";
20
19
  import "../chunk-ZVEEWGDK.js";
20
+ import "../chunk-KWXUBMXU.js";
21
21
  import "../chunk-XZ27ENOZ.js";
22
22
  import "../chunk-M3K2HVQD.js";
23
23
  import "../chunk-YJMCRXQQ.js";
@@ -1,3 +1,6 @@
1
+ import {
2
+ createInteractionAnswerRoute
3
+ } from "../chunk-ZSSCVT6H.js";
1
4
  import {
2
5
  parseJsonObjectBody
3
6
  } from "../chunk-F7VH4G5C.js";
@@ -25,30 +28,13 @@ import {
25
28
  sanitizeAttachmentFileName,
26
29
  sniffBinary
27
30
  } from "../chunk-CCVG2TL6.js";
28
- import {
29
- normalizeClientTurnId,
30
- resolveChatTurn
31
- } from "../chunk-JEZJ6HTF.js";
32
- import {
33
- DEFAULT_STALE_TURN_LOCK_GRACE_MS,
34
- DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS,
35
- reconcileStaleTurnLock
36
- } from "../chunk-CCVWR33L.js";
37
- import {
38
- coalesceDeltas,
39
- createBufferedTurnTap,
40
- replayTurnEvents,
41
- stampReplaySeq
42
- } from "../chunk-SDEADFHZ.js";
43
- import {
44
- createInteractionAnswerRoute
45
- } from "../chunk-ZSSCVT6H.js";
46
31
  import {
47
32
  attachmentInputToPart,
48
33
  attachmentKindForMime,
49
34
  mentionInputToPart,
50
35
  toChatMessageParts
51
- } from "../chunk-B2UEW62O.js";
36
+ } from "../chunk-5ZTFZBS6.js";
37
+ import "../chunk-ZVEEWGDK.js";
52
38
  import {
53
39
  ChatTurnInputError,
54
40
  DISPATCH_MAX_MEDIA_PARTS,
@@ -70,7 +56,10 @@ import {
70
56
  promptPartsByteSize,
71
57
  validateSandboxMentionPath
72
58
  } from "../chunk-KWXUBMXU.js";
73
- import "../chunk-ZVEEWGDK.js";
59
+ import {
60
+ normalizeClientTurnId,
61
+ resolveChatTurn
62
+ } from "../chunk-JEZJ6HTF.js";
74
63
  import {
75
64
  asRecord,
76
65
  asString,
@@ -98,6 +87,17 @@ import {
98
87
  parsePlanSubmittedEvent,
99
88
  planToPersistedPart
100
89
  } from "../chunk-YJMCRXQQ.js";
90
+ import {
91
+ DEFAULT_STALE_TURN_LOCK_GRACE_MS,
92
+ DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS,
93
+ reconcileStaleTurnLock
94
+ } from "../chunk-CCVWR33L.js";
95
+ import {
96
+ coalesceDeltas,
97
+ createBufferedTurnTap,
98
+ replayTurnEvents,
99
+ stampReplaySeq
100
+ } from "../chunk-SDEADFHZ.js";
101
101
  import {
102
102
  ModelFailoverExhaustedError,
103
103
  buildModelChain,
@@ -109,7 +109,7 @@ import {
109
109
  flattenHistory,
110
110
  readSandboxBinaryBytes,
111
111
  statSandboxFileSize
112
- } from "../chunk-7V2I3JGZ.js";
112
+ } from "../chunk-OTLEYHOG.js";
113
113
  import "../chunk-TH7L265V.js";
114
114
  import "../chunk-LWSJK546.js";
115
115
  import "../chunk-P5PIAQVS.js";
@@ -19,9 +19,9 @@ import {
19
19
  mentionInputToPart,
20
20
  mentionPartsFromMessageParts,
21
21
  toChatMessageParts
22
- } from "../chunk-B2UEW62O.js";
23
- import "../chunk-KWXUBMXU.js";
22
+ } from "../chunk-5ZTFZBS6.js";
24
23
  import "../chunk-ZVEEWGDK.js";
24
+ import "../chunk-KWXUBMXU.js";
25
25
  import {
26
26
  attachmentPartKey
27
27
  } from "../chunk-XZ27ENOZ.js";
@@ -1,9 +1,9 @@
1
- import {
2
- mentionKindForPath
3
- } from "./chunk-KWXUBMXU.js";
4
1
  import {
5
2
  persistedPartToWorkProduct
6
3
  } from "./chunk-ZVEEWGDK.js";
4
+ import {
5
+ mentionKindForPath
6
+ } from "./chunk-KWXUBMXU.js";
7
7
  import {
8
8
  persistedPartToInteraction
9
9
  } from "./chunk-M3K2HVQD.js";
@@ -155,4 +155,4 @@ export {
155
155
  buildAttachmentPromptBlock,
156
156
  historyContentWithAttachments
157
157
  };
158
- //# sourceMappingURL=chunk-B2UEW62O.js.map
158
+ //# sourceMappingURL=chunk-5ZTFZBS6.js.map
@@ -0,0 +1,509 @@
1
+ // src/signoff/proof-git.ts
2
+ import { spawnSync } from "child_process";
3
+ import { mkdtempSync, rmSync } from "fs";
4
+ import { tmpdir } from "os";
5
+ import { join } from "path";
6
+ var SignoffGitError = class extends Error {
7
+ args;
8
+ status;
9
+ stderr;
10
+ constructor(args, status, stderr) {
11
+ super(`git ${args.join(" ")} exited ${status ?? "null"}: ${stderr.trim()}`);
12
+ this.name = "SignoffGitError";
13
+ this.args = args;
14
+ this.status = status;
15
+ this.stderr = stderr;
16
+ }
17
+ };
18
+ function runGit(repoDir, args, options = {}) {
19
+ const result = spawnSync("git", [...args], {
20
+ cwd: repoDir,
21
+ encoding: "utf8",
22
+ input: options.input,
23
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", ...options.env },
24
+ maxBuffer: 128 * 1024 * 1024
25
+ });
26
+ if (result.error) throw new Error(`git ${args.join(" ")} could not run: ${result.error.message}`);
27
+ return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
28
+ }
29
+ function gitText(repoDir, args, options = {}) {
30
+ const result = runGit(repoDir, args, options);
31
+ if (result.status !== 0) throw new SignoffGitError(args, result.status, result.stderr);
32
+ return result.stdout.replace(/\n$/, "");
33
+ }
34
+ function gitIsAncestor(repoDir) {
35
+ return (ancestor, descendant) => {
36
+ const result = runGit(repoDir, ["merge-base", "--is-ancestor", ancestor, descendant]);
37
+ if (result.status === 0) return true;
38
+ if (result.status === 1) return false;
39
+ throw new SignoffGitError(["merge-base", "--is-ancestor", ancestor, descendant], result.status, result.stderr);
40
+ };
41
+ }
42
+ function resolveCommit(repoDir, rev) {
43
+ return gitText(repoDir, ["rev-parse", "--verify", `${rev}^{commit}`]);
44
+ }
45
+ function readCommitFacts(repoDir, rev) {
46
+ const commit = resolveCommit(repoDir, rev);
47
+ const record = gitText(repoDir, ["show", "--no-patch", "--format=%T%n%P%n%cI", commit]);
48
+ const [tree, parents, committedAt] = record.split("\n");
49
+ if (tree === void 0 || parents === void 0 || committedAt === void 0) {
50
+ throw new Error(`git show returned an unreadable record for ${commit}: ${JSON.stringify(record)}`);
51
+ }
52
+ return {
53
+ commit,
54
+ commitTree: tree,
55
+ parents: parents.length === 0 ? [] : parents.split(" "),
56
+ committedAt: new Date(committedAt).toISOString()
57
+ };
58
+ }
59
+ function computeWorktreeTree(repoDir) {
60
+ const scratch = mkdtempSync(join(tmpdir(), "agent-app-signoff-index-"));
61
+ const indexFile = join(scratch, "index");
62
+ try {
63
+ const env = { GIT_INDEX_FILE: indexFile };
64
+ gitText(repoDir, ["add", "-A", "--"], { env });
65
+ return gitText(repoDir, ["write-tree"], { env });
66
+ } finally {
67
+ rmSync(scratch, { recursive: true, force: true });
68
+ }
69
+ }
70
+
71
+ // src/signoff/proof-record.ts
72
+ import { spawnSync as spawnSync2 } from "child_process";
73
+ import { createHash, createHmac, timingSafeEqual } from "crypto";
74
+ import { readFileSync } from "fs";
75
+ import { hostname, userInfo } from "os";
76
+ import { join as join2 } from "path";
77
+ import { z } from "zod";
78
+ var SIGNOFF_PROOF_VERSION = 1;
79
+ var isoString = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp");
80
+ var sha1Hex = z.string().regex(/^[0-9a-f]{40}$/, "must be a 40-hex git object id");
81
+ var sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, "must be a 64-hex sha256 digest");
82
+ var SIGNOFF_STEP_STATUSES = ["passed", "failed", "skipped", "cancelled", "blocked"];
83
+ var signoffProofStepSchema = z.object({
84
+ /** Stable id a repo's required-step table refers to (`typecheck`, `test`, `knip`, …). */
85
+ id: z.string().min(1),
86
+ command: z.string().min(1),
87
+ cwd: z.string().min(1),
88
+ /** How the runner judged the step. Only `passed` can satisfy a requirement. */
89
+ status: z.enum(SIGNOFF_STEP_STATUSES),
90
+ exitCode: z.number().int(),
91
+ durationMs: z.number().int().nonnegative(),
92
+ startedAt: isoString,
93
+ /** sha256 of the step's combined stdout+stderr. Logs are not carried; the digest is. */
94
+ outputSha256: sha256Hex
95
+ });
96
+ var signoffProofPeerSchema = z.object({
97
+ name: z.string().min(1),
98
+ /** `null` when the package is not resolvable on disk — recorded, never guessed. */
99
+ version: z.string().min(1).nullable()
100
+ });
101
+ var signoffProofSubjectSchema = z.object({
102
+ repo: z.string().min(1),
103
+ commit: sha1Hex,
104
+ /** Tree the checks actually ran against, including uncommitted work. */
105
+ tree: sha1Hex,
106
+ /** Tree the commit itself carries. Equal to `tree` on a clean sign-off. */
107
+ commitTree: sha1Hex,
108
+ parents: z.array(sha1Hex),
109
+ committedAt: isoString
110
+ });
111
+ var signoffProofBodySchema = z.object({
112
+ proofVersion: z.number().int().positive(),
113
+ subject: signoffProofSubjectSchema,
114
+ signedAt: isoString,
115
+ host: z.object({
116
+ hostname: z.string().min(1),
117
+ platform: z.string().min(1),
118
+ arch: z.string().min(1),
119
+ user: z.string().min(1)
120
+ }),
121
+ tooling: z.object({
122
+ node: z.string().min(1),
123
+ pnpm: z.string().min(1).nullable(),
124
+ peers: z.array(signoffProofPeerSchema)
125
+ }),
126
+ /**
127
+ * Real elapsed time for the whole run. Recorded separately from the steps
128
+ * because the runner schedules them as wide as their dependencies allow, so
129
+ * the sum of step durations is the SERIAL cost and would overstate this.
130
+ */
131
+ wallClockMs: z.number().int().nonnegative(),
132
+ /** Seeds fed to anything non-deterministic, so a reader can reproduce the same run. */
133
+ seeds: z.record(z.string(), z.union([z.string(), z.number()])),
134
+ /** The step ids this run claims were required. The verifier holds the authoritative table. */
135
+ declaredRequired: z.array(z.string().min(1)),
136
+ steps: z.array(signoffProofStepSchema),
137
+ verdict: z.enum(["pass", "fail"])
138
+ });
139
+ var signoffProofSealSchema = z.object({
140
+ algorithm: z.enum(["sha256", "hmac-sha256"]),
141
+ /** sha256 over the canonical body. Chains the seal to every field, including the commit and tree. */
142
+ bodySha256: sha256Hex,
143
+ /** First 12 hex of sha256(key), so a reader can tell WHICH key sealed this. */
144
+ keyId: z.string().regex(/^[0-9a-f]{12}$/).nullable(),
145
+ mac: sha256Hex.nullable()
146
+ });
147
+ var signoffProofSchema = z.object({
148
+ body: signoffProofBodySchema,
149
+ seal: signoffProofSealSchema
150
+ });
151
+ function canonicalJson(value) {
152
+ if (value === null) return "null";
153
+ if (typeof value === "string") return JSON.stringify(value);
154
+ if (typeof value === "boolean") return value ? "true" : "false";
155
+ if (typeof value === "number") {
156
+ if (!Number.isFinite(value)) throw new Error(`canonicalJson: ${String(value)} is not representable`);
157
+ return JSON.stringify(value);
158
+ }
159
+ if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
160
+ const record = value;
161
+ const keys = Object.keys(record).sort();
162
+ const fields = keys.map((key) => {
163
+ const entry = record[key];
164
+ if (entry === void 0) throw new Error(`canonicalJson: field ${JSON.stringify(key)} is undefined`);
165
+ return `${JSON.stringify(key)}:${canonicalJson(entry)}`;
166
+ });
167
+ return `{${fields.join(",")}}`;
168
+ }
169
+ function canonicalizeProofBody(body) {
170
+ return canonicalJson(body);
171
+ }
172
+ function hashProofBody(body) {
173
+ return createHash("sha256").update(canonicalizeProofBody(body), "utf8").digest("hex");
174
+ }
175
+ function hashStepOutput(output) {
176
+ return createHash("sha256").update(output, "utf8").digest("hex");
177
+ }
178
+ function signoffKeyId(key) {
179
+ return createHash("sha256").update(key).digest("hex").slice(0, 12);
180
+ }
181
+ function readSignoffKey(path) {
182
+ const raw = readFileSync(path);
183
+ if (raw.byteLength < 16) throw new Error(`sign-off key at ${path} is ${raw.byteLength} bytes; at least 16 are required`);
184
+ return new Uint8Array(raw);
185
+ }
186
+ function sealProof(body, key) {
187
+ const canonical = canonicalizeProofBody(body);
188
+ const bodySha256 = createHash("sha256").update(canonical, "utf8").digest("hex");
189
+ if (key === void 0) {
190
+ return { body, seal: { algorithm: "sha256", bodySha256, keyId: null, mac: null } };
191
+ }
192
+ return {
193
+ body,
194
+ seal: {
195
+ algorithm: "hmac-sha256",
196
+ bodySha256,
197
+ keyId: signoffKeyId(key),
198
+ mac: createHmac("sha256", key).update(canonical, "utf8").digest("hex")
199
+ }
200
+ };
201
+ }
202
+ function macMatches(expected, actual) {
203
+ if (expected.length !== actual.length) return false;
204
+ return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(actual, "hex"));
205
+ }
206
+ function collectToolingFacts(input) {
207
+ return {
208
+ node: process.version,
209
+ pnpm: readPnpmVersion(input.repoDir),
210
+ peers: input.peerNames.map((name) => ({ name, version: readInstalledVersion(input.repoDir, name) }))
211
+ };
212
+ }
213
+ function readPnpmVersion(repoDir) {
214
+ const result = spawnSync2("pnpm", ["--version"], { cwd: repoDir, encoding: "utf8" });
215
+ if (result.error || result.status !== 0) return null;
216
+ return result.stdout.trim();
217
+ }
218
+ function readInstalledVersion(repoDir, packageName) {
219
+ try {
220
+ const manifest = JSON.parse(readFileSync(join2(repoDir, "node_modules", packageName, "package.json"), "utf8"));
221
+ return typeof manifest.version === "string" ? manifest.version : null;
222
+ } catch {
223
+ return null;
224
+ }
225
+ }
226
+ function tangleDependencyNames(repoDir) {
227
+ const manifest = JSON.parse(readFileSync(join2(repoDir, "package.json"), "utf8"));
228
+ const names = /* @__PURE__ */ new Set();
229
+ for (const block of [manifest.dependencies, manifest.peerDependencies, manifest.devDependencies]) {
230
+ for (const name of Object.keys(block ?? {})) {
231
+ if (name.startsWith("@tangle-network/")) names.add(name);
232
+ }
233
+ }
234
+ return [...names].sort();
235
+ }
236
+ function buildSignoffProof(input) {
237
+ const facts = readCommitFacts(input.repoDir, input.rev ?? "HEAD");
238
+ const body = {
239
+ proofVersion: SIGNOFF_PROOF_VERSION,
240
+ subject: {
241
+ repo: input.repo,
242
+ commit: facts.commit,
243
+ tree: computeWorktreeTree(input.repoDir),
244
+ commitTree: facts.commitTree,
245
+ parents: [...facts.parents],
246
+ committedAt: facts.committedAt
247
+ },
248
+ signedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
249
+ wallClockMs: Math.max(0, Math.round(input.wallClockMs)),
250
+ host: { hostname: hostname(), platform: process.platform, arch: process.arch, user: userInfo().username },
251
+ tooling: collectToolingFacts({ repoDir: input.repoDir, peerNames: input.peerNames ?? tangleDependencyNames(input.repoDir) }),
252
+ seeds: { ...input.seeds },
253
+ declaredRequired: [...input.declaredRequired],
254
+ steps: input.steps.map((step) => ({ ...step })),
255
+ verdict: input.steps.every((step) => step.status === "passed" && step.exitCode === 0) ? "pass" : "fail"
256
+ };
257
+ return sealProof(signoffProofBodySchema.parse(body), input.key);
258
+ }
259
+ function parseSignoffProof(json) {
260
+ return signoffProofSchema.parse(JSON.parse(json));
261
+ }
262
+ function serializeSignoffProof(proof) {
263
+ return `${JSON.stringify(proof, null, 2)}
264
+ `;
265
+ }
266
+ function formatDuration(ms) {
267
+ const seconds = Math.round(ms / 1e3);
268
+ if (seconds < 60) return `${seconds}s`;
269
+ return `${Math.floor(seconds / 60)}m${String(seconds % 60).padStart(2, "0")}s`;
270
+ }
271
+ function formatSignoffSummary(proof) {
272
+ const { body, seal } = proof;
273
+ const passed = body.steps.filter((step) => step.status === "passed" && step.exitCode === 0).length;
274
+ const serialMs = body.steps.reduce((total, step) => total + step.durationMs, 0);
275
+ const wall = serialMs > body.wallClockMs ? `${formatDuration(body.wallClockMs)} (serial ${formatDuration(serialMs)})` : formatDuration(body.wallClockMs);
276
+ const seeds = Object.entries(body.seeds).sort(([a], [b]) => a.localeCompare(b)).map(([name, value]) => `${name}=${value}`).join(" ");
277
+ const seal_ = seal.algorithm === "hmac-sha256" ? `sealed ${seal.algorithm} key ${seal.keyId ?? "unknown"}` : `unsealed sha256 ${seal.bodySha256.slice(0, 12)}`;
278
+ const dirty = body.subject.tree === body.subject.commitTree ? "" : " DIRTY-TREE";
279
+ return [
280
+ `signoff ${body.verdict}${dirty}`,
281
+ `${passed}/${body.steps.length} steps`,
282
+ wall,
283
+ `${body.subject.repo}@${body.subject.commit.slice(0, 9)} tree ${body.subject.tree.slice(0, 9)}`,
284
+ seeds.length === 0 ? "seeds none" : `seeds ${seeds}`,
285
+ `node ${body.tooling.node} pnpm ${body.tooling.pnpm ?? "unresolved"}`,
286
+ seal_,
287
+ body.signedAt
288
+ ].join(" \xB7 ");
289
+ }
290
+
291
+ // src/signoff/proof-attach.ts
292
+ var SIGNOFF_NOTES_REF = "refs/notes/signoff";
293
+ var SIGNOFF_NOTES_GIT_CONFIG = [
294
+ `git config --add remote.origin.fetch '+${SIGNOFF_NOTES_REF}:${SIGNOFF_NOTES_REF}'`,
295
+ `git config --add remote.origin.push '${SIGNOFF_NOTES_REF}'`,
296
+ `git config notes.rewriteRef '${SIGNOFF_NOTES_REF}'`,
297
+ "git config notes.rewrite.amend true",
298
+ "git config notes.rewrite.rebase true"
299
+ ];
300
+ function attachSignoffProof(input) {
301
+ const commit = resolveCommit(input.repoDir, input.rev ?? input.proof.body.subject.commit);
302
+ const args = ["notes", `--ref=${SIGNOFF_NOTES_REF}`, "add"];
303
+ if (input.overwrite === true) args.push("-f");
304
+ args.push("-F", "-", commit);
305
+ const result = runGit(input.repoDir, args, { input: `${JSON.stringify(input.proof, null, 2)}
306
+ ` });
307
+ if (result.status !== 0) throw new SignoffGitError(args, result.status, result.stderr);
308
+ return { commit, ref: SIGNOFF_NOTES_REF };
309
+ }
310
+ function readSignoffProofNote(repoDir, rev) {
311
+ const commit = resolveCommit(repoDir, rev);
312
+ const result = runGit(repoDir, ["notes", `--ref=${SIGNOFF_NOTES_REF}`, "show", commit]);
313
+ if (result.status !== 0) return { found: false, commit };
314
+ return { found: true, proof: parseSignoffProof(result.stdout), commit, binding: "exact" };
315
+ }
316
+ function listSignoffProofs(repoDir) {
317
+ const listed = runGit(repoDir, ["notes", `--ref=${SIGNOFF_NOTES_REF}`, "list"]);
318
+ if (listed.status !== 0) return [];
319
+ const out = [];
320
+ for (const line of listed.stdout.split("\n")) {
321
+ const [noteBlob, commit] = line.trim().split(/\s+/);
322
+ if (noteBlob === void 0 || commit === void 0) continue;
323
+ out.push({ commit, proof: parseSignoffProof(gitText(repoDir, ["cat-file", "blob", noteBlob])) });
324
+ }
325
+ return out;
326
+ }
327
+ function resolveSignoffProof(repoDir, rev) {
328
+ const direct = readSignoffProofNote(repoDir, rev);
329
+ if (direct.found) return direct;
330
+ const facts = readCommitFacts(repoDir, rev);
331
+ for (const entry of listSignoffProofs(repoDir)) {
332
+ if (entry.proof.body.subject.commitTree === facts.commitTree) {
333
+ return { found: true, proof: entry.proof, commit: facts.commit, binding: "tree-equivalent" };
334
+ }
335
+ }
336
+ return { found: false, commit: facts.commit };
337
+ }
338
+
339
+ // src/signoff/proof-verify.ts
340
+ import { readFileSync as readFileSync2 } from "fs";
341
+ import { createHmac as createHmac2 } from "crypto";
342
+ var SIGNOFF_REQUIRED_STEPS = {
343
+ "agent-app": ["install", "typecheck", "test:gates", "test", "build", "test:generated", "knip"],
344
+ "tax-agent": ["install", "peer-check", "typecheck", "test", "toolkit-deps", "toolkit-test", "build", "worker-startup"],
345
+ "legal-agent": ["install", "peer-check", "typegen", "typecheck", "test", "build:check"]
346
+ };
347
+ function requiredStepsFor(repo) {
348
+ const steps = SIGNOFF_REQUIRED_STEPS[repo];
349
+ if (steps === void 0) {
350
+ throw new Error(`no required-step table for repo ${JSON.stringify(repo)}; known: ${Object.keys(SIGNOFF_REQUIRED_STEPS).sort().join(", ")}`);
351
+ }
352
+ return steps;
353
+ }
354
+ function verifySignoffProof(proof, options) {
355
+ const failures = [];
356
+ const { body, seal } = proof;
357
+ const target = options.target;
358
+ if (body.proofVersion !== SIGNOFF_PROOF_VERSION) {
359
+ failures.push({ code: "unsupported-version", detail: `proof declares version ${body.proofVersion}; this verifier reads ${SIGNOFF_PROOF_VERSION}` });
360
+ }
361
+ const recomputed = hashProofBody(body);
362
+ if (recomputed !== seal.bodySha256) {
363
+ failures.push({ code: "body-tampered", detail: `seal claims body sha256 ${seal.bodySha256}; the body hashes to ${recomputed}` });
364
+ }
365
+ let macChecked = false;
366
+ if (options.key !== void 0) {
367
+ if (seal.mac === null) {
368
+ failures.push({ code: "mac-missing", detail: "a key was supplied but the proof carries no mac; it was produced unsealed" });
369
+ } else {
370
+ const expected = createHmac2("sha256", options.key).update(canonicalizeProofBody(body), "utf8").digest("hex");
371
+ if (macMatches(expected, seal.mac)) macChecked = true;
372
+ else failures.push({ code: "mac-invalid", detail: `mac does not verify under the supplied key (proof keyId ${seal.keyId ?? "none"})` });
373
+ }
374
+ }
375
+ if (options.expectRepo !== void 0 && options.expectRepo !== body.subject.repo) {
376
+ failures.push({ code: "repo-mismatch", detail: `proof is for repo ${body.subject.repo}; ${options.expectRepo} was requested` });
377
+ }
378
+ let requiredSteps = options.requiredSteps ?? [];
379
+ if (options.requiredSteps === void 0) {
380
+ const known = SIGNOFF_REQUIRED_STEPS[body.subject.repo];
381
+ if (known === void 0) {
382
+ failures.push({ code: "unknown-repo", detail: `no required-step table for ${body.subject.repo}; a repo with no declared bar cannot be signed off` });
383
+ } else {
384
+ requiredSteps = known;
385
+ }
386
+ }
387
+ const seen = /* @__PURE__ */ new Map();
388
+ for (const step of body.steps) seen.set(step.id, (seen.get(step.id) ?? 0) + 1);
389
+ for (const [id, count] of seen) {
390
+ if (count > 1) failures.push({ code: "duplicate-step", detail: `step ${id} appears ${count} times; a repeated id makes coverage ambiguous` });
391
+ }
392
+ const missing = requiredSteps.filter((id) => !seen.has(id));
393
+ if (missing.length > 0) {
394
+ failures.push({ code: "missing-required-step", detail: `required step(s) never ran: ${missing.join(", ")}` });
395
+ }
396
+ const declared = new Set(body.declaredRequired);
397
+ const understated = requiredSteps.filter((id) => !declared.has(id));
398
+ if (understated.length > 0) {
399
+ failures.push({ code: "lowered-bar", detail: `proof declares a smaller required set than ${body.subject.repo}'s table; missing: ${understated.join(", ")}` });
400
+ }
401
+ for (const step of body.steps) {
402
+ if (step.status !== "passed") {
403
+ failures.push({ code: "step-failed", detail: `step ${step.id} is ${step.status}, not passed (exit ${step.exitCode}, ${step.command})` });
404
+ } else if (step.exitCode !== 0) {
405
+ failures.push({ code: "step-failed", detail: `step ${step.id} claims status passed but exited ${step.exitCode} (${step.command})` });
406
+ }
407
+ }
408
+ if (body.verdict !== "pass") {
409
+ failures.push({ code: "verdict-fail", detail: `the run recorded verdict ${body.verdict}` });
410
+ }
411
+ if (body.subject.tree !== body.subject.commitTree) {
412
+ failures.push({
413
+ code: "dirty-worktree",
414
+ detail: `checks ran against tree ${body.subject.tree} while the commit carries ${body.subject.commitTree}; uncommitted work was in the tree`
415
+ });
416
+ }
417
+ if (body.subject.commitTree !== target.commitTree) {
418
+ failures.push({ code: "tree-mismatch", detail: `proof covers tree ${body.subject.commitTree}; ${target.commit} carries ${target.commitTree}` });
419
+ }
420
+ const commitBinding = body.subject.commit === target.commit ? "exact" : body.subject.commitTree === target.commitTree ? "tree-equivalent" : "none";
421
+ if (commitBinding === "none") {
422
+ failures.push({ code: "commit-unbound", detail: `proof names commit ${body.subject.commit}, which is neither ${target.commit} nor its content` });
423
+ }
424
+ const signedAt = Date.parse(body.signedAt);
425
+ if (signedAt < Date.parse(body.subject.committedAt)) {
426
+ failures.push({ code: "stale-proof", detail: `signed at ${body.signedAt}, before the commit it names was written at ${body.subject.committedAt}` });
427
+ }
428
+ if (commitBinding === "tree-equivalent" && options.isAncestor(body.subject.commit, target.commit)) {
429
+ failures.push({
430
+ code: "stale-proof",
431
+ detail: `${body.subject.commit} is an ancestor of ${target.commit}; the tree matches only because later work was undone, and that work was never signed off`
432
+ });
433
+ }
434
+ return { ok: failures.length === 0, commitBinding, macChecked, failures, proof, target, requiredSteps };
435
+ }
436
+ function verifySignoffAtRev(input) {
437
+ const lookup = resolveSignoffProof(input.repoDir, input.rev);
438
+ const target = readCommitFacts(input.repoDir, input.rev);
439
+ if (!lookup.found) return { found: false, commit: lookup.commit, hint: SIGNOFF_NOTES_GIT_CONFIG };
440
+ return {
441
+ found: true,
442
+ ...verifySignoffProof(lookup.proof, {
443
+ target,
444
+ isAncestor: gitIsAncestor(input.repoDir),
445
+ key: input.key,
446
+ requiredSteps: input.requiredSteps,
447
+ expectRepo: input.expectRepo
448
+ })
449
+ };
450
+ }
451
+ function verifySignoffProofFile(input) {
452
+ const proof = parseSignoffProof(readFileSync2(input.file, "utf8"));
453
+ const target = readCommitFacts(input.repoDir, input.rev);
454
+ return verifySignoffProof(proof, {
455
+ target,
456
+ isAncestor: gitIsAncestor(input.repoDir),
457
+ key: input.key,
458
+ requiredSteps: input.requiredSteps,
459
+ expectRepo: input.expectRepo
460
+ });
461
+ }
462
+ function formatSignoffVerification(result) {
463
+ const head = result.ok ? `VERIFIED ${result.proof.body.subject.repo}@${result.target.commit.slice(0, 9)} binding=${result.commitBinding} mac=${result.macChecked ? "checked" : "unchecked"}` : `REJECTED ${result.proof.body.subject.repo}@${result.target.commit.slice(0, 9)} binding=${result.commitBinding} (${result.failures.length} failure${result.failures.length === 1 ? "" : "s"})`;
464
+ return [head, ...result.failures.map((failure) => ` ${failure.code}: ${failure.detail}`)].join("\n");
465
+ }
466
+
467
+ export {
468
+ SignoffGitError,
469
+ runGit,
470
+ gitText,
471
+ gitIsAncestor,
472
+ resolveCommit,
473
+ readCommitFacts,
474
+ computeWorktreeTree,
475
+ SIGNOFF_PROOF_VERSION,
476
+ SIGNOFF_STEP_STATUSES,
477
+ signoffProofStepSchema,
478
+ signoffProofBodySchema,
479
+ signoffProofSealSchema,
480
+ signoffProofSchema,
481
+ canonicalJson,
482
+ canonicalizeProofBody,
483
+ hashProofBody,
484
+ hashStepOutput,
485
+ signoffKeyId,
486
+ readSignoffKey,
487
+ sealProof,
488
+ macMatches,
489
+ collectToolingFacts,
490
+ readInstalledVersion,
491
+ tangleDependencyNames,
492
+ buildSignoffProof,
493
+ parseSignoffProof,
494
+ serializeSignoffProof,
495
+ formatSignoffSummary,
496
+ SIGNOFF_NOTES_REF,
497
+ SIGNOFF_NOTES_GIT_CONFIG,
498
+ attachSignoffProof,
499
+ readSignoffProofNote,
500
+ listSignoffProofs,
501
+ resolveSignoffProof,
502
+ SIGNOFF_REQUIRED_STEPS,
503
+ requiredStepsFor,
504
+ verifySignoffProof,
505
+ verifySignoffAtRev,
506
+ verifySignoffProofFile,
507
+ formatSignoffVerification
508
+ };
509
+ //# sourceMappingURL=chunk-N3G3FSM4.js.map