@catalyst-cloud/cli 0.16.4 → 0.16.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/onboard-next.js +1 -0
- package/dist/onboard-runner-custody.js +680 -0
- package/dist/onboard-runner.js +158 -40
- package/package.json +4 -4
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +2 -1
- package/skills/catalyst-onboard/references/preserving-runner-replacement.md +26 -0
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +1 -1
- package/vendor/self-host/compose.darwin-thoughts.yaml +18 -0
- package/vendor/self-host/compose.yaml +4 -3
- package/vendor/self-host/darwin-thoughts-custody/manifest.json +18 -0
- package/vendor/self-host/darwin-thoughts-custody/producer.mjs +1202 -0
- package/vendor/self-host/darwin-thoughts-custody/service.plist.in +21 -0
- package/vendor/self-host/darwin-thoughts-custody/verifier.d.mts +157 -0
- package/vendor/self-host/darwin-thoughts-custody/verifier.mjs +755 -0
- package/vendor/self-host/darwin-thoughts-custody/watchdog-deadline.mjs +39 -0
- package/vendor/self-host/darwin-thoughts-custody/watchdog.mjs +85 -0
- package/vendor/self-host/provenance.json +10 -2
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
// packages/protocol/src/darwin-thoughts-custody.ts
|
|
2
|
+
var DARWIN_THOUGHTS_CUSTODY_VERSION = 1;
|
|
3
|
+
var DARWIN_THOUGHTS_PRIVATE_ROOT = "/run/catalyst-thoughts-private";
|
|
4
|
+
var DARWIN_THOUGHTS_MAX_EXCHANGE_MS = 30000;
|
|
5
|
+
var DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES = 16384;
|
|
6
|
+
var AUTHORITY_FIELDS = [
|
|
7
|
+
"version",
|
|
8
|
+
"installationId",
|
|
9
|
+
"publicKey",
|
|
10
|
+
"nativeUid",
|
|
11
|
+
"nativeGid",
|
|
12
|
+
"nativeHome",
|
|
13
|
+
"endpoint",
|
|
14
|
+
"daemonId",
|
|
15
|
+
"thoughtsRoot",
|
|
16
|
+
"locksRoot",
|
|
17
|
+
"requestsRoot",
|
|
18
|
+
"responsesRoot"
|
|
19
|
+
];
|
|
20
|
+
var REQUEST_FIELDS = [
|
|
21
|
+
"version",
|
|
22
|
+
"installationId",
|
|
23
|
+
"nonce",
|
|
24
|
+
"tenant",
|
|
25
|
+
"repository",
|
|
26
|
+
"issuedAtMs",
|
|
27
|
+
"expiresAtMs"
|
|
28
|
+
];
|
|
29
|
+
var RESPONSE_FIELDS = [
|
|
30
|
+
"version",
|
|
31
|
+
"kind",
|
|
32
|
+
"authority",
|
|
33
|
+
"request",
|
|
34
|
+
"observedAtMs",
|
|
35
|
+
"expiresAtMs",
|
|
36
|
+
"nativeBootId",
|
|
37
|
+
"producerInstance"
|
|
38
|
+
];
|
|
39
|
+
var STAT_FIELDS = [
|
|
40
|
+
"path",
|
|
41
|
+
"device",
|
|
42
|
+
"inode",
|
|
43
|
+
"uid",
|
|
44
|
+
"gid",
|
|
45
|
+
"mode",
|
|
46
|
+
"isDirectory",
|
|
47
|
+
"isSymbolicLink"
|
|
48
|
+
];
|
|
49
|
+
var HEX_NONCE = /^[a-f0-9]{64}$/;
|
|
50
|
+
var TOKEN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/;
|
|
51
|
+
var TENANT = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
|
|
52
|
+
var OWNER = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,99}$/;
|
|
53
|
+
var REPOSITORY = /^[A-Za-z0-9_.-]{1,100}$/;
|
|
54
|
+
var PUBLIC_KEY = /^MCowBQYDK2VwAyEA[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]=$/;
|
|
55
|
+
var SIGNATURE = /^[A-Za-z0-9+/]{85}[AQgw]==$/;
|
|
56
|
+
var UINT64_MAX = "18446744073709551615";
|
|
57
|
+
var MAX_PATH_CHARS = 4096;
|
|
58
|
+
function invalid(reason) {
|
|
59
|
+
throw new Error(reason);
|
|
60
|
+
}
|
|
61
|
+
function plainRecord(value) {
|
|
62
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
63
|
+
return false;
|
|
64
|
+
const prototype = Object.getPrototypeOf(value);
|
|
65
|
+
return prototype === null || prototype === Object.prototype;
|
|
66
|
+
}
|
|
67
|
+
function exact(value, required, optional, reason) {
|
|
68
|
+
if (!plainRecord(value))
|
|
69
|
+
return invalid(reason);
|
|
70
|
+
const fields = Reflect.ownKeys(value);
|
|
71
|
+
if (required.some((key) => !Object.hasOwn(value, key)) || fields.some((key) => typeof key !== "string" || !required.includes(key) && !optional.includes(key)) || fields.some((key) => !Object.hasOwn(Object.getOwnPropertyDescriptor(value, key) ?? {}, "value")))
|
|
72
|
+
return invalid(reason);
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
function matches(value, pattern, reason) {
|
|
76
|
+
if (typeof value !== "string" || !pattern.test(value))
|
|
77
|
+
return invalid(reason);
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function integer(value, min, max, reason) {
|
|
81
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max)
|
|
82
|
+
return invalid(reason);
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
function path(value, reason) {
|
|
86
|
+
if (typeof value !== "string" || value.length > MAX_PATH_CHARS || !value.startsWith("/") || value === "/" || value.endsWith("/") || Array.from(value).some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || value.slice(1).split("/").some((part) => part === "" || part === "." || part === ".."))
|
|
87
|
+
return invalid(reason);
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function below(child, parent) {
|
|
91
|
+
return child.startsWith(parent + "/");
|
|
92
|
+
}
|
|
93
|
+
function tuple(tenant, repository, reason) {
|
|
94
|
+
const tenantName = matches(tenant, TENANT, reason);
|
|
95
|
+
if (typeof repository !== "string" || repository.length > 201)
|
|
96
|
+
return invalid(reason);
|
|
97
|
+
const parts = repository.split("/");
|
|
98
|
+
const owner = matches(parts[0], OWNER, reason);
|
|
99
|
+
const name = matches(parts[1], REPOSITORY, reason);
|
|
100
|
+
if (parts.length !== 2 || name === "." || name === "..")
|
|
101
|
+
return invalid(reason);
|
|
102
|
+
return { tenant: tenantName, repository: owner + "/" + name };
|
|
103
|
+
}
|
|
104
|
+
function parseDarwinThoughtsAuthority(value) {
|
|
105
|
+
const reason = "darwin_thoughts_authority_invalid";
|
|
106
|
+
const row = exact(value, AUTHORITY_FIELDS, [], reason);
|
|
107
|
+
if (row.version !== DARWIN_THOUGHTS_CUSTODY_VERSION)
|
|
108
|
+
return invalid(reason);
|
|
109
|
+
const nativeHome = path(row.nativeHome, reason);
|
|
110
|
+
const thoughtsRoot = path(row.thoughtsRoot, reason);
|
|
111
|
+
const locksRoot = path(row.locksRoot, reason);
|
|
112
|
+
const requestsRoot = path(row.requestsRoot, reason);
|
|
113
|
+
const responsesRoot = path(row.responsesRoot, reason);
|
|
114
|
+
const roots = [thoughtsRoot, locksRoot, requestsRoot, responsesRoot];
|
|
115
|
+
if (roots.some((root) => !below(root, nativeHome)) || roots.some((root, index) => roots.some((other, otherIndex) => index !== otherIndex && (root === other || below(root, other) || below(other, root)))))
|
|
116
|
+
return invalid(reason);
|
|
117
|
+
if (typeof row.endpoint !== "string" || !row.endpoint.startsWith("unix://"))
|
|
118
|
+
return invalid(reason);
|
|
119
|
+
const endpoint = "unix://" + path(row.endpoint.slice(7), reason);
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
version: DARWIN_THOUGHTS_CUSTODY_VERSION,
|
|
122
|
+
installationId: matches(row.installationId, HEX_NONCE, reason),
|
|
123
|
+
publicKey: matches(row.publicKey, PUBLIC_KEY, reason),
|
|
124
|
+
nativeUid: integer(row.nativeUid, 1, 4294967294, reason),
|
|
125
|
+
nativeGid: integer(row.nativeGid, 0, 4294967294, reason),
|
|
126
|
+
nativeHome,
|
|
127
|
+
endpoint,
|
|
128
|
+
daemonId: matches(row.daemonId, TOKEN, reason),
|
|
129
|
+
thoughtsRoot,
|
|
130
|
+
locksRoot,
|
|
131
|
+
requestsRoot,
|
|
132
|
+
responsesRoot
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function deriveDarwinThoughtsTargets(authority, tenant, repository) {
|
|
136
|
+
const installed = parseDarwinThoughtsAuthority(authority);
|
|
137
|
+
const selected = tuple(tenant, repository, "darwin_thoughts_tuple_invalid");
|
|
138
|
+
const suffix = selected.tenant + "/" + selected.repository;
|
|
139
|
+
const privateRoot = DARWIN_THOUGHTS_PRIVATE_ROOT;
|
|
140
|
+
return Object.freeze({
|
|
141
|
+
checkoutSource: installed.thoughtsRoot + "/" + suffix,
|
|
142
|
+
lockSource: installed.locksRoot + "/" + selected.tenant,
|
|
143
|
+
requestSource: installed.requestsRoot + "/" + suffix,
|
|
144
|
+
responseSource: installed.responsesRoot + "/" + suffix,
|
|
145
|
+
publicCheckoutTarget: "/srv/catalyst/tenants/" + selected.tenant + "/repos/" + selected.repository,
|
|
146
|
+
privateCheckoutTarget: privateRoot + "/checkouts/" + suffix,
|
|
147
|
+
privateLockTarget: privateRoot + "/locks/" + selected.tenant,
|
|
148
|
+
privateRequestTarget: privateRoot + "/requests/" + suffix,
|
|
149
|
+
privateResponseTarget: privateRoot + "/responses/" + suffix
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function parseDarwinThoughtsRequest(value) {
|
|
153
|
+
const reason = "darwin_thoughts_request_invalid";
|
|
154
|
+
const row = exact(value, REQUEST_FIELDS, [], reason);
|
|
155
|
+
if (row.version !== DARWIN_THOUGHTS_CUSTODY_VERSION)
|
|
156
|
+
return invalid(reason);
|
|
157
|
+
const selected = tuple(row.tenant, row.repository, reason);
|
|
158
|
+
const issuedAtMs = integer(row.issuedAtMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
159
|
+
const expiresAtMs = integer(row.expiresAtMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
160
|
+
if (expiresAtMs <= issuedAtMs || expiresAtMs - issuedAtMs > DARWIN_THOUGHTS_MAX_EXCHANGE_MS)
|
|
161
|
+
return invalid(reason);
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
version: DARWIN_THOUGHTS_CUSTODY_VERSION,
|
|
164
|
+
installationId: matches(row.installationId, HEX_NONCE, reason),
|
|
165
|
+
nonce: matches(row.nonce, HEX_NONCE, reason),
|
|
166
|
+
...selected,
|
|
167
|
+
issuedAtMs,
|
|
168
|
+
expiresAtMs
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function decimal(value, nonzero, reason) {
|
|
172
|
+
const text = matches(value, /^(0|[1-9][0-9]{0,19})$/, reason);
|
|
173
|
+
if (nonzero && text === "0" || text.length === 20 && text > UINT64_MAX)
|
|
174
|
+
return invalid(reason);
|
|
175
|
+
return text;
|
|
176
|
+
}
|
|
177
|
+
function statIdentity(value, expectedPath, authority, native, git) {
|
|
178
|
+
const reason = "darwin_thoughts_response_invalid";
|
|
179
|
+
const row = exact(value, STAT_FIELDS, [], reason);
|
|
180
|
+
const observedPath = path(row.path, reason);
|
|
181
|
+
const uid = integer(row.uid, 0, 4294967294, reason);
|
|
182
|
+
const gid = integer(row.gid, 0, 4294967294, reason);
|
|
183
|
+
const mode = integer(row.mode, 0, 4095, reason);
|
|
184
|
+
if (observedPath !== expectedPath || row.isDirectory !== true || row.isSymbolicLink !== false || (native ? uid !== authority.nativeUid || gid !== authority.nativeGid : uid !== 0 && uid !== 10001) || (mode & 448) !== 448 || (mode & 3602) !== 0 || !git && (mode & 7) !== 0)
|
|
185
|
+
return invalid(reason);
|
|
186
|
+
return Object.freeze({
|
|
187
|
+
path: observedPath,
|
|
188
|
+
device: decimal(row.device, false, reason),
|
|
189
|
+
inode: decimal(row.inode, true, reason),
|
|
190
|
+
uid,
|
|
191
|
+
gid,
|
|
192
|
+
mode,
|
|
193
|
+
isDirectory: true,
|
|
194
|
+
isSymbolicLink: false
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function parseDarwinThoughtsResponse(value, context) {
|
|
198
|
+
const reason = "darwin_thoughts_response_invalid";
|
|
199
|
+
try {
|
|
200
|
+
const basic = exact(value, RESPONSE_FIELDS, [
|
|
201
|
+
"nativeCheckout",
|
|
202
|
+
"nativeLock",
|
|
203
|
+
"engineCheckout",
|
|
204
|
+
"engineLock",
|
|
205
|
+
"nativeGit",
|
|
206
|
+
"engineGit",
|
|
207
|
+
"reason"
|
|
208
|
+
], reason);
|
|
209
|
+
if (basic.version !== DARWIN_THOUGHTS_CUSTODY_VERSION)
|
|
210
|
+
return invalid(reason);
|
|
211
|
+
const authority = parseDarwinThoughtsAuthority(basic.authority);
|
|
212
|
+
const request = parseDarwinThoughtsRequest(basic.request);
|
|
213
|
+
const installed = parseDarwinThoughtsAuthority(context.authority);
|
|
214
|
+
const expected = parseDarwinThoughtsRequest(context.request);
|
|
215
|
+
const nowMs = integer(context.nowMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
216
|
+
if (canonicalDarwinThoughtsJson(authority) !== canonicalDarwinThoughtsJson(installed) || canonicalDarwinThoughtsJson(request) !== canonicalDarwinThoughtsJson(expected) || request.installationId !== authority.installationId || request.issuedAtMs > nowMs || request.expiresAtMs <= nowMs)
|
|
217
|
+
return invalid(reason);
|
|
218
|
+
const observedAtMs = integer(basic.observedAtMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
219
|
+
const expiresAtMs = integer(basic.expiresAtMs, 1, Number.MAX_SAFE_INTEGER, reason);
|
|
220
|
+
if (observedAtMs < request.issuedAtMs || observedAtMs > nowMs || expiresAtMs <= observedAtMs || expiresAtMs <= nowMs || expiresAtMs > request.expiresAtMs)
|
|
221
|
+
return invalid(reason);
|
|
222
|
+
const binding = {
|
|
223
|
+
version: DARWIN_THOUGHTS_CUSTODY_VERSION,
|
|
224
|
+
authority,
|
|
225
|
+
request,
|
|
226
|
+
observedAtMs,
|
|
227
|
+
expiresAtMs,
|
|
228
|
+
nativeBootId: matches(basic.nativeBootId, TOKEN, reason),
|
|
229
|
+
producerInstance: matches(basic.producerInstance, HEX_NONCE, reason)
|
|
230
|
+
};
|
|
231
|
+
if (basic.kind === "refused") {
|
|
232
|
+
const row = exact(value, [...RESPONSE_FIELDS, "reason"], [], reason);
|
|
233
|
+
return Object.freeze({
|
|
234
|
+
...binding,
|
|
235
|
+
kind: "refused",
|
|
236
|
+
reason: matches(row.reason, /^[a-z][a-z0-9_]{0,127}$/, reason)
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (basic.kind !== "accepted")
|
|
240
|
+
return invalid(reason);
|
|
241
|
+
const row = exact(value, [...RESPONSE_FIELDS, "nativeCheckout", "nativeLock", "engineCheckout", "engineLock"], ["nativeGit", "engineGit"], reason);
|
|
242
|
+
const paths = deriveDarwinThoughtsTargets(authority, request.tenant, request.repository);
|
|
243
|
+
const hasGit = Object.hasOwn(row, "nativeGit");
|
|
244
|
+
if (hasGit !== Object.hasOwn(row, "engineGit"))
|
|
245
|
+
return invalid(reason);
|
|
246
|
+
return Object.freeze({
|
|
247
|
+
...binding,
|
|
248
|
+
kind: "accepted",
|
|
249
|
+
nativeCheckout: statIdentity(row.nativeCheckout, paths.checkoutSource, authority, true, false),
|
|
250
|
+
nativeLock: statIdentity(row.nativeLock, paths.lockSource, authority, true, false),
|
|
251
|
+
engineCheckout: statIdentity(row.engineCheckout, paths.checkoutSource, authority, false, false),
|
|
252
|
+
engineLock: statIdentity(row.engineLock, paths.lockSource, authority, false, false),
|
|
253
|
+
...hasGit ? {
|
|
254
|
+
nativeGit: statIdentity(row.nativeGit, paths.checkoutSource + "/.git", authority, true, true),
|
|
255
|
+
engineGit: statIdentity(row.engineGit, paths.checkoutSource + "/.git", authority, false, true)
|
|
256
|
+
} : {}
|
|
257
|
+
});
|
|
258
|
+
} catch {
|
|
259
|
+
return invalid(reason);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function utf8Bytes(text, reason) {
|
|
263
|
+
let size = 0;
|
|
264
|
+
for (let index = 0;index < text.length; index++) {
|
|
265
|
+
const char = text.charCodeAt(index);
|
|
266
|
+
if (char <= 127)
|
|
267
|
+
size++;
|
|
268
|
+
else if (char <= 2047)
|
|
269
|
+
size += 2;
|
|
270
|
+
else if (char >= 55296 && char <= 56319) {
|
|
271
|
+
const next = text.charCodeAt(++index);
|
|
272
|
+
if (!(next >= 56320 && next <= 57343))
|
|
273
|
+
return invalid(reason);
|
|
274
|
+
size += 4;
|
|
275
|
+
} else {
|
|
276
|
+
if (char >= 56320 && char <= 57343)
|
|
277
|
+
return invalid(reason);
|
|
278
|
+
size += 3;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return size;
|
|
282
|
+
}
|
|
283
|
+
function parseDarwinThoughtsSignedEnvelope(value) {
|
|
284
|
+
const reason = "darwin_thoughts_envelope_invalid";
|
|
285
|
+
const row = exact(value, ["version", "payload", "signature"], [], reason);
|
|
286
|
+
if (row.version !== DARWIN_THOUGHTS_CUSTODY_VERSION || typeof row.payload !== "string" || row.payload.length === 0 || row.payload.length > DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES || utf8Bytes(row.payload, reason) > DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES)
|
|
287
|
+
return invalid(reason);
|
|
288
|
+
const envelope = Object.freeze({
|
|
289
|
+
version: DARWIN_THOUGHTS_CUSTODY_VERSION,
|
|
290
|
+
payload: row.payload,
|
|
291
|
+
signature: matches(row.signature, SIGNATURE, reason)
|
|
292
|
+
});
|
|
293
|
+
try {
|
|
294
|
+
canonicalDarwinThoughtsJson(envelope);
|
|
295
|
+
} catch {
|
|
296
|
+
return invalid(reason);
|
|
297
|
+
}
|
|
298
|
+
return envelope;
|
|
299
|
+
}
|
|
300
|
+
function canonicalDarwinThoughtsJson(value) {
|
|
301
|
+
const reason = "darwin_thoughts_json_invalid";
|
|
302
|
+
const seen = new Set;
|
|
303
|
+
let nodes = 0;
|
|
304
|
+
function encode(input, depth) {
|
|
305
|
+
if (++nodes > 4096 || depth > 16)
|
|
306
|
+
return invalid(reason);
|
|
307
|
+
if (input === null)
|
|
308
|
+
return "null";
|
|
309
|
+
if (typeof input === "boolean")
|
|
310
|
+
return input ? "true" : "false";
|
|
311
|
+
if (typeof input === "number") {
|
|
312
|
+
if (!Number.isSafeInteger(input) || Object.is(input, -0))
|
|
313
|
+
return invalid(reason);
|
|
314
|
+
return String(input);
|
|
315
|
+
}
|
|
316
|
+
if (typeof input === "string") {
|
|
317
|
+
if (utf8Bytes(input, reason) > DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES)
|
|
318
|
+
return invalid(reason);
|
|
319
|
+
return JSON.stringify(input);
|
|
320
|
+
}
|
|
321
|
+
if (typeof input !== "object" || seen.has(input))
|
|
322
|
+
return invalid(reason);
|
|
323
|
+
seen.add(input);
|
|
324
|
+
try {
|
|
325
|
+
if (Array.isArray(input)) {
|
|
326
|
+
const keys = Reflect.ownKeys(input);
|
|
327
|
+
if (input.length > 4096 || Object.keys(input).length !== input.length || keys.length !== input.length + 1 || keys.some((key) => key !== "length" && (typeof key !== "string" || !/^(0|[1-9][0-9]*)$/.test(key) || !Object.hasOwn(Object.getOwnPropertyDescriptor(input, key) ?? {}, "value"))))
|
|
328
|
+
return invalid(reason);
|
|
329
|
+
return "[" + input.map((item) => encode(item, depth + 1)).join(",") + "]";
|
|
330
|
+
}
|
|
331
|
+
if (!plainRecord(input) || Reflect.ownKeys(input).some((key) => typeof key !== "string"))
|
|
332
|
+
return invalid(reason);
|
|
333
|
+
const keys = Object.keys(input).sort();
|
|
334
|
+
if (keys.length > 4096 || Reflect.ownKeys(input).length !== keys.length)
|
|
335
|
+
return invalid(reason);
|
|
336
|
+
return "{" + keys.map((key) => {
|
|
337
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
338
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value"))
|
|
339
|
+
return invalid(reason);
|
|
340
|
+
return JSON.stringify(key) + ":" + encode(descriptor.value, depth + 1);
|
|
341
|
+
}).join(",") + "}";
|
|
342
|
+
} finally {
|
|
343
|
+
seen.delete(input);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const result = encode(value, 0);
|
|
347
|
+
if (utf8Bytes(result, reason) > DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES)
|
|
348
|
+
return invalid(reason);
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
// packages/host-isolation/src/darwin-thoughts-custody.ts
|
|
352
|
+
import { createPublicKey, randomBytes, verify } from "node:crypto";
|
|
353
|
+
import fs from "node:fs";
|
|
354
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
355
|
+
function failure(reason, cause) {
|
|
356
|
+
return new Error(`darwin_thoughts_custody:${reason}`, { cause });
|
|
357
|
+
}
|
|
358
|
+
function guarded(operation) {
|
|
359
|
+
try {
|
|
360
|
+
return operation();
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (error instanceof Error && error.message.startsWith("darwin_thoughts_custody:"))
|
|
363
|
+
throw error;
|
|
364
|
+
throw failure("invalid", error);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function sameDescriptor(left, right) {
|
|
368
|
+
return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode && left.uid === right.uid && left.gid === right.gid && left.nlink === right.nlink && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
|
|
369
|
+
}
|
|
370
|
+
function checkRegular(stat) {
|
|
371
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n || (stat.mode & 0o7777n) !== 0o600n || stat.size <= 0n || stat.size > BigInt(DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES)) {
|
|
372
|
+
throw failure("unsafe_file");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function readDarwinThoughtsCustodyFile(path, expectedOwner) {
|
|
376
|
+
return guarded(() => {
|
|
377
|
+
const namedBefore = fs.lstatSync(path, { bigint: true });
|
|
378
|
+
checkRegular(namedBefore);
|
|
379
|
+
if (expectedOwner && (namedBefore.uid !== BigInt(expectedOwner.uid) || namedBefore.gid !== BigInt(expectedOwner.gid))) {
|
|
380
|
+
throw failure("file_owner");
|
|
381
|
+
}
|
|
382
|
+
const fd = fs.openSync(path, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
383
|
+
try {
|
|
384
|
+
const opened = fs.fstatSync(fd, { bigint: true });
|
|
385
|
+
checkRegular(opened);
|
|
386
|
+
if (!sameDescriptor(namedBefore, opened))
|
|
387
|
+
throw failure("substituted_file");
|
|
388
|
+
const bytes = Buffer.alloc(DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES + 1);
|
|
389
|
+
let length = 0;
|
|
390
|
+
while (length < bytes.length) {
|
|
391
|
+
const count = fs.readSync(fd, bytes, length, bytes.length - length, length);
|
|
392
|
+
if (count === 0)
|
|
393
|
+
break;
|
|
394
|
+
length += count;
|
|
395
|
+
}
|
|
396
|
+
const after = fs.fstatSync(fd, { bigint: true });
|
|
397
|
+
const namedAfter = fs.lstatSync(path, { bigint: true });
|
|
398
|
+
checkRegular(after);
|
|
399
|
+
checkRegular(namedAfter);
|
|
400
|
+
if (length > DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES || BigInt(length) !== after.size || !sameDescriptor(opened, after) || !sameDescriptor(after, namedAfter))
|
|
401
|
+
throw failure("substituted_file");
|
|
402
|
+
return bytes.subarray(0, length);
|
|
403
|
+
} finally {
|
|
404
|
+
fs.closeSync(fd);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
function canonicalValue(text) {
|
|
409
|
+
const value = JSON.parse(text);
|
|
410
|
+
if (canonicalDarwinThoughtsJson(value) !== text)
|
|
411
|
+
throw failure("noncanonical_json");
|
|
412
|
+
return value;
|
|
413
|
+
}
|
|
414
|
+
function verifyDarwinThoughtsCustodyResponse(bytes, expected) {
|
|
415
|
+
return guarded(() => {
|
|
416
|
+
if (bytes.byteLength === 0 || bytes.byteLength > DARWIN_THOUGHTS_MAX_PAYLOAD_BYTES)
|
|
417
|
+
throw failure("size");
|
|
418
|
+
const authority = parseDarwinThoughtsAuthority(expected.authority);
|
|
419
|
+
const request = parseDarwinThoughtsRequest(expected.request);
|
|
420
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
421
|
+
const envelope = parseDarwinThoughtsSignedEnvelope(canonicalValue(text));
|
|
422
|
+
const key = createPublicKey({
|
|
423
|
+
key: Buffer.from(authority.publicKey, "base64"),
|
|
424
|
+
type: "spki",
|
|
425
|
+
format: "der"
|
|
426
|
+
});
|
|
427
|
+
if (key.asymmetricKeyType !== "ed25519" || key.export({ type: "spki", format: "der" }).toString("base64") !== authority.publicKey || !verify(null, Buffer.from(envelope.payload, "utf8"), key, Buffer.from(envelope.signature, "base64"))) {
|
|
428
|
+
throw failure("signature");
|
|
429
|
+
}
|
|
430
|
+
const response = parseDarwinThoughtsResponse(canonicalValue(envelope.payload), {
|
|
431
|
+
authority,
|
|
432
|
+
request,
|
|
433
|
+
nowMs: expected.nowMs
|
|
434
|
+
});
|
|
435
|
+
if (response.kind !== "accepted")
|
|
436
|
+
throw failure("producer_refused");
|
|
437
|
+
for (const [actual, current] of [
|
|
438
|
+
[response.engineCheckout, expected.engineCheckout],
|
|
439
|
+
[response.engineLock, expected.engineLock],
|
|
440
|
+
[response.engineGit, expected.engineGit]
|
|
441
|
+
]) {
|
|
442
|
+
if (canonicalDarwinThoughtsJson(actual ?? null) !== canonicalDarwinThoughtsJson(current ?? null)) {
|
|
443
|
+
throw failure("engine_identity");
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return response;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function cleanPath(path) {
|
|
450
|
+
if (!isAbsolute(path) || resolve(path) !== path || path === "/")
|
|
451
|
+
throw failure("path");
|
|
452
|
+
let current = "/";
|
|
453
|
+
for (const component of path.slice(1).split("/")) {
|
|
454
|
+
current = join(current, component);
|
|
455
|
+
const stat = fs.lstatSync(current);
|
|
456
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
457
|
+
throw failure("directory");
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function directory(path, privateChannel = false) {
|
|
461
|
+
return guarded(() => {
|
|
462
|
+
cleanPath(path);
|
|
463
|
+
const named = fs.lstatSync(path, { bigint: true });
|
|
464
|
+
const fd = fs.openSync(path, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
465
|
+
try {
|
|
466
|
+
const opened = fs.fstatSync(fd, { bigint: true });
|
|
467
|
+
const after = fs.lstatSync(path, { bigint: true });
|
|
468
|
+
if (!opened.isDirectory() || !sameDirectoryAuthority(named, opened) || !sameDirectoryAuthority(opened, after) || (opened.mode & 0o7022n) !== 0n || (opened.mode & 0o700n) !== 0o700n || privateChannel && ((opened.mode & 0o077n) !== 0n || opened.uid !== 0n && opened.uid !== 10001n)) {
|
|
469
|
+
throw failure("unsafe_directory");
|
|
470
|
+
}
|
|
471
|
+
return opened;
|
|
472
|
+
} finally {
|
|
473
|
+
fs.closeSync(fd);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function currentIdentity(path, logicalPath) {
|
|
478
|
+
const stat = directory(path);
|
|
479
|
+
return {
|
|
480
|
+
path: logicalPath,
|
|
481
|
+
device: stat.dev.toString(),
|
|
482
|
+
inode: stat.ino.toString(),
|
|
483
|
+
uid: Number(stat.uid),
|
|
484
|
+
gid: Number(stat.gid),
|
|
485
|
+
mode: Number(stat.mode & 0o7777n),
|
|
486
|
+
isDirectory: true,
|
|
487
|
+
isSymbolicLink: false
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function sameDirectoryAuthority(left, right) {
|
|
491
|
+
return left.dev === right.dev && left.ino === right.ino && left.uid === right.uid && left.gid === right.gid && left.mode === right.mode;
|
|
492
|
+
}
|
|
493
|
+
function ancestors(paths, allowMissing = false) {
|
|
494
|
+
const observed = new Map;
|
|
495
|
+
for (const path of paths) {
|
|
496
|
+
let current = "/";
|
|
497
|
+
for (const component of ["", ...path.slice(1).split("/")]) {
|
|
498
|
+
if (component)
|
|
499
|
+
current = join(current, component);
|
|
500
|
+
if (observed.has(current))
|
|
501
|
+
continue;
|
|
502
|
+
let stat;
|
|
503
|
+
try {
|
|
504
|
+
stat = fs.lstatSync(current, { bigint: true });
|
|
505
|
+
} catch (error) {
|
|
506
|
+
if (allowMissing && isMissing(error))
|
|
507
|
+
break;
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
511
|
+
throw failure("ancestor");
|
|
512
|
+
observed.set(current, stat);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return Array.from(observed, ([path, stat]) => ({ path, stat }));
|
|
516
|
+
}
|
|
517
|
+
function checkAncestors(expected) {
|
|
518
|
+
for (const ancestor of expected) {
|
|
519
|
+
const current = fs.lstatSync(ancestor.path, { bigint: true });
|
|
520
|
+
if (!current.isDirectory() || current.isSymbolicLink() || !sameDirectoryAuthority(current, ancestor.stat)) {
|
|
521
|
+
throw failure("ancestor_identity");
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function isMissing(error) {
|
|
526
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
527
|
+
}
|
|
528
|
+
function gitPresent(path) {
|
|
529
|
+
try {
|
|
530
|
+
fs.lstatSync(path);
|
|
531
|
+
return true;
|
|
532
|
+
} catch (error) {
|
|
533
|
+
if (isMissing(error))
|
|
534
|
+
return false;
|
|
535
|
+
throw failure("git_descriptor", error);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function removeOwned(path, owned) {
|
|
539
|
+
if (!owned)
|
|
540
|
+
return;
|
|
541
|
+
try {
|
|
542
|
+
const stat = fs.lstatSync(path, { bigint: true });
|
|
543
|
+
if (stat.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino)
|
|
544
|
+
fs.unlinkSync(path);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
if (!isMissing(error))
|
|
547
|
+
throw failure("cleanup", error);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
async function requestFreshDarwinThoughtsCustody(options) {
|
|
551
|
+
return exchange(options, false);
|
|
552
|
+
}
|
|
553
|
+
async function requestFreshDarwinThoughtsBootstrap(options) {
|
|
554
|
+
const authority = guarded(() => parseDarwinThoughtsAuthority(options.authority));
|
|
555
|
+
const targets = guarded(() => deriveDarwinThoughtsTargets(authority, options.tenant, options.repository));
|
|
556
|
+
if (options.requestDir !== authority.requestsRoot || options.responseDir !== authority.responsesRoot || options.checkoutPath !== targets.checkoutSource || options.lockPath !== targets.lockSource || options.gitPath !== undefined && options.gitPath !== join(targets.checkoutSource, ".git"))
|
|
557
|
+
throw failure("bootstrap_path");
|
|
558
|
+
return exchange(options, true);
|
|
559
|
+
}
|
|
560
|
+
function bootstrapPaths(authority, tenant, repository) {
|
|
561
|
+
const targets = deriveDarwinThoughtsTargets(authority, tenant, repository);
|
|
562
|
+
return [
|
|
563
|
+
[authority.thoughtsRoot, targets.checkoutSource],
|
|
564
|
+
[authority.locksRoot, targets.lockSource],
|
|
565
|
+
[authority.requestsRoot, targets.requestSource],
|
|
566
|
+
[authority.responsesRoot, targets.responseSource]
|
|
567
|
+
];
|
|
568
|
+
}
|
|
569
|
+
function checkBootstrapDirectories(paths, allowMissing) {
|
|
570
|
+
for (const [index, [root, leaf]] of paths.entries()) {
|
|
571
|
+
if (!root || !leaf)
|
|
572
|
+
throw failure("bootstrap_path");
|
|
573
|
+
let current = root;
|
|
574
|
+
const components = ["", ...leaf.slice(root.length + 1).split("/")];
|
|
575
|
+
for (const component of components) {
|
|
576
|
+
if (component)
|
|
577
|
+
current = join(current, component);
|
|
578
|
+
try {
|
|
579
|
+
fs.lstatSync(current);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
if (allowMissing && current !== root && isMissing(error))
|
|
582
|
+
break;
|
|
583
|
+
throw failure("bootstrap_directory", error);
|
|
584
|
+
}
|
|
585
|
+
const stat = directory(current, index >= 2);
|
|
586
|
+
if (stat.uid !== 0n && stat.uid !== 10001n)
|
|
587
|
+
throw failure("bootstrap_owner");
|
|
588
|
+
if ((stat.mode & 0o007n) !== 0n)
|
|
589
|
+
throw failure("unsafe_directory");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async function exchange(options, bootstrap) {
|
|
594
|
+
const now = options.now ?? Date.now;
|
|
595
|
+
const sleep = options.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
|
|
596
|
+
const authority = guarded(() => parseDarwinThoughtsAuthority(options.authority));
|
|
597
|
+
const targets = guarded(() => deriveDarwinThoughtsTargets(authority, options.tenant, options.repository));
|
|
598
|
+
const issuedAtMs = now();
|
|
599
|
+
if (!Number.isSafeInteger(issuedAtMs) || !Number.isSafeInteger(options.deadlineMs) || options.deadlineMs <= issuedAtMs || options.deadlineMs - issuedAtMs > DARWIN_THOUGHTS_MAX_EXCHANGE_MS)
|
|
600
|
+
throw failure("deadline");
|
|
601
|
+
const request = guarded(() => parseDarwinThoughtsRequest({
|
|
602
|
+
version: 1,
|
|
603
|
+
installationId: authority.installationId,
|
|
604
|
+
nonce: randomBytes(32).toString("hex"),
|
|
605
|
+
tenant: options.tenant,
|
|
606
|
+
repository: options.repository,
|
|
607
|
+
issuedAtMs,
|
|
608
|
+
expiresAtMs: options.deadlineMs
|
|
609
|
+
}));
|
|
610
|
+
const requests = directory(options.requestDir, true);
|
|
611
|
+
const responses = directory(options.responseDir, true);
|
|
612
|
+
const creationPaths = bootstrap ? bootstrapPaths(authority, options.tenant, options.repository) : [];
|
|
613
|
+
if (bootstrap)
|
|
614
|
+
checkBootstrapDirectories(creationPaths, true);
|
|
615
|
+
else {
|
|
616
|
+
currentIdentity(options.checkoutPath, targets.checkoutSource);
|
|
617
|
+
currentIdentity(options.lockPath, targets.lockSource);
|
|
618
|
+
}
|
|
619
|
+
const gitPath = join(options.checkoutPath, ".git");
|
|
620
|
+
if (options.gitPath !== undefined && options.gitPath !== gitPath)
|
|
621
|
+
throw failure("git_path");
|
|
622
|
+
const initialGitPresent = options.gitPath !== undefined || gitPresent(gitPath);
|
|
623
|
+
if (initialGitPresent)
|
|
624
|
+
currentIdentity(gitPath, join(targets.checkoutSource, ".git"));
|
|
625
|
+
const aliasAncestors = guarded(() => ancestors([
|
|
626
|
+
options.requestDir,
|
|
627
|
+
options.responseDir,
|
|
628
|
+
options.checkoutPath,
|
|
629
|
+
options.lockPath,
|
|
630
|
+
...initialGitPresent ? [gitPath] : [],
|
|
631
|
+
...creationPaths.map((paths) => paths[1])
|
|
632
|
+
], bootstrap));
|
|
633
|
+
const finalPath = join(options.requestDir, `${request.nonce}.json`);
|
|
634
|
+
const stagingPath = join(options.requestDir, `.${request.nonce}.tmp`);
|
|
635
|
+
const responsePath = join(options.responseDir, `${request.nonce}.json`);
|
|
636
|
+
let owned;
|
|
637
|
+
let published = false;
|
|
638
|
+
try {
|
|
639
|
+
const fd = guarded(() => fs.openSync(stagingPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK, 384));
|
|
640
|
+
try {
|
|
641
|
+
owned = fs.fstatSync(fd, { bigint: true });
|
|
642
|
+
const bytes = Buffer.from(canonicalDarwinThoughtsJson(request));
|
|
643
|
+
fs.writeFileSync(fd, bytes);
|
|
644
|
+
fs.fsyncSync(fd);
|
|
645
|
+
const written = fs.fstatSync(fd, { bigint: true });
|
|
646
|
+
const named = fs.lstatSync(stagingPath, { bigint: true });
|
|
647
|
+
checkRegular(written);
|
|
648
|
+
if (!sameDescriptor(written, named) || written.dev !== owned.dev || written.ino !== owned.ino) {
|
|
649
|
+
throw failure("substituted_request");
|
|
650
|
+
}
|
|
651
|
+
} finally {
|
|
652
|
+
fs.closeSync(fd);
|
|
653
|
+
}
|
|
654
|
+
checkAncestors(aliasAncestors);
|
|
655
|
+
if (!sameDirectoryAuthority(requests, directory(options.requestDir, true)))
|
|
656
|
+
throw failure("channel_identity");
|
|
657
|
+
try {
|
|
658
|
+
fs.lstatSync(finalPath);
|
|
659
|
+
throw failure("nonce_collision");
|
|
660
|
+
} catch (error) {
|
|
661
|
+
if (!isMissing(error))
|
|
662
|
+
throw error;
|
|
663
|
+
}
|
|
664
|
+
fs.renameSync(stagingPath, finalPath);
|
|
665
|
+
published = true;
|
|
666
|
+
let previousNow = issuedAtMs;
|
|
667
|
+
while (true) {
|
|
668
|
+
const nowMs = now();
|
|
669
|
+
if (!Number.isSafeInteger(nowMs) || nowMs < previousNow || nowMs >= options.deadlineMs)
|
|
670
|
+
throw failure("deadline");
|
|
671
|
+
previousNow = nowMs;
|
|
672
|
+
checkAncestors(aliasAncestors);
|
|
673
|
+
for (const [path, expected] of [
|
|
674
|
+
[options.requestDir, requests],
|
|
675
|
+
[options.responseDir, responses]
|
|
676
|
+
]) {
|
|
677
|
+
const current = directory(path, true);
|
|
678
|
+
if (!sameDirectoryAuthority(current, expected))
|
|
679
|
+
throw failure("channel_identity");
|
|
680
|
+
}
|
|
681
|
+
let present = false;
|
|
682
|
+
try {
|
|
683
|
+
fs.lstatSync(responsePath);
|
|
684
|
+
present = true;
|
|
685
|
+
} catch (error) {
|
|
686
|
+
if (!isMissing(error))
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
if (present) {
|
|
690
|
+
if (bootstrap)
|
|
691
|
+
checkBootstrapDirectories(creationPaths, false);
|
|
692
|
+
const receivedAncestors = bootstrap ? guarded(() => ancestors(creationPaths.map((paths) => paths[1]))) : [];
|
|
693
|
+
const receivedGitPresent = options.gitPath !== undefined || gitPresent(gitPath);
|
|
694
|
+
const receivedGitAncestors = receivedGitPresent ? guarded(() => ancestors([gitPath])) : [];
|
|
695
|
+
const bytes = readDarwinThoughtsCustodyFile(responsePath, {
|
|
696
|
+
uid: Number(responses.uid),
|
|
697
|
+
gid: Number(responses.gid)
|
|
698
|
+
});
|
|
699
|
+
const expectation = {
|
|
700
|
+
authority,
|
|
701
|
+
request,
|
|
702
|
+
nowMs: 0,
|
|
703
|
+
engineCheckout: currentIdentity(options.checkoutPath, targets.checkoutSource),
|
|
704
|
+
engineLock: currentIdentity(options.lockPath, targets.lockSource)
|
|
705
|
+
};
|
|
706
|
+
if (receivedGitPresent)
|
|
707
|
+
expectation.engineGit = currentIdentity(gitPath, join(targets.checkoutSource, ".git"));
|
|
708
|
+
checkAncestors(aliasAncestors);
|
|
709
|
+
checkAncestors(receivedGitAncestors);
|
|
710
|
+
expectation.nowMs = now();
|
|
711
|
+
if (!Number.isSafeInteger(expectation.nowMs) || expectation.nowMs < previousNow || expectation.nowMs >= options.deadlineMs)
|
|
712
|
+
throw failure("deadline");
|
|
713
|
+
const proof = verifyDarwinThoughtsCustodyResponse(bytes, expectation);
|
|
714
|
+
checkAncestors(receivedGitAncestors);
|
|
715
|
+
if (bootstrap) {
|
|
716
|
+
checkAncestors(receivedAncestors);
|
|
717
|
+
checkAncestors(aliasAncestors);
|
|
718
|
+
checkBootstrapDirectories(creationPaths, false);
|
|
719
|
+
}
|
|
720
|
+
const finalNow = now();
|
|
721
|
+
if (!Number.isSafeInteger(finalNow) || finalNow < expectation.nowMs || finalNow >= options.deadlineMs)
|
|
722
|
+
throw failure("deadline");
|
|
723
|
+
if (options.onProof) {
|
|
724
|
+
options.onProof({
|
|
725
|
+
bytes: Buffer.from(bytes),
|
|
726
|
+
proof: structuredClone(proof),
|
|
727
|
+
expectation: structuredClone(expectation)
|
|
728
|
+
});
|
|
729
|
+
const afterCallback = now();
|
|
730
|
+
if (!Number.isSafeInteger(afterCallback) || afterCallback < finalNow || afterCallback >= options.deadlineMs)
|
|
731
|
+
throw failure("deadline");
|
|
732
|
+
}
|
|
733
|
+
return proof;
|
|
734
|
+
}
|
|
735
|
+
await sleep(Math.min(50, options.deadlineMs - nowMs));
|
|
736
|
+
}
|
|
737
|
+
} catch (error) {
|
|
738
|
+
if (error instanceof Error && error.message.startsWith("darwin_thoughts_custody:"))
|
|
739
|
+
throw error;
|
|
740
|
+
throw failure("exchange", error);
|
|
741
|
+
} finally {
|
|
742
|
+
removeOwned(published ? finalPath : stagingPath, owned);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
export {
|
|
746
|
+
canonicalDarwinThoughtsJson,
|
|
747
|
+
deriveDarwinThoughtsTargets,
|
|
748
|
+
parseDarwinThoughtsAuthority,
|
|
749
|
+
parseDarwinThoughtsRequest,
|
|
750
|
+
parseDarwinThoughtsResponse,
|
|
751
|
+
parseDarwinThoughtsSignedEnvelope,
|
|
752
|
+
requestFreshDarwinThoughtsBootstrap,
|
|
753
|
+
requestFreshDarwinThoughtsCustody,
|
|
754
|
+
verifyDarwinThoughtsCustodyResponse
|
|
755
|
+
};
|