witnora 0.13.4 → 0.13.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.
@@ -0,0 +1,333 @@
1
+ import { createDecipheriv, createHash, createPrivateKey, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomUUID } from "node:crypto";
2
+ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import { credentialsPath } from "./credentials.js";
6
+ import { saveRuntimeReferenceManifest } from "./runtime-sandbox-kit.js";
7
+ const SCHEMA_VERSION = "witnora.runtime_bootstrap.v0.1";
8
+ const ENVELOPE_ALGORITHM = "X25519-HKDF-SHA256-A256GCM";
9
+ const PROBE_SCOPES = ["outcomes:verify"];
10
+ const HKDF_INFO = "witnora:runtime-bootstrap:probe:v0.1";
11
+ export async function bootstrapLocalRuntime(input) {
12
+ const server = normalizeServer(input.server);
13
+ const directory = join(dirname(credentialsPath({ configHome: input.configHome })), "runtime-bootstrap", stableId(server, input.projectId, input.planId));
14
+ await mkdir(directory, { recursive: true, mode: 0o700 });
15
+ const runtimeSigningKeyPath = join(directory, "runtime-signing-private.pem");
16
+ const runtimePrivateKeyPem = await loadOrCreatePrivateKey(runtimeSigningKeyPath);
17
+ const runtimePublicKeyPem = createPublicKey(createPrivateKey(runtimePrivateKeyPem)).export({ type: "spki", format: "pem" }).toString();
18
+ const proposedModules = {
19
+ sandboxOrigin: exactLocalOrigin(input.modules.sandboxOrigin),
20
+ adapterDigestSha256: exactDigest(input.modules.adapterDigestSha256, "adapter"),
21
+ probeDigestSha256: exactDigest(input.modules.probeDigestSha256, "probe"),
22
+ fixtureContractDigestSha256: exactDigest(input.modules.fixtureContractDigestSha256, "fixture contract"),
23
+ };
24
+ const attemptPath = join(directory, "pending-attempt.json");
25
+ const attempt = await loadOrCreateAttempt(attemptPath, proposedModules);
26
+ const modules = {
27
+ adapterDigestSha256: attempt.modules.adapterDigestSha256,
28
+ probeDigestSha256: attempt.modules.probeDigestSha256,
29
+ fixtureContractDigestSha256: attempt.modules.fixtureContractDigestSha256,
30
+ };
31
+ const contract = {
32
+ schemaVersion: SCHEMA_VERSION,
33
+ attemptId: attempt.attemptId,
34
+ idempotencyKey: attempt.idempotencyKey,
35
+ runtimeIdentity: { publicKeyPem: runtimePublicKeyPem },
36
+ delivery: { publicKeyPem: attempt.deliveryPublicKeyPem },
37
+ modules,
38
+ };
39
+ const contractFingerprintSha256 = sha256(canonicalJson({
40
+ schemaVersion: SCHEMA_VERSION,
41
+ runtimeIdentity: contract.runtimeIdentity,
42
+ modules,
43
+ }));
44
+ const requestFetch = input.fetch ?? fetch;
45
+ const endpoint = `${server}/v1/projects/${encodeURIComponent(input.projectId)}/setup-plans/${encodeURIComponent(input.planId)}/runtime-bootstrap`;
46
+ const response = await requestFetch(endpoint, {
47
+ method: "POST",
48
+ headers: { authorization: `Bearer ${input.apiKey}`, "content-type": "application/json" },
49
+ body: JSON.stringify({ ...contract, contractFingerprintSha256 }),
50
+ });
51
+ const body = await response.json().catch(() => ({}));
52
+ if (!response.ok)
53
+ throw new Error(`Hosted Runtime bootstrap did not complete (HTTP ${response.status}).`);
54
+ const issued = validateResponse(body, contractFingerprintSha256, attempt.deliveryPublicKeyPem);
55
+ const probeCredentialPath = join(directory, "outcome-probe-api-key.txt");
56
+ const probeCredentialMetadataPath = join(directory, "outcome-probe-api-key.json");
57
+ const credential = issued.envelope
58
+ ? decryptEnvelope(issued.bootstrapId, issued.envelope, attempt.deliveryPrivateKeyPem)
59
+ : await existingCredential(probeCredentialPath, probeCredentialMetadataPath, issued.probeCredential.apiKeyId);
60
+ if (!credential)
61
+ throw new Error("Hosted Runtime bootstrap acknowledged delivery but the matching local probe credential is unavailable.");
62
+ if (credential.apiKeyId !== issued.probeCredential.apiKeyId || JSON.stringify(credential.scopes) !== JSON.stringify(PROBE_SCOPES)) {
63
+ throw new Error("Hosted Runtime bootstrap credential payload did not match its public binding.");
64
+ }
65
+ await atomicWrite(probeCredentialPath, Buffer.from(`${credential.secret}\n`));
66
+ await atomicWrite(probeCredentialMetadataPath, Buffer.from(`${JSON.stringify({ apiKeyId: credential.apiKeyId, scopes: credential.scopes })}\n`));
67
+ const references = {
68
+ bootstrapId: issued.bootstrapId,
69
+ mandateId: issued.mandate.id,
70
+ probeApiKeyId: issued.probeCredential.apiKeyId,
71
+ probeHostedCredentialHandle: pathToFileURL(probeCredentialPath).href,
72
+ runtimeIdentityId: issued.runtimeIdentity.id,
73
+ runtimeKeyId: issued.runtimeIdentity.keyId,
74
+ runtimeSigningKeyHandle: pathToFileURL(runtimeSigningKeyPath).href,
75
+ setupPlanId: input.planId,
76
+ sandboxOrigin: attempt.modules.sandboxOrigin,
77
+ adapterDigestSha256: modules.adapterDigestSha256,
78
+ probeDigestSha256: modules.probeDigestSha256,
79
+ fixtureContractDigestSha256: modules.fixtureContractDigestSha256,
80
+ };
81
+ if (issued.envelope)
82
+ await acknowledge(requestFetch, endpoint, input.apiKey, issued.bootstrapId, issued.envelope.digestSha256);
83
+ const manifestPath = join(dirname(credentialsPath({ configHome: input.configHome })), "runtime-references.json");
84
+ await saveRuntimeReferenceManifest(manifestPath, input.projectId, server, references);
85
+ await rm(attemptPath, { force: true });
86
+ return { references, bootstrapId: issued.bootstrapId };
87
+ }
88
+ async function acknowledge(requestFetch, endpoint, apiKey, bootstrapId, digest) {
89
+ const response = await requestFetch(`${endpoint}/${encodeURIComponent(bootstrapId)}/ack`, {
90
+ method: "POST",
91
+ headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
92
+ body: JSON.stringify({ schemaVersion: SCHEMA_VERSION, envelopeDigestSha256: digest }),
93
+ });
94
+ const body = await response.json().catch(() => ({}));
95
+ if (!response.ok || body.schemaVersion !== SCHEMA_VERSION || body.bootstrapId !== bootstrapId || body.acknowledged !== true || body.secretDelivered !== true) {
96
+ throw new Error(`Hosted Runtime bootstrap credential acknowledgement did not complete (HTTP ${response.status}).`);
97
+ }
98
+ }
99
+ async function loadOrCreateAttempt(path, modules) {
100
+ try {
101
+ const parsed = JSON.parse(await readFile(path, "utf8"));
102
+ if (parsed.schemaVersion !== SCHEMA_VERSION || !identifier(parsed.attemptId) || !identifier(parsed.idempotencyKey)
103
+ || exactLocalOrigin(parsed.modules?.sandboxOrigin) !== parsed.modules.sandboxOrigin
104
+ || !exactDigest(parsed.modules?.adapterDigestSha256, "adapter") || !exactDigest(parsed.modules?.probeDigestSha256, "probe")
105
+ || !exactDigest(parsed.modules?.fixtureContractDigestSha256, "fixture contract")) {
106
+ throw new Error("Pending Runtime bootstrap attempt does not match the local contract.");
107
+ }
108
+ const privateKey = createPrivateKey(parsed.deliveryPrivateKeyPem);
109
+ if (privateKey.asymmetricKeyType !== "x25519" || createPublicKey(privateKey).export({ type: "spki", format: "pem" }).toString() !== parsed.deliveryPublicKeyPem) {
110
+ throw new Error("Pending Runtime bootstrap delivery key is invalid.");
111
+ }
112
+ return parsed;
113
+ }
114
+ catch (error) {
115
+ if (error.code !== "ENOENT") {
116
+ try {
117
+ await rename(path, `${path}.invalid-${Date.now()}`);
118
+ }
119
+ catch {
120
+ throw new Error("Damaged pending Runtime bootstrap state could not be quarantined for safe rotation.");
121
+ }
122
+ }
123
+ }
124
+ const deliveryKeys = generateKeyPairSync("x25519");
125
+ const attempt = {
126
+ schemaVersion: SCHEMA_VERSION,
127
+ attemptId: `attempt-${randomUUID()}`,
128
+ idempotencyKey: `runtime-bootstrap-${randomUUID()}`,
129
+ deliveryPrivateKeyPem: deliveryKeys.privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
130
+ deliveryPublicKeyPem: deliveryKeys.publicKey.export({ type: "spki", format: "pem" }).toString(),
131
+ modules,
132
+ };
133
+ try {
134
+ await writePrivateExclusive(path, Buffer.from(`${JSON.stringify(attempt, null, 2)}\n`));
135
+ return attempt;
136
+ }
137
+ catch (error) {
138
+ if (error.code !== "EEXIST")
139
+ throw error;
140
+ return loadOrCreateAttempt(path, modules);
141
+ }
142
+ }
143
+ export async function automaticRuntimeReferencesUsable(references) {
144
+ if (!references.bootstrapId || !references.setupPlanId || !references.probeApiKeyId || !references.probeHostedCredentialHandle)
145
+ return false;
146
+ try {
147
+ const secretPath = fileURLToPath(new URL(references.probeHostedCredentialHandle));
148
+ const metadata = JSON.parse(await readFile(join(dirname(secretPath), "outcome-probe-api-key.json"), "utf8"));
149
+ return Boolean((await readFile(secretPath, "utf8")).trim()
150
+ && metadata.apiKeyId === references.probeApiKeyId
151
+ && JSON.stringify(metadata.scopes) === JSON.stringify(PROBE_SCOPES));
152
+ }
153
+ catch {
154
+ return false;
155
+ }
156
+ }
157
+ export async function verifyAutomaticRuntimeProbe(input) {
158
+ const { references } = input;
159
+ if (!references.bootstrapId || !references.setupPlanId || !references.probeApiKeyId || !references.probeHostedCredentialHandle) {
160
+ throw new Error("Automatic Runtime probe references are incomplete.");
161
+ }
162
+ const secret = (await readFile(fileURLToPath(new URL(references.probeHostedCredentialHandle)), "utf8")).trim();
163
+ if (!secret)
164
+ throw new Error("Automatic Runtime probe credential is unavailable.");
165
+ await verifyHostedProbeCredential({
166
+ projectId: input.projectId,
167
+ server: input.server,
168
+ setupPlanId: references.setupPlanId,
169
+ bootstrapId: references.bootstrapId,
170
+ probeApiKeyId: references.probeApiKeyId,
171
+ apiKey: secret,
172
+ fetch: input.fetch,
173
+ });
174
+ }
175
+ export async function verifyHostedProbeCredential(input) {
176
+ const response = await (input.fetch ?? fetch)(`${normalizeServer(input.server)}/v1/projects/${encodeURIComponent(input.projectId)}/setup-plans/${encodeURIComponent(input.setupPlanId)}/runtime-bootstrap/${encodeURIComponent(input.bootstrapId)}/probe-status`, {
177
+ headers: { authorization: `Bearer ${input.apiKey}` },
178
+ });
179
+ const body = await response.json().catch(() => ({}));
180
+ const credential = object(body.probeCredential);
181
+ if (!response.ok || body.schemaVersion !== SCHEMA_VERSION || body.bootstrapId !== input.bootstrapId
182
+ || credential.apiKeyId !== input.probeApiKeyId || credential.projectId !== input.projectId
183
+ || JSON.stringify(credential.scopes) !== JSON.stringify(PROBE_SCOPES) || credential.status !== "ACTIVE"
184
+ || (credential.expiresAt !== undefined && !futureDate(credential.expiresAt))) {
185
+ throw new Error(`Hosted Runtime probe preflight did not establish an active exact credential (HTTP ${response.status}).`);
186
+ }
187
+ }
188
+ async function loadOrCreatePrivateKey(path) {
189
+ try {
190
+ const existing = await readFile(path, "utf8");
191
+ if (createPrivateKey(existing).asymmetricKeyType !== "ed25519")
192
+ throw new Error("wrong key type");
193
+ return existing;
194
+ }
195
+ catch (error) {
196
+ if (error.code !== "ENOENT")
197
+ throw new Error("The private Runtime signing key is unreadable or invalid.");
198
+ }
199
+ const generated = generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }).toString();
200
+ try {
201
+ await writePrivateExclusive(path, Buffer.from(generated));
202
+ return generated;
203
+ }
204
+ catch (error) {
205
+ if (error.code !== "EEXIST")
206
+ throw error;
207
+ return loadOrCreatePrivateKey(path);
208
+ }
209
+ }
210
+ function validateResponse(value, fingerprint, deliveryPublicKeyPem) {
211
+ if (value.schemaVersion !== SCHEMA_VERSION)
212
+ throw new Error("Hosted Runtime bootstrap returned an unsupported schema.");
213
+ if (value.contractFingerprintSha256 !== fingerprint)
214
+ throw new Error("Hosted Runtime bootstrap fingerprint did not match the local contract.");
215
+ const bootstrapId = identifier(value.bootstrapId) ? value.bootstrapId : undefined;
216
+ const runtimeIdentity = object(value.runtimeIdentity);
217
+ const mandate = object(value.mandate);
218
+ const probeCredential = object(value.probeCredential);
219
+ if (!bootstrapId || !identifier(runtimeIdentity.id) || !identifier(runtimeIdentity.keyId) || !futureDate(runtimeIdentity.validUntil))
220
+ throw new Error("Hosted Runtime bootstrap identity binding was invalid.");
221
+ if (!identifier(mandate.id) || !exactDigest(mandate.digestSha256, "mandate") || !futureDate(mandate.expiresAt) || mandate.maxUses !== 10)
222
+ throw new Error("Hosted Runtime bootstrap mandate binding was invalid.");
223
+ if (!identifier(probeCredential.apiKeyId) || JSON.stringify(probeCredential.scopes) !== JSON.stringify(PROBE_SCOPES))
224
+ throw new Error("Hosted Runtime bootstrap probe credential was invalid.");
225
+ const envelope = probeCredential.sealedEnvelope === undefined ? undefined : validateEnvelope(object(probeCredential.sealedEnvelope), deliveryPublicKeyPem);
226
+ if ((envelope && probeCredential.secretDelivered !== false) || (!envelope && probeCredential.secretDelivered !== true)) {
227
+ throw new Error("Hosted Runtime bootstrap probe credential delivery state was invalid.");
228
+ }
229
+ return {
230
+ bootstrapId,
231
+ runtimeIdentity: { id: String(runtimeIdentity.id), keyId: String(runtimeIdentity.keyId) },
232
+ mandate: { id: String(mandate.id) },
233
+ probeCredential: { apiKeyId: String(probeCredential.apiKeyId) },
234
+ ...(envelope ? { envelope } : {}),
235
+ };
236
+ }
237
+ function validateEnvelope(value, deliveryPublicKeyPem) {
238
+ if (value.algorithm !== ENVELOPE_ALGORITHM || typeof value.ephemeralPublicKeyPem !== "string")
239
+ throw new Error("Hosted Runtime bootstrap envelope algorithm was invalid.");
240
+ try {
241
+ if (createPublicKey(value.ephemeralPublicKeyPem).asymmetricKeyType !== "x25519")
242
+ throw new Error();
243
+ }
244
+ catch {
245
+ throw new Error("Hosted Runtime bootstrap envelope public key was invalid.");
246
+ }
247
+ const clientPublicKeySha256 = sha256(createPublicKey(deliveryPublicKeyPem).export({ type: "spki", format: "der" }));
248
+ if (value.clientPublicKeySha256 !== clientPublicKeySha256)
249
+ throw new Error("Hosted Runtime bootstrap envelope was not bound to this client key.");
250
+ for (const field of ["iv", "ciphertext", "authTag"])
251
+ if (typeof value[field] !== "string" || !base64url(value[field]))
252
+ throw new Error("Hosted Runtime bootstrap envelope encoding was invalid.");
253
+ const digestPayload = {
254
+ algorithm: value.algorithm, ephemeralPublicKeyPem: value.ephemeralPublicKeyPem, iv: value.iv, ciphertext: value.ciphertext,
255
+ authTag: value.authTag, clientPublicKeySha256: value.clientPublicKeySha256,
256
+ };
257
+ if (value.digestSha256 !== sha256(JSON.stringify(digestPayload)))
258
+ throw new Error("Hosted Runtime bootstrap envelope digest was invalid.");
259
+ return value;
260
+ }
261
+ function decryptEnvelope(bootstrapId, envelope, deliveryPrivateKeyPem) {
262
+ const sharedSecret = diffieHellman({ privateKey: createPrivateKey(deliveryPrivateKeyPem), publicKey: createPublicKey(envelope.ephemeralPublicKeyPem) });
263
+ const key = Buffer.from(hkdfSync("sha256", sharedSecret, Buffer.from(bootstrapId), Buffer.from(HKDF_INFO), 32));
264
+ const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(envelope.iv, "base64url"));
265
+ decipher.setAAD(Buffer.from(bootstrapId));
266
+ decipher.setAuthTag(Buffer.from(envelope.authTag, "base64url"));
267
+ let plaintext;
268
+ try {
269
+ plaintext = Buffer.concat([decipher.update(Buffer.from(envelope.ciphertext, "base64url")), decipher.final()]);
270
+ }
271
+ catch {
272
+ throw new Error("Hosted Runtime bootstrap credential envelope authentication failed.");
273
+ }
274
+ let payload;
275
+ try {
276
+ payload = JSON.parse(plaintext.toString("utf8"));
277
+ }
278
+ catch {
279
+ throw new Error("Hosted Runtime bootstrap credential payload was invalid.");
280
+ }
281
+ if (!identifier(payload.apiKeyId) || typeof payload.secret !== "string" || !payload.secret || /[\r\n]/.test(payload.secret) || JSON.stringify(payload.scopes) !== JSON.stringify(PROBE_SCOPES)) {
282
+ throw new Error("Hosted Runtime bootstrap credential payload was invalid.");
283
+ }
284
+ return { apiKeyId: payload.apiKeyId, secret: payload.secret, scopes: [...PROBE_SCOPES] };
285
+ }
286
+ async function existingCredential(path, metadataPath, apiKeyId) {
287
+ try {
288
+ const [secret, metadata] = await Promise.all([readFile(path, "utf8"), readFile(metadataPath, "utf8")]);
289
+ const value = JSON.parse(metadata);
290
+ return value.apiKeyId === apiKeyId && secret.trim() && JSON.stringify(value.scopes) === JSON.stringify(PROBE_SCOPES)
291
+ ? { apiKeyId, secret: secret.trim(), scopes: [...PROBE_SCOPES] }
292
+ : undefined;
293
+ }
294
+ catch {
295
+ return undefined;
296
+ }
297
+ }
298
+ async function atomicWrite(path, bytes) {
299
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
300
+ const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
301
+ await writeFile(temporary, bytes, { mode: 0o600 });
302
+ await rename(temporary, path);
303
+ await chmod(path, 0o600).catch(() => undefined);
304
+ }
305
+ async function writePrivateExclusive(path, bytes) {
306
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
307
+ await writeFile(path, bytes, { mode: 0o600, flag: "wx" });
308
+ await chmod(path, 0o600).catch(() => undefined);
309
+ }
310
+ function exactDigest(value, label) { if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value))
311
+ throw new Error(`The ${label} digest was invalid.`); return value; }
312
+ function identifier(value) { return typeof value === "string" && /^[A-Za-z0-9._:-]{1,200}$/.test(value); }
313
+ function futureDate(value) { return typeof value === "string" && Number.isFinite(Date.parse(value)) && Date.parse(value) > Date.now(); }
314
+ function exactLocalOrigin(value) { const url = new URL(value); if (url.protocol !== "http:" || url.hostname !== "127.0.0.1" || !url.port || url.pathname !== "/" || url.search || url.hash)
315
+ throw new Error("Runtime bootstrap sandbox origin must be localhost-only."); return url.origin; }
316
+ function object(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : {}; }
317
+ function normalizeServer(value) { const url = new URL(value); if (url.protocol !== "https:" && url.hostname !== "127.0.0.1" && url.hostname !== "localhost")
318
+ throw new Error("Witnora server must use HTTPS."); return url.toString().replace(/\/$/, ""); }
319
+ function stableId(...values) { return sha256(canonicalJson(values)); }
320
+ function sha256(value) { return createHash("sha256").update(value).digest("hex"); }
321
+ function base64url(value) { return /^[A-Za-z0-9_-]+$/.test(value); }
322
+ function canonicalJson(value) { return JSON.stringify(canonical(value)); }
323
+ function canonical(value) {
324
+ if (value === null || typeof value === "string" || typeof value === "boolean")
325
+ return value;
326
+ if (typeof value === "number" && Number.isFinite(value))
327
+ return Object.is(value, -0) ? 0 : value;
328
+ if (Array.isArray(value))
329
+ return value.map(canonical);
330
+ if (value && typeof value === "object")
331
+ return Object.fromEntries(Object.keys(value).sort().filter((key) => value[key] !== undefined).map((key) => [key, canonical(value[key])]));
332
+ throw new Error("Runtime bootstrap contract contains a non-canonical value.");
333
+ }
@@ -0,0 +1,166 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ import { appendFile, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const CONTRACT = "witnora.local_sandbox_fixture.v0.1|127.0.0.1|GET,POST|/mock-state/:id|separate-read-write|persistent-state-audit|body<=8192";
7
+ export const LOCAL_SANDBOX_FIXTURE_CONTRACT_SHA256 = createHash("sha256").update(CONTRACT).digest("hex");
8
+ const MAX_BODY_BYTES = 8_192;
9
+ const MAX_AUDIT_BYTES = 10 * 1024 * 1024;
10
+ const MAX_RESOURCES = 1_000;
11
+ const RESOURCE = /^\/mock-state\/([A-Za-z0-9._:-]{1,128})$/;
12
+ const AUDIT = /^\/audit\/actions\/([A-Za-z0-9._:-]{1,256})\/sessions\/([A-Za-z0-9._:-]{1,256})$/;
13
+ export async function findAvailableRuntimeSandboxOrigin() {
14
+ const server = createServer();
15
+ await listen(server, 0);
16
+ const address = server.address();
17
+ if (!address || typeof address === "string")
18
+ throw new Error("Could not allocate a localhost sandbox port.");
19
+ await close(server);
20
+ return `http://127.0.0.1:${address.port}`;
21
+ }
22
+ export async function startRuntimeSandboxFixture(input) {
23
+ const origin = exactOrigin(input.origin);
24
+ if (input.contractSha256 !== LOCAL_SANDBOX_FIXTURE_CONTRACT_SHA256)
25
+ throw new Error("Local sandbox fixture contract digest does not match.");
26
+ const [readCredential, writeCredential, auditCredential] = await Promise.all([
27
+ secret(input.readCredentialHandle), secret(input.writeCredentialHandle), secret(input.auditCredentialHandle),
28
+ ]);
29
+ if (new Set([readCredential, writeCredential, auditCredential]).size !== 3)
30
+ throw new Error("Local sandbox fixture credentials must contain separate values.");
31
+ const statePath = join(input.directory, "state.json");
32
+ const auditPath = join(input.directory, "audit.jsonl");
33
+ await mkdir(input.directory, { recursive: true });
34
+ let state = await loadState(statePath);
35
+ let writes = 0;
36
+ let queue = Promise.resolve();
37
+ const server = createServer((request, response) => {
38
+ queue = queue.then(() => handle(request, response)).catch((error) => send(response, 500, { error: safeError(error) }));
39
+ });
40
+ async function handle(request, response) {
41
+ response.setHeader("content-type", "application/json");
42
+ response.setHeader("cache-control", "no-store");
43
+ if (!request.url || request.url.length > 512)
44
+ return send(response, 404, { error: "not_found" });
45
+ if (request.url === "/healthz" && request.method === "GET") {
46
+ if (request.headers["x-sandbox-read-credential"] !== readCredential)
47
+ return send(response, 401, { error: "unauthorized" });
48
+ return send(response, 200, { schemaVersion: "witnora.local_sandbox_fixture_health.v0.1", contractSha256: input.contractSha256, origin, writes });
49
+ }
50
+ const auditMatch = AUDIT.exec(request.url);
51
+ if (auditMatch && request.method === "GET") {
52
+ if (request.headers["x-sandbox-audit-credential"] !== auditCredential)
53
+ return send(response, 401, { error: "unauthorized" });
54
+ const record = await committedAudit(auditPath, auditMatch[1], auditMatch[2]);
55
+ return record ? send(response, 200, record) : send(response, 404, { error: "not_found" });
56
+ }
57
+ const match = RESOURCE.exec(request.url);
58
+ if (!match)
59
+ return send(response, 404, { error: "not_found" });
60
+ const resourceId = match[1];
61
+ if (request.method === "GET") {
62
+ if (request.headers["x-sandbox-read-credential"] !== readCredential)
63
+ return send(response, 401, { error: "unauthorized" });
64
+ const value = state[resourceId];
65
+ return value ? send(response, 200, value) : send(response, 404, { error: "not_found" });
66
+ }
67
+ if (request.method !== "POST")
68
+ return send(response, 405, { error: "method_not_allowed" });
69
+ if (request.headers["x-sandbox-write-credential"] !== writeCredential)
70
+ return send(response, 401, { error: "unauthorized" });
71
+ const body = await readBoundedJson(request);
72
+ const status = text(body.status, "status", 128);
73
+ const actionId = text(body.actionId, "actionId", 256);
74
+ const executionSessionId = text(body.executionSessionId, "executionSessionId", 256);
75
+ if (!(resourceId in state) && Object.keys(state).length >= MAX_RESOURCES)
76
+ throw new Error("sandbox_resource_limit_reached");
77
+ const auditBytes = await stat(auditPath).then((value) => value.size).catch(() => 0);
78
+ if (auditBytes >= MAX_AUDIT_BYTES)
79
+ throw new Error("sandbox_audit_limit_reached");
80
+ const transactionId = randomUUID();
81
+ const next = { resourceId, status, actionId, executionSessionId };
82
+ await appendFile(auditPath, `${JSON.stringify({ transactionId, phase: "PREPARED", resourceId, actionId, executionSessionId, at: new Date().toISOString() })}\n`, { mode: 0o600 });
83
+ state = { ...state, [resourceId]: next };
84
+ await atomicJson(statePath, state);
85
+ await appendFile(auditPath, `${JSON.stringify({ transactionId, phase: "COMMITTED", resourceId, actionId, executionSessionId, observedState: next, stateSha256: createHash("sha256").update(JSON.stringify(next)).digest("hex"), at: new Date().toISOString() })}\n`, { mode: 0o600 });
86
+ writes += 1;
87
+ return send(response, 200, next);
88
+ }
89
+ await listen(server, Number(new URL(origin).port));
90
+ return { origin, close: () => close(server) };
91
+ }
92
+ async function committedAudit(path, actionId, executionSessionId) {
93
+ let content;
94
+ try {
95
+ content = await readFile(path, "utf8");
96
+ }
97
+ catch (error) {
98
+ if (error.code === "ENOENT")
99
+ return undefined;
100
+ throw error;
101
+ }
102
+ if (Buffer.byteLength(content) > MAX_AUDIT_BYTES + MAX_BODY_BYTES)
103
+ throw new Error("sandbox_audit_limit_reached");
104
+ for (const line of content.trim().split("\n").reverse()) {
105
+ const record = JSON.parse(line);
106
+ if (record.phase === "COMMITTED" && record.actionId === actionId && record.executionSessionId === executionSessionId)
107
+ return record;
108
+ }
109
+ return undefined;
110
+ }
111
+ async function loadState(path) {
112
+ try {
113
+ const value = JSON.parse(await readFile(path, "utf8"));
114
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
115
+ }
116
+ catch (error) {
117
+ if (error.code === "ENOENT")
118
+ return {};
119
+ throw error;
120
+ }
121
+ }
122
+ async function atomicJson(path, value) {
123
+ await mkdir(dirname(path), { recursive: true });
124
+ const temporary = `${path}.${randomUUID()}.tmp`;
125
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
126
+ await rename(temporary, path);
127
+ }
128
+ async function readBoundedJson(request) {
129
+ const declared = Number(request.headers["content-length"] ?? 0);
130
+ if (declared > MAX_BODY_BYTES)
131
+ throw new Error("sandbox_body_too_large");
132
+ const chunks = [];
133
+ let size = 0;
134
+ for await (const chunk of request) {
135
+ size += Buffer.byteLength(chunk);
136
+ if (size > MAX_BODY_BYTES)
137
+ throw new Error("sandbox_body_too_large");
138
+ chunks.push(Buffer.from(chunk));
139
+ }
140
+ const value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
141
+ if (!value || typeof value !== "object" || Array.isArray(value))
142
+ throw new Error("sandbox_body_invalid");
143
+ return value;
144
+ }
145
+ async function secret(handle) {
146
+ const url = new URL(handle);
147
+ if (url.protocol !== "file:")
148
+ throw new Error("Local sandbox fixture supports file:// secret handles only.");
149
+ const value = (await readFile(fileURLToPath(url), "utf8")).trim();
150
+ if (!value)
151
+ throw new Error("Local sandbox fixture credential is empty.");
152
+ return value;
153
+ }
154
+ function exactOrigin(value) {
155
+ const url = new URL(value);
156
+ if (url.protocol !== "http:" || url.hostname !== "127.0.0.1" || !url.port || url.username || url.password || url.pathname !== "/" || url.search || url.hash)
157
+ throw new Error("Local sandbox fixture must use a bare 127.0.0.1 HTTP origin.");
158
+ return url.origin;
159
+ }
160
+ function text(value, name, max) { if (typeof value !== "string" || !value || value.length > max)
161
+ throw new Error(`sandbox_${name}_invalid`); return value; }
162
+ function safeError(error) { return error instanceof SyntaxError ? "sandbox_body_invalid" : error instanceof Error && /^sandbox_[a-z_]+$/.test(error.message) ? error.message : "sandbox_internal_error"; }
163
+ function send(response, status, body) { if (response.writableEnded)
164
+ return; response.statusCode = status; response.end(JSON.stringify(body)); }
165
+ function listen(server, port) { return new Promise((resolve, reject) => { server.once("error", reject); server.listen(port, "127.0.0.1", () => { server.off("error", reject); resolve(); }); }); }
166
+ function close(server) { return new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); }