@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
package/dist/assistant/index.js
CHANGED
|
@@ -6,18 +6,18 @@ import {
|
|
|
6
6
|
ChatMessages,
|
|
7
7
|
ModelPicker,
|
|
8
8
|
ProviderLogo
|
|
9
|
-
} from "../chunk-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
112
|
+
} from "../chunk-OTLEYHOG.js";
|
|
113
113
|
import "../chunk-TH7L265V.js";
|
|
114
114
|
import "../chunk-LWSJK546.js";
|
|
115
115
|
import "../chunk-P5PIAQVS.js";
|
package/dist/chat-store/index.js
CHANGED
|
@@ -19,9 +19,9 @@ import {
|
|
|
19
19
|
mentionInputToPart,
|
|
20
20
|
mentionPartsFromMessageParts,
|
|
21
21
|
toChatMessageParts
|
|
22
|
-
} from "../chunk-
|
|
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-
|
|
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
|