@odla-ai/cli 0.13.0 → 0.14.0

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.
@@ -137,7 +137,7 @@ import process2 from "process";
137
137
  async function openUrl(url, options = {}) {
138
138
  const command = openerFor(options.platform ?? process2.platform);
139
139
  const doSpawn = options.spawnImpl ?? spawn;
140
- await new Promise((resolve7, reject) => {
140
+ await new Promise((resolve10, reject) => {
141
141
  const child = doSpawn(command.cmd, [...command.args, url], {
142
142
  stdio: "ignore",
143
143
  detached: true
@@ -145,7 +145,7 @@ async function openUrl(url, options = {}) {
145
145
  child.once("error", reject);
146
146
  child.once("spawn", () => {
147
147
  child.unref();
148
- resolve7();
148
+ resolve10();
149
149
  });
150
150
  });
151
151
  }
@@ -393,7 +393,7 @@ function audienceBoundEnvToken(token, platform) {
393
393
  async function scopedToken(platform, scope, options, doFetch, out) {
394
394
  const audience = platformAudience(platform);
395
395
  const tokenFile = options.tokenFile ?? `${process5.cwd()}/.odla/admin-token.local.json`;
396
- const cache = readJsonFile(tokenFile);
396
+ const cache = options.cache === false ? null : readJsonFile(tokenFile);
397
397
  const cached = cache?.platform === audience ? cache.tokens?.[scope] : void 0;
398
398
  if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
399
399
  out.log(`auth: using cached ${scope} grant (${tokenFile})`);
@@ -403,7 +403,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
403
403
  const { token, expiresAt } = await requestToken2({
404
404
  endpoint: audience,
405
405
  email,
406
- label: `odla CLI admin AI (${scope})`,
406
+ label: options.label ?? `odla CLI admin AI (${scope})`,
407
407
  scopes: [scope],
408
408
  fetch: doFetch,
409
409
  onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
@@ -414,11 +414,15 @@ async function scopedToken(platform, scope, options, doFetch, out) {
414
414
  }
415
415
  }
416
416
  });
417
- const tokens = cache?.platform === audience ? { ...cache.tokens ?? {} } : {};
418
- tokens[scope] = { token, expiresAt };
419
- if (existsSync2(`${process5.cwd()}/.git`)) ensureGitignore(process5.cwd(), [tokenFile]);
420
- writePrivateJson(tokenFile, { platform: audience, email, tokens });
421
- out.log(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
417
+ if (options.cache !== false) {
418
+ const tokens = cache?.platform === audience ? { ...cache.tokens ?? {} } : {};
419
+ tokens[scope] = { token, expiresAt };
420
+ if (existsSync2(`${process5.cwd()}/.git`)) ensureGitignore(process5.cwd(), [tokenFile]);
421
+ writePrivateJson(tokenFile, { platform: audience, email, tokens });
422
+ out.log(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
423
+ } else {
424
+ out.log(`auth: ${scope} grant is in memory only; its credential record remains in odla-ai/db`);
425
+ }
422
426
  return token;
423
427
  }
424
428
 
@@ -466,11 +470,11 @@ function adminAiAuditQuery(filters) {
466
470
  return `?limit=${filters.limit}`;
467
471
  }
468
472
  async function readAdminAiAudit(request) {
469
- const response = await request.fetch(`${request.platform}/registry/platform/ai-audit${request.query}`, {
473
+ const response2 = await request.fetch(`${request.platform}/registry/platform/ai-audit${request.query}`, {
470
474
  headers: request.headers
471
475
  });
472
- const body = await responseBody(response);
473
- if (!response.ok) throw new Error(apiError(response.status, body));
476
+ const body = await responseBody(response2);
477
+ if (!response2.ok) throw new Error(apiError(response2.status, body));
474
478
  if (request.json) {
475
479
  request.stdout.log(JSON.stringify(body, null, 2));
476
480
  return;
@@ -480,18 +484,18 @@ async function readAdminAiAudit(request) {
480
484
  for (const event of events) {
481
485
  const before = isRecord(event.oldPolicy) ? event.oldPolicy : void 0;
482
486
  const after = isRecord(event.newPolicy) ? event.newPolicy : void 0;
483
- const route = before && after ? `${String(before.provider)}/${String(before.model)}@v${String(before.version)} -> ${String(after.provider)}/${String(after.model)}@v${String(after.version)}` : "value not retained";
487
+ const route2 = before && after ? `${String(before.provider)}/${String(before.model)}@v${String(before.version)} -> ${String(after.provider)}/${String(after.model)}@v${String(after.version)}` : "value not retained";
484
488
  request.stdout.log([
485
489
  timestamp(event.createdAt),
486
490
  String(event.changeKind ?? ""),
487
491
  String(event.purpose ?? event.provider ?? ""),
488
- route,
492
+ route2,
489
493
  `${String(event.actorType ?? "")}:${String(event.actorId ?? "")}`
490
494
  ].join(" "));
491
495
  }
492
496
  }
493
- async function responseBody(response) {
494
- const text = await response.text();
497
+ async function responseBody(response2) {
498
+ const text = await response2.text();
495
499
  if (!text) return {};
496
500
  try {
497
501
  return JSON.parse(text);
@@ -501,8 +505,8 @@ async function responseBody(response) {
501
505
  }
502
506
  function apiError(status, body) {
503
507
  const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
504
- const message = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
505
- return `read System AI admin changes failed (${status}): ${message}`;
508
+ const message3 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
509
+ return `read System AI admin changes failed (${status}): ${message3}`;
506
510
  }
507
511
  function timestamp(value) {
508
512
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
@@ -594,8 +598,8 @@ async function responseBody2(res) {
594
598
  }
595
599
  function apiError2(action, status, body) {
596
600
  const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
597
- const message = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
598
- return `${action} failed (${status}): ${message}`;
601
+ const message3 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
602
+ return `${action} failed (${status}): ${message3}`;
599
603
  }
600
604
  function isRecord2(value) {
601
605
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -722,9 +726,9 @@ async function adminAi(options) {
722
726
  validation: validationUpdate
723
727
  })
724
728
  });
725
- const response2 = await responseBody3(res2);
726
- if (!res2.ok) throw new Error(apiError3("update security review routes", res2.status, response2));
727
- out.log(options.json ? JSON.stringify(response2, null, 2) : "updated security review discovery and independent validation routes atomically");
729
+ const response3 = await responseBody3(res2);
730
+ if (!res2.ok) throw new Error(apiError3("update security review routes", res2.status, response3));
731
+ out.log(options.json ? JSON.stringify(response3, null, 2) : "updated security review discovery and independent validation routes atomically");
728
732
  return;
729
733
  }
730
734
  const purpose = requireSystemAiPurpose(options.purpose);
@@ -742,9 +746,9 @@ async function adminAi(options) {
742
746
  headers,
743
747
  body: JSON.stringify(body)
744
748
  });
745
- const response = await responseBody3(res);
746
- if (!res.ok) throw new Error(apiError3(`update ${purpose}`, res.status, response));
747
- const policy = isRecord3(response) && isRecord3(response.policy) ? response.policy : isRecord3(response) ? response : {};
749
+ const response2 = await responseBody3(res);
750
+ if (!res.ok) throw new Error(apiError3(`update ${purpose}`, res.status, response2));
751
+ const policy = isRecord3(response2) && isRecord3(response2.policy) ? response2.policy : isRecord3(response2) ? response2 : {};
748
752
  out.log(options.json ? JSON.stringify({ policy }, null, 2) : `updated ${purpose}: ${String(policy.provider ?? "unchanged")}/${String(policy.model ?? "unchanged")}`);
749
753
  }
750
754
  function requireProvider(value) {
@@ -775,8 +779,8 @@ async function responseBody3(res) {
775
779
  }
776
780
  function apiError3(action, status, body) {
777
781
  const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
778
- const message = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
779
- return `${action} failed (${status}): ${message}`;
782
+ const message3 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
783
+ return `${action} failed (${status}): ${message3}`;
780
784
  }
781
785
  function isRecord3(value) {
782
786
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -1072,6 +1076,7 @@ var CAPABILITIES = {
1072
1076
  "start an email-bound device request without accepting a password or user session token",
1073
1077
  "register the app and enable configured services per environment",
1074
1078
  "issue, persist, and explicitly rotate configured service credentials",
1079
+ "add or remove app co-owners, each of whom self-provisions their own credentials for the shared db without any secret handoff",
1075
1080
  "write local Worker values and push deployed Worker secrets through Wrangler stdin",
1076
1081
  "store tenant-vault secrets (including the Clerk webhook secret and reserved Clerk secret key) write-only from stdin or an env var",
1077
1082
  "compose declared app-capability schema/rules, create guarded seeds when absent, and configure platform AI, auth, and deployment links",
@@ -1100,13 +1105,13 @@ var CAPABILITIES = {
1100
1105
  "view telemetry and environment state",
1101
1106
  "let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
1102
1107
  "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
1103
- "perform manual credential recovery when the CLI's local shown-once copy is unavailable",
1108
+ "perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
1104
1109
  "configure system AI purposes and view app/environment/run-attributed usage",
1105
1110
  "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
1106
1111
  ]
1107
1112
  };
1108
- function printCapabilities(json = false, out = console) {
1109
- if (json) {
1113
+ function printCapabilities(json2 = false, out = console) {
1114
+ if (json2) {
1110
1115
  out.log(JSON.stringify(CAPABILITIES, null, 2));
1111
1116
  return;
1112
1117
  }
@@ -1155,8 +1160,8 @@ var CalendarRequestError = class extends Error {
1155
1160
  status;
1156
1161
  /** Machine-readable `error.code` from the response body, when present. */
1157
1162
  code;
1158
- constructor(message, status, code) {
1159
- super(message);
1163
+ constructor(message3, status, code) {
1164
+ super(message3);
1160
1165
  this.name = "CalendarRequestError";
1161
1166
  this.status = status;
1162
1167
  if (code !== void 0) this.code = code;
@@ -1290,9 +1295,9 @@ async function calendarJson(ctx, suffix, init) {
1290
1295
  const url = new URL(`/registry/apps/${encodeURIComponent(appId)}/calendar${suffix}`, platform);
1291
1296
  url.searchParams.set("env", env);
1292
1297
  const token = credential(ctx.token);
1293
- let response;
1298
+ let response2;
1294
1299
  try {
1295
- response = await (ctx.fetch ?? fetch)(url, {
1300
+ response2 = await (ctx.fetch ?? fetch)(url, {
1296
1301
  ...init,
1297
1302
  redirect: "error",
1298
1303
  credentials: "omit",
@@ -1301,12 +1306,12 @@ async function calendarJson(ctx, suffix, init) {
1301
1306
  } catch (error) {
1302
1307
  throw new Error(`calendar request failed: ${error instanceof Error ? error.message : String(error)}`);
1303
1308
  }
1304
- const body = await response.json().catch(() => ({}));
1305
- if (!response.ok) {
1309
+ const body = await response2.json().catch(() => ({}));
1310
+ if (!response2.ok) {
1306
1311
  const code = textField(body.error?.code, 128);
1307
- const message = textField(body.error?.message, 500);
1308
- const detail = `${code ? ` ${code}` : ""}${message ? `: ${message}` : ""}`;
1309
- throw new CalendarRequestError(redactSecrets(`calendar request failed (${response.status})${detail}`), response.status, code);
1312
+ const message3 = textField(body.error?.message, 500);
1313
+ const detail = `${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`;
1314
+ throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
1310
1315
  }
1311
1316
  return body;
1312
1317
  }
@@ -1364,8 +1369,8 @@ function credential(value) {
1364
1369
  // src/calendar-poll.ts
1365
1370
  async function waitForCalendarPoll(milliseconds, signal) {
1366
1371
  if (signal?.aborted) throw signal.reason ?? new Error("calendar connection aborted");
1367
- await new Promise((resolve7, reject) => {
1368
- const timer = setTimeout(resolve7, milliseconds);
1372
+ await new Promise((resolve10, reject) => {
1373
+ const timer = setTimeout(resolve10, milliseconds);
1369
1374
  signal?.addEventListener("abort", () => {
1370
1375
  clearTimeout(timer);
1371
1376
  reject(signal.reason ?? new Error("calendar connection aborted"));
@@ -1467,7 +1472,7 @@ async function continueConnectedCalendar(ctx, current, options) {
1467
1472
  if (current.status !== "authorizing") return null;
1468
1473
  const now = options.now ?? Date.now;
1469
1474
  const deadline = now() + pollTimeout(options.pollTimeoutMs);
1470
- const wait = options.wait ?? waitForCalendarPoll;
1475
+ const wait2 = options.wait ?? waitForCalendarPoll;
1471
1476
  let prior = "";
1472
1477
  while (now() < deadline) {
1473
1478
  if (options.signal?.aborted) throw options.signal.reason ?? new Error("calendar connection aborted");
@@ -1483,7 +1488,7 @@ async function continueConnectedCalendar(ctx, current, options) {
1483
1488
  throw new Error(`calendar connection failed${reason ? `: ${reason}` : ""}; inspect status and reconnect explicitly`);
1484
1489
  }
1485
1490
  if (status.status !== "authorizing") return null;
1486
- await wait(Math.min(2e3, Math.max(0, deadline - now())), options.signal);
1491
+ await wait2(Math.min(2e3, Math.max(0, deadline - now())), options.signal);
1487
1492
  }
1488
1493
  throw new Error('Existing Google Calendar authorization timed out; run "odla-ai calendar status" and retry');
1489
1494
  }
@@ -1498,7 +1503,7 @@ async function connectWithContext(ctx, options) {
1498
1503
  const now = options.now ?? Date.now;
1499
1504
  const timeout = pollTimeout(options.pollTimeoutMs);
1500
1505
  const deadline = Math.min(attempt.expiresAt, now() + timeout);
1501
- const wait = options.wait ?? waitForCalendarPoll;
1506
+ const wait2 = options.wait ?? waitForCalendarPoll;
1502
1507
  let prior = "";
1503
1508
  while (now() < deadline) {
1504
1509
  if (options.signal?.aborted) throw options.signal.reason ?? new Error("calendar connection aborted");
@@ -1515,7 +1520,7 @@ async function connectWithContext(ctx, options) {
1515
1520
  const reason = status.error?.message ?? status.error?.code;
1516
1521
  throw new Error(`calendar connection ${status.status}${reason ? `: ${reason}` : ""}`);
1517
1522
  }
1518
- await wait(Math.min(2e3, Math.max(0, deadline - now())), options.signal);
1523
+ await wait2(Math.min(2e3, Math.max(0, deadline - now())), options.signal);
1519
1524
  }
1520
1525
  throw new Error('Google Calendar consent timed out; run "odla-ai calendar status" and retry connect');
1521
1526
  }
@@ -1541,8 +1546,8 @@ function pollTimeout(value = 10 * 6e4) {
1541
1546
  function productionConsent(env, yes, action) {
1542
1547
  if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
1543
1548
  }
1544
- function printStatus(status, json, out) {
1545
- if (json) {
1549
+ function printStatus(status, json2, out) {
1550
+ if (json2) {
1546
1551
  out.log(JSON.stringify(status, null, 2));
1547
1552
  return;
1548
1553
  }
@@ -1554,224 +1559,3163 @@ function printStatus(status, json, out) {
1554
1559
  if (status.error?.code || status.error?.message) out.log(` error: ${status.error.code ?? "error"}${status.error.message ? ` \u2014 ${status.error.message}` : ""}`);
1555
1560
  }
1556
1561
 
1557
- // src/doctor-checks.ts
1558
- import { execFileSync } from "child_process";
1559
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1560
- import { join as join3, resolve as resolve3 } from "path";
1562
+ // src/security-hosted-github.ts
1563
+ import { execFile } from "child_process";
1564
+ import { promisify } from "util";
1561
1565
 
1562
- // src/wrangler.ts
1563
- import { spawn as spawn2 } from "child_process";
1564
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1565
- import { join as join2 } from "path";
1566
- var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
1567
- const child = spawn2(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
1568
- let stdout = "";
1569
- let stderr = "";
1570
- child.stdout.on("data", (chunk) => stdout += chunk.toString());
1571
- child.stderr.on("data", (chunk) => stderr += chunk.toString());
1572
- child.on("error", reject);
1573
- child.on("close", (code) => resolvePromise({ code: code ?? 1, stdout, stderr }));
1574
- child.stdin.end(opts?.input ?? "");
1575
- });
1576
- var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
1577
- function findWranglerConfig(rootDir) {
1578
- for (const name of WRANGLER_CONFIG_FILES) {
1579
- const path = join2(rootDir, name);
1580
- if (existsSync4(path)) return path;
1581
- }
1582
- return null;
1583
- }
1584
- function readWranglerConfig(path) {
1585
- if (path.endsWith(".toml")) return null;
1586
- try {
1587
- return JSON.parse(stripJsonComments(readFileSync3(path, "utf8")));
1588
- } catch {
1589
- return null;
1566
+ // src/security-hosted-request.ts
1567
+ async function requestHostedSecurityJson(options, path, init, action) {
1568
+ const platform = platformAudience(options.platform);
1569
+ const token = hostedSecurityCredential(options.token);
1570
+ const requestInit = {
1571
+ ...init,
1572
+ redirect: "error",
1573
+ credentials: "omit",
1574
+ cache: "no-store",
1575
+ signal: options.signal,
1576
+ headers: {
1577
+ authorization: `Bearer ${token}`,
1578
+ "content-type": "application/json",
1579
+ ...init.headers
1580
+ }
1581
+ };
1582
+ const response2 = await (options.fetch ?? fetch)(new URL(path, platform), requestInit);
1583
+ const body = await response2.json().catch(() => ({}));
1584
+ if (!response2.ok) {
1585
+ const code = optionalHostedText(body.error?.code, "error code", 128);
1586
+ const message3 = optionalHostedText(body.error?.message, "error message", 300);
1587
+ throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`);
1590
1588
  }
1589
+ return body;
1591
1590
  }
1592
- function stripJsonComments(text) {
1593
- let result = "";
1594
- let inString = false;
1595
- for (let i = 0; i < text.length; i++) {
1596
- const ch = text[i];
1597
- if (inString) {
1598
- result += ch;
1599
- if (ch === "\\") {
1600
- result += text[i + 1] ?? "";
1601
- i++;
1602
- } else if (ch === '"') {
1603
- inString = false;
1604
- }
1605
- continue;
1606
- }
1607
- if (ch === '"') {
1608
- inString = true;
1609
- result += ch;
1610
- continue;
1611
- }
1612
- if (ch === "/" && text[i + 1] === "/") {
1613
- while (i < text.length && text[i] !== "\n") i++;
1614
- result += "\n";
1615
- continue;
1616
- }
1617
- if (ch === "/" && text[i + 1] === "*") {
1618
- i += 2;
1619
- while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
1620
- i++;
1621
- continue;
1622
- }
1623
- result += ch;
1591
+ function hostedIdentifier(value, name) {
1592
+ const result = optionalHostedText(value, name, 180);
1593
+ if (!result || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(result)) {
1594
+ throw new Error(`${name} is invalid`);
1624
1595
  }
1625
1596
  return result;
1626
1597
  }
1627
- async function wranglerLoggedIn(run, cwd) {
1628
- try {
1629
- const result = await run("npx", ["wrangler", "whoami"], { cwd });
1630
- return result.code === 0 && !/not authenticated/i.test(`${result.stdout}${result.stderr}`);
1631
- } catch {
1632
- return false;
1598
+ function positivePolicyVersion(value, name) {
1599
+ if (!Number.isSafeInteger(value) || value < 1) {
1600
+ throw new Error(`${name} is invalid`);
1633
1601
  }
1602
+ return value;
1634
1603
  }
1635
- function wranglerPutSecret(run, opts) {
1636
- const args = ["wrangler", "secret", "put", opts.name, ...opts.env ? ["--env", opts.env] : []];
1637
- return run("npx", args, { input: opts.value, cwd: opts.cwd });
1604
+ function optionalHostedText(value, name, maxLength) {
1605
+ if (value === void 0 || value === null || value === "") return void 0;
1606
+ if (typeof value !== "string" || value.length > maxLength || /[\u0000-\u001f\u007f]/.test(value)) {
1607
+ throw new Error(`${name} is invalid`);
1608
+ }
1609
+ return value;
1638
1610
  }
1639
-
1640
- // src/doctor-checks.ts
1641
- function lintRules(rules, entities, publicRead) {
1642
- const warnings = [];
1643
- if (!rules) return warnings;
1644
- for (const [ns, actions] of Object.entries(rules)) {
1645
- for (const [action, expr] of Object.entries(actions)) {
1646
- if (typeof expr !== "string" || expr.trim() !== "true") continue;
1647
- if (action === "view" && publicRead.includes(ns)) continue;
1648
- warnings.push(
1649
- action === "view" ? `rules.${ns}.view is "true" \u2014 public read; add "${ns}" to db.publicRead if intended` : `rules.${ns}.${action} is "true" \u2014 any client can ${action} ${ns} rows`
1650
- );
1651
- }
1611
+ function githubRepositoryName(value) {
1612
+ if (typeof value !== "string" || value.length > 201) {
1613
+ throw new Error("repository must be owner/name");
1652
1614
  }
1653
- for (const entity of entities) {
1654
- if (!(entity in rules)) warnings.push(`schema entity "${entity}" has no rules entry (all client access denied)`);
1615
+ const parts = value.split("/");
1616
+ const owner = parts[0] ?? "";
1617
+ const name = (parts[1] ?? "").replace(/\.git$/i, "");
1618
+ if (parts.length !== 2 || !/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,98}[A-Za-z0-9])?$/.test(owner) || !/^[A-Za-z0-9_.-]{1,100}$/.test(name) || name === "." || name === "..") {
1619
+ throw new Error("repository must be owner/name");
1655
1620
  }
1656
- return warnings;
1621
+ return `${owner}/${name}`;
1657
1622
  }
1658
- var defaultExec = (cmd, args, cwd) => execFileSync(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
1659
- function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
1660
- let output;
1623
+ function trustedGitHubInstallUrl(value) {
1624
+ let url;
1661
1625
  try {
1662
- const custom = localPaths.map((path) => gitignoreEntry(rootDir, path)).filter((path) => !!path);
1663
- output = exec("git", ["ls-files", "--", ".dev.vars", ".odla", ...custom], rootDir);
1626
+ url = new URL(value);
1664
1627
  } catch {
1665
- return [];
1666
- }
1667
- return output.split(/\r?\n/).filter(Boolean).map((path) => `${path} is tracked by git \u2014 run "git rm --cached ${path}" and commit; it belongs in .gitignore`);
1668
- }
1669
- function wranglerWarnings(rootDir) {
1670
- const configPath = findWranglerConfig(rootDir);
1671
- if (!configPath) return [];
1672
- const config = readWranglerConfig(configPath);
1673
- if (!config) return [];
1674
- const warnings = [];
1675
- const blocks = [{ label: "", block: config }];
1676
- const envs = config.env;
1677
- if (envs && typeof envs === "object") {
1678
- for (const [name, block] of Object.entries(envs)) {
1679
- if (block && typeof block === "object") blocks.push({ label: `env.${name}.`, block });
1680
- }
1681
- }
1682
- for (const { label, block } of blocks) {
1683
- const assets = block.assets;
1684
- if (assets?.directory) {
1685
- const dir = resolve3(rootDir, assets.directory);
1686
- if (dir === resolve3(rootDir)) {
1687
- warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
1688
- } else if (existsSync5(join3(dir, "node_modules"))) {
1689
- warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
1690
- }
1691
- }
1692
- const vars = block.vars;
1693
- if (vars && typeof vars === "object") {
1694
- for (const [name, value] of Object.entries(vars)) {
1695
- if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value === "string" && looksSecret(value)) {
1696
- warnings.push(`wrangler ${label}vars.${name} looks like a secret \u2014 use "odla-ai secrets push" / wrangler secret put, never vars`);
1697
- }
1698
- }
1699
- }
1628
+ throw new Error("odla.ai returned an invalid GitHub installation URL");
1700
1629
  }
1701
- const pkg = readPackageJson(rootDir);
1702
- if (pkg && typeof pkg.scripts === "object" && pkg.scripts && "deploy" in pkg.scripts) {
1703
- warnings.push(`package.json has a script named "deploy" \u2014 CI may auto-deploy it; rename to deploy:app`);
1630
+ if (url.protocol !== "https:" || url.hostname !== "github.com" || url.username || url.password) {
1631
+ throw new Error("odla.ai returned an untrusted GitHub installation URL");
1704
1632
  }
1705
- return warnings;
1633
+ return url.toString();
1706
1634
  }
1707
- function o11yProjectWarnings(rootDir) {
1708
- const warnings = [];
1709
- const pkg = readPackageJson(rootDir);
1710
- const dependencies = pkg?.dependencies;
1711
- const devDependencies = pkg?.devDependencies;
1712
- if (!dependencies?.["@odla-ai/o11y"]) {
1713
- warnings.push(
1714
- devDependencies?.["@odla-ai/o11y"] ? "@odla-ai/o11y is a devDependency \u2014 move it to dependencies so the Worker can import it" : 'Worker instrumentation is missing @odla-ai/o11y \u2014 install it and wrap the handler with "withObservability"'
1715
- );
1716
- }
1717
- const configPath = findWranglerConfig(rootDir);
1718
- const config = configPath ? readWranglerConfig(configPath) : null;
1719
- if (!configPath || !config) {
1720
- warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
1721
- return warnings;
1722
- }
1723
- const main = typeof config.main === "string" ? resolve3(rootDir, config.main) : null;
1724
- if (!main || !existsSync5(main)) {
1725
- warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
1726
- } else {
1727
- let source = "";
1728
- try {
1729
- source = readFileSync4(main, "utf8");
1730
- } catch {
1731
- }
1732
- if (!/\bwithObservability\b/.test(source)) {
1733
- warnings.push(`wrangler entrypoint ${config.main} does not reference withObservability`);
1734
- }
1635
+ function hostedPollInterval(value = 2e3) {
1636
+ if (!Number.isSafeInteger(value) || value < 100 || value > 3e4) {
1637
+ throw new Error("poll interval must be 100-30000ms");
1735
1638
  }
1736
- const flags = Array.isArray(config.compatibility_flags) ? config.compatibility_flags : [];
1737
- if (!flags.includes("nodejs_compat")) {
1738
- warnings.push('wrangler compatibility_flags is missing "nodejs_compat" required by @odla-ai/o11y');
1639
+ return value;
1640
+ }
1641
+ function hostedPollTimeout(value = 10 * 6e4) {
1642
+ if (!Number.isSafeInteger(value) || value < 1e3 || value > 60 * 6e4) {
1643
+ throw new Error("poll timeout must be 1000-3600000ms");
1739
1644
  }
1740
- return warnings;
1645
+ return value;
1741
1646
  }
1742
- function calendarProjectWarnings(rootDir) {
1743
- const pkg = readPackageJson(rootDir);
1744
- const dependencies = pkg?.dependencies;
1745
- const devDependencies = pkg?.devDependencies;
1746
- if (dependencies?.["@odla-ai/calendar"]) return [];
1747
- return [
1748
- devDependencies?.["@odla-ai/calendar"] ? "@odla-ai/calendar is a devDependency \u2014 move it to dependencies so trusted Worker code can import it" : "calendar service is enabled but @odla-ai/calendar is not installed in dependencies"
1749
- ];
1647
+ async function waitForHostedPoll(milliseconds, signal) {
1648
+ if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
1649
+ await new Promise((resolve10, reject) => {
1650
+ const timer = setTimeout(resolve10, milliseconds);
1651
+ signal?.addEventListener("abort", () => {
1652
+ clearTimeout(timer);
1653
+ reject(signal.reason ?? new DOMException("aborted", "AbortError"));
1654
+ }, { once: true });
1655
+ });
1750
1656
  }
1751
- function readPackageJson(rootDir) {
1752
- try {
1753
- return JSON.parse(readFileSync4(join3(rootDir, "package.json"), "utf8"));
1754
- } catch {
1755
- return null;
1657
+ function isValidHostedSecurityPlan(value, env) {
1658
+ if (!value || value.env !== env || !/^sha256:[a-f0-9]{64}$/.test(value.planDigest) || value.consentContract !== "odla.hosted-security-consent.v1" || value.reportProjection !== "odla.hosted-security-report.v1" || value.redactionContract !== "odla.best-effort-credential-pattern-redaction.v1" || typeof value.promptBundle !== "string" || value.promptBundle.length < 1 || value.promptBundle.length > 100 || typeof value.ready !== "boolean" || typeof value.independent !== "boolean" || value.sourceDisclosure !== "redacted" || value.targetExecution !== false || !Number.isSafeInteger(value.reportRetentionDays) || value.reportRetentionDays < 1) return false;
1659
+ const validRoute = (route2, purpose) => !!route2 && route2.purpose === purpose && typeof route2.enabled === "boolean" && typeof route2.credentialReady === "boolean" && typeof route2.provider === "string" && route2.provider.length > 0 && route2.provider.length <= 100 && typeof route2.model === "string" && route2.model.length > 0 && route2.model.length <= 200 && Number.isSafeInteger(route2.policyVersion) && route2.policyVersion >= 1 && Number.isSafeInteger(route2.maxCallsPerRun) && route2.maxCallsPerRun >= 1 && Number.isSafeInteger(route2.maxInputBytes) && route2.maxInputBytes >= 1 && Number.isSafeInteger(route2.maxOutputTokens) && route2.maxOutputTokens >= 1;
1660
+ return validRoute(value.routes?.discovery, "security.discovery") && validRoute(value.routes?.validation, "security.validation");
1661
+ }
1662
+ function hostedSecurityCredential(value) {
1663
+ if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
1664
+ throw new Error("Hosted security requires an injected odla developer token");
1756
1665
  }
1666
+ return value;
1757
1667
  }
1758
1668
 
1759
- // src/integrations.ts
1760
- import { isDeepStrictEqual } from "util";
1761
- async function resolveDatabaseConfig(cfg, options = {}) {
1762
- const integrations = cfg.integrations ?? [];
1763
- if (!cfg.services.includes("db")) return { schema: void 0, rules: void 0, integrations };
1764
- const authoredSchema = await resolveDataExport(cfg, cfg.db?.schema, ["schema", "SCHEMA"]);
1765
- const schema = mergeSchemas(authoredSchema, integrations);
1766
- if (options.validateSeeds !== false) assertSeedContracts(integrations, schema);
1767
- const configuredRules = await resolveDataExport(cfg, cfg.db?.rules, ["rules", "RULES"]);
1768
- const explicitRules = mergeRules(configuredRules, integrations);
1769
- const rules = configuredRules === void 0 && schema && cfg.db?.defaultRules !== false ? { ...rulesFromSchema(schema), ...explicitRules ?? {} } : explicitRules;
1770
- return { schema, rules, integrations };
1771
- }
1772
- function integrationWarnings(integrations, schema, rules) {
1773
- const warnings = [];
1774
- const entities = new Set(serializedEntities(schema));
1669
+ // src/security-hosted-github.ts
1670
+ async function connectGitHubSecuritySource(options) {
1671
+ const out = options.stdout ?? console;
1672
+ const repository = githubRepositoryName(options.repository);
1673
+ const attempt = await requestHostedSecurityJson(
1674
+ options,
1675
+ "/registry/github/connect",
1676
+ {
1677
+ method: "POST",
1678
+ body: JSON.stringify({
1679
+ appId: hostedIdentifier(options.appId, "appId"),
1680
+ env: hostedIdentifier(options.env, "env"),
1681
+ repository
1682
+ })
1683
+ },
1684
+ "start GitHub connection"
1685
+ );
1686
+ const serverExpiry = Date.parse(attempt?.expiresAt ?? "");
1687
+ if (!attempt?.attemptId || !attempt.installUrl || !Number.isFinite(serverExpiry)) {
1688
+ throw new Error("odla.ai returned an invalid GitHub connection attempt");
1689
+ }
1690
+ const installUrl = trustedGitHubInstallUrl(attempt.installUrl);
1691
+ out.log(`GitHub approval: ${installUrl}`);
1692
+ if (options.open !== false) {
1693
+ await (options.openInstallUrl ?? openUrl)(installUrl);
1694
+ out.log("github: opened installation approval in your browser");
1695
+ }
1696
+ const interval = hostedPollInterval(options.pollIntervalMs);
1697
+ const now = options.now ?? Date.now;
1698
+ const deadline = Math.min(serverExpiry, now() + hostedPollTimeout(options.pollTimeoutMs));
1699
+ const wait2 = options.wait ?? waitForHostedPoll;
1700
+ while (true) {
1701
+ const state = await requestHostedSecurityJson(
1702
+ options,
1703
+ `/registry/github/connect/${encodeURIComponent(attempt.attemptId)}`,
1704
+ {},
1705
+ "check GitHub connection"
1706
+ );
1707
+ if (state.status !== "pending") {
1708
+ if (state.status === "connected") return state;
1709
+ throw new Error(`GitHub connection ${state.status}${state.failureCode ? `: ${state.failureCode}` : ""}`);
1710
+ }
1711
+ if (now() >= deadline) throw new Error("GitHub connection approval timed out");
1712
+ await wait2(Math.min(interval, Math.max(0, deadline - now())), options.signal);
1713
+ }
1714
+ }
1715
+ async function listGitHubSecuritySources(options) {
1716
+ const appId = hostedIdentifier(options.appId, "appId");
1717
+ const env = hostedIdentifier(options.env, "env");
1718
+ const body = await requestHostedSecurityJson(
1719
+ options,
1720
+ `/registry/apps/${encodeURIComponent(appId)}/github/sources?env=${encodeURIComponent(env)}`,
1721
+ {},
1722
+ "list GitHub security sources"
1723
+ );
1724
+ return Array.isArray(body.sources) ? body.sources : [];
1725
+ }
1726
+ async function disconnectGitHubSecuritySource(options) {
1727
+ const appId = hostedIdentifier(options.appId, "appId");
1728
+ const sourceId = hostedIdentifier(options.sourceId, "sourceId");
1729
+ await requestHostedSecurityJson(
1730
+ options,
1731
+ `/registry/apps/${encodeURIComponent(appId)}/github/sources/${encodeURIComponent(sourceId)}`,
1732
+ { method: "DELETE" },
1733
+ "disconnect GitHub security source"
1734
+ );
1735
+ }
1736
+ function repositoryFromGitRemote(remoteInput) {
1737
+ const remote = remoteInput.trim();
1738
+ const scp = /^git@github\.com:([^/\s]+)\/([^/\s]+?)\/?$/.exec(remote);
1739
+ if (scp) return githubRepositoryName(`${scp[1]}/${scp[2].replace(/\.git$/, "")}`);
1740
+ let url;
1741
+ try {
1742
+ url = new URL(remote);
1743
+ } catch {
1744
+ throw new Error("origin must be a github.com HTTPS or SSH repository URL");
1745
+ }
1746
+ if (url.hostname !== "github.com" || url.protocol !== "https:" && url.protocol !== "ssh:") {
1747
+ throw new Error("origin must be a github.com HTTPS or SSH repository URL");
1748
+ }
1749
+ if (url.password || url.protocol === "https:" && url.username || url.search || url.hash) {
1750
+ throw new Error("credential-bearing GitHub origin URLs are not accepted");
1751
+ }
1752
+ const parts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
1753
+ if (parts.length !== 2) {
1754
+ throw new Error("origin must identify one GitHub owner/name repository");
1755
+ }
1756
+ return githubRepositoryName(`${parts[0]}/${parts[1].replace(/\.git$/, "")}`);
1757
+ }
1758
+ async function inferGitHubRepository(cwd = process.cwd(), readOrigin = defaultReadOrigin) {
1759
+ let remote;
1760
+ try {
1761
+ remote = await readOrigin(cwd);
1762
+ } catch {
1763
+ throw new Error("Could not read git origin; pass --repo owner/name");
1764
+ }
1765
+ return repositoryFromGitRemote(remote);
1766
+ }
1767
+ async function defaultReadOrigin(cwd) {
1768
+ const result = await promisify(execFile)(
1769
+ "git",
1770
+ ["remote", "get-url", "origin"],
1771
+ { cwd, encoding: "utf8" }
1772
+ );
1773
+ return result.stdout;
1774
+ }
1775
+
1776
+ // src/code-connect.ts
1777
+ import { existsSync as existsSync4 } from "fs";
1778
+ import { cpus, hostname, totalmem } from "os";
1779
+ import { resolve as resolve5 } from "path";
1780
+
1781
+ // ../harness/dist/chunk-UG5XHNFB.js
1782
+ var HARNESS_PROTOCOL_VERSION = 1;
1783
+
1784
+ // ../harness/dist/chunk-QALIIZRJ.js
1785
+ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
1786
+ var HarnessProtocolError = class extends Error {
1787
+ name = "HarnessProtocolError";
1788
+ };
1789
+ function record2(value) {
1790
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1791
+ }
1792
+ function boundedText(value, label, max) {
1793
+ if (typeof value !== "string" || !value || value.length > max || CONTROL.test(value)) {
1794
+ throw new HarnessProtocolError(`${label} must be a non-empty string of at most ${max} characters`);
1795
+ }
1796
+ return value;
1797
+ }
1798
+ function parseAgentOutput(line) {
1799
+ if (Buffer.byteLength(line, "utf8") > 1e6) throw new HarnessProtocolError("agent message exceeds 1 MB");
1800
+ let value;
1801
+ try {
1802
+ value = JSON.parse(line);
1803
+ } catch {
1804
+ throw new HarnessProtocolError("agent emitted invalid JSON");
1805
+ }
1806
+ const message3 = record2(value);
1807
+ if (!message3 || message3.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
1808
+ throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
1809
+ }
1810
+ if (message3.type === "event") {
1811
+ return {
1812
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
1813
+ type: "event",
1814
+ kind: boundedText(message3.kind, "event.kind", 120),
1815
+ ...message3.payload === void 0 ? {} : { payload: message3.payload }
1816
+ };
1817
+ }
1818
+ if (message3.type === "inference.request") {
1819
+ const call = record2(message3.call);
1820
+ if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
1821
+ throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
1822
+ }
1823
+ return {
1824
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
1825
+ type: "inference.request",
1826
+ requestId: boundedText(message3.requestId, "requestId", 180),
1827
+ call
1828
+ };
1829
+ }
1830
+ if (message3.type === "tool.request") {
1831
+ const input = record2(message3.input);
1832
+ const tool = String(message3.tool);
1833
+ if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
1834
+ throw new HarnessProtocolError("tool.request requires a registered tool and object input");
1835
+ }
1836
+ return {
1837
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
1838
+ type: "tool.request",
1839
+ requestId: boundedText(message3.requestId, "requestId", 180),
1840
+ tool,
1841
+ input
1842
+ };
1843
+ }
1844
+ if (message3.type === "attempt.complete") {
1845
+ if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message3.status))) {
1846
+ throw new HarnessProtocolError("attempt.complete.status is invalid");
1847
+ }
1848
+ return {
1849
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
1850
+ type: "attempt.complete",
1851
+ status: message3.status,
1852
+ ...message3.result === void 0 ? {} : { result: message3.result }
1853
+ };
1854
+ }
1855
+ throw new HarnessProtocolError("agent message type is unsupported");
1856
+ }
1857
+ function encodeAgentInput(message3) {
1858
+ return `${JSON.stringify(message3)}
1859
+ `;
1860
+ }
1861
+
1862
+ // ../harness/dist/chunk-FNYUIJUN.js
1863
+ import { execFile as execFile2, spawn as spawn2 } from "child_process";
1864
+ import { constants } from "fs";
1865
+ import { access } from "fs/promises";
1866
+ import { delimiter, join as join2 } from "path";
1867
+ import { getgid, getuid } from "process";
1868
+ import { chmod, copyFile, mkdir, mkdtemp, readdir, realpath, rm, stat } from "fs/promises";
1869
+ import { tmpdir } from "os";
1870
+ import { basename, join as join22, relative as relative2, resolve as resolve3 } from "path";
1871
+ import { spawn as spawn22 } from "child_process";
1872
+ var DIGEST_IMAGE = /^[a-z0-9][a-z0-9._/-]*(?::[a-zA-Z0-9._-]+)?@sha256:[0-9a-f]{64}$/;
1873
+ function assertPinnedImage(image) {
1874
+ if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
1875
+ }
1876
+ async function commandAvailable(engine) {
1877
+ for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
1878
+ try {
1879
+ await access(join2(directory, engine), constants.X_OK);
1880
+ return true;
1881
+ } catch {
1882
+ }
1883
+ }
1884
+ return false;
1885
+ }
1886
+ async function selectContainerEngine(requested = "auto", options = {}) {
1887
+ const platform = options.platform ?? process.platform;
1888
+ const arch = options.arch ?? process.arch;
1889
+ const uid = options.uid ?? (typeof getuid === "function" ? getuid() : 1e3);
1890
+ const available = options.available ?? commandAvailable;
1891
+ const validate3 = async (engine) => {
1892
+ if (engine === "container" && (platform !== "darwin" || arch !== "arm64")) {
1893
+ throw new TypeError("Apple container requires Apple Silicon macOS");
1894
+ }
1895
+ if (engine === "podman" && platform === "linux" && uid === 0) {
1896
+ throw new TypeError("the Linux harness requires rootless Podman; do not run the runner as root");
1897
+ }
1898
+ if (!await available(engine)) throw new TypeError(`${engine} is not installed or executable`);
1899
+ return engine;
1900
+ };
1901
+ if (requested !== "auto") return validate3(requested);
1902
+ const candidates = platform === "darwin" ? arch === "arm64" ? ["container", "podman"] : ["podman"] : platform === "linux" ? ["podman"] : [];
1903
+ for (const engine of candidates) {
1904
+ if (await available(engine)) return validate3(engine);
1905
+ }
1906
+ if (platform === "linux") {
1907
+ throw new TypeError("no rootless Podman found; install Podman or explicitly choose --engine docker after reviewing its daemon boundary");
1908
+ }
1909
+ if (platform === "darwin") {
1910
+ throw new TypeError("no Apple container or Podman Machine found; install one or explicitly choose --engine docker");
1911
+ }
1912
+ throw new TypeError("no supported container engine found");
1913
+ }
1914
+ function inspectRootlessPodman() {
1915
+ return new Promise((resolve23, reject) => {
1916
+ execFile2(
1917
+ "podman",
1918
+ ["info", "--format", "{{.Host.Security.Rootless}}"],
1919
+ { encoding: "utf8", maxBuffer: 16 * 1024, timeout: 1e4 },
1920
+ (error, stdout) => {
1921
+ if (error) {
1922
+ reject(new TypeError("could not verify that the active Podman service is rootless"));
1923
+ return;
1924
+ }
1925
+ resolve23(stdout.trim() === "true");
1926
+ }
1927
+ );
1928
+ });
1929
+ }
1930
+ async function verifyContainerEngineBoundary(engine, options = {}) {
1931
+ const platform = options.platform ?? process.platform;
1932
+ const arch = options.arch ?? process.arch;
1933
+ const uid = options.uid ?? (typeof getuid === "function" ? getuid() : 1e3);
1934
+ if (engine === "container" && (platform !== "darwin" || arch !== "arm64")) {
1935
+ throw new TypeError("Apple container requires Apple Silicon macOS");
1936
+ }
1937
+ if (engine !== "podman" || platform !== "linux") return;
1938
+ if (uid === 0) throw new TypeError("the Linux harness requires rootless Podman; do not run the runner as root");
1939
+ const rootless = await (options.podmanRootless ?? inspectRootlessPodman)();
1940
+ if (!rootless) throw new TypeError("the active Podman service is not rootless; refusing to run the harness");
1941
+ }
1942
+ function buildContainerRunArgs(options) {
1943
+ if (!options.allowUnpinnedImage) assertPinnedImage(options.image);
1944
+ if (/[,\r\n]/.test(options.workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
1945
+ const uid = typeof getuid === "function" ? getuid() : 1e3;
1946
+ const gid = typeof getgid === "function" ? getgid() : 1e3;
1947
+ const safeAttempt = options.task.attemptId.toLowerCase().replace(/[^a-z0-9_.-]/g, "-").slice(0, 40);
1948
+ const name = `odla-harness-${safeAttempt}-${crypto.randomUUID().slice(0, 8)}`;
1949
+ const limits = options.limits ?? {};
1950
+ const access2 = options.workspaceAccess ?? "read-write";
1951
+ const appleMount = access2 === "none" ? [] : [`--mount=type=bind,source=${options.workspaceDir},target=/workspace${access2 === "read-only" ? ",readonly" : ""}`];
1952
+ const ociMount = access2 === "none" ? [] : [`--mount=type=bind,src=${options.workspaceDir},dst=/workspace${access2 === "read-only" ? ",readonly" : ""}`];
1953
+ if (options.engine === "container") {
1954
+ return [
1955
+ "run",
1956
+ "--rm",
1957
+ "--interactive",
1958
+ `--name=${name}`,
1959
+ "--network=none",
1960
+ "--read-only",
1961
+ "--cap-drop=ALL",
1962
+ `--memory=${limits.memory ?? "1g"}`,
1963
+ `--cpus=${limits.cpus ?? 1}`,
1964
+ `--user=${uid}:${gid}`,
1965
+ "--tmpfs=/tmp",
1966
+ ...appleMount,
1967
+ "--workdir=/workspace",
1968
+ `--env=ODLA_HARNESS_PROTOCOL=${HARNESS_PROTOCOL_VERSION}`,
1969
+ `--label=ai.odla.harness.attempt=${options.task.attemptId}`,
1970
+ options.image
1971
+ ];
1972
+ }
1973
+ return [
1974
+ "run",
1975
+ "--rm",
1976
+ "--interactive",
1977
+ `--name=${name}`,
1978
+ "--pull=never",
1979
+ "--network=none",
1980
+ "--read-only",
1981
+ "--cap-drop=ALL",
1982
+ "--security-opt=no-new-privileges",
1983
+ `--pids-limit=${limits.pids ?? 256}`,
1984
+ `--memory=${limits.memory ?? "1g"}`,
1985
+ `--cpus=${limits.cpus ?? 1}`,
1986
+ `--user=${uid}:${gid}`,
1987
+ `--tmpfs=/tmp:rw,noexec,nosuid,nodev,size=${limits.tmpfsBytes ?? 64 * 1024 * 1024}`,
1988
+ ...ociMount,
1989
+ "--workdir=/workspace",
1990
+ `--env=ODLA_HARNESS_PROTOCOL=${HARNESS_PROTOCOL_VERSION}`,
1991
+ `--label=ai.odla.harness.attempt=${options.task.attemptId}`,
1992
+ options.image
1993
+ ];
1994
+ }
1995
+ function containerName(args) {
1996
+ return args.find((arg) => arg.startsWith("--name=")).slice("--name=".length);
1997
+ }
1998
+ async function runContainerAttempt(options) {
1999
+ if (options.signal?.aborted) return { exitCode: 1, status: "cancelled", stderr: "" };
2000
+ await verifyContainerEngineBoundary(options.engine);
2001
+ const args = buildContainerRunArgs(options);
2002
+ const name = containerName(args);
2003
+ const child = spawn2(options.engine, args, { stdio: ["pipe", "pipe", "pipe"], shell: false });
2004
+ let stderr = "";
2005
+ let outputBytes = 0;
2006
+ let complete = null;
2007
+ let stopped = false;
2008
+ let exited = false;
2009
+ child.stderr.setEncoding("utf8");
2010
+ child.stderr.on("data", (text) => {
2011
+ if (stderr.length < 64 * 1024) stderr += text.slice(0, 64 * 1024 - stderr.length);
2012
+ });
2013
+ const stop = (reason) => {
2014
+ if (stopped || exited) return;
2015
+ stopped = true;
2016
+ if (!child.stdin.destroyed) {
2017
+ const cancel = { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "attempt.cancel", reason };
2018
+ child.stdin.write(encodeAgentInput(cancel));
2019
+ }
2020
+ const removeArgs = options.engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
2021
+ const killer = spawn2(options.engine, removeArgs, { stdio: "ignore", shell: false });
2022
+ killer.unref();
2023
+ };
2024
+ const abort = () => stop("runner_cancelled");
2025
+ options.signal?.addEventListener("abort", abort, { once: true });
2026
+ const timeout = setTimeout(() => stop("timeout"), options.task.policy.timeoutMs);
2027
+ const start = { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "task.start", task: options.task };
2028
+ if (!stopped && !options.signal?.aborted) child.stdin.write(encodeAgentInput(start));
2029
+ const consume = (async () => {
2030
+ let pending = Buffer.alloc(0);
2031
+ const handleLine = async (raw) => {
2032
+ const bytes = raw.at(-1) === 13 ? raw.subarray(0, -1) : raw;
2033
+ if (bytes.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
2034
+ const line = bytes.toString("utf8");
2035
+ if (!line.trim()) return;
2036
+ const message3 = parseAgentOutput(line);
2037
+ if (message3.type === "attempt.complete") complete = message3;
2038
+ const response2 = await options.onMessage(message3);
2039
+ if (response2 && !child.stdin.destroyed) child.stdin.write(encodeAgentInput(response2));
2040
+ };
2041
+ try {
2042
+ for await (const raw of child.stdout) {
2043
+ const chunk = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
2044
+ outputBytes += chunk.byteLength;
2045
+ if (outputBytes > options.task.policy.maxOutputBytes) {
2046
+ throw new Error(`agent output exceeds ${options.task.policy.maxOutputBytes} bytes`);
2047
+ }
2048
+ pending = Buffer.concat([pending, chunk]);
2049
+ let newline = pending.indexOf(10);
2050
+ while (newline >= 0) {
2051
+ await handleLine(pending.subarray(0, newline));
2052
+ pending = pending.subarray(newline + 1);
2053
+ newline = pending.indexOf(10);
2054
+ }
2055
+ if (pending.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
2056
+ }
2057
+ if (pending.byteLength) await handleLine(pending);
2058
+ } catch (error) {
2059
+ stop("protocol_error");
2060
+ throw error;
2061
+ }
2062
+ })();
2063
+ const exit = new Promise((accept, reject) => {
2064
+ child.once("error", reject);
2065
+ child.once("exit", (code) => {
2066
+ exited = true;
2067
+ accept(code ?? 1);
2068
+ });
2069
+ });
2070
+ try {
2071
+ const [exitCode] = await Promise.all([exit, consume]);
2072
+ if (stderr && options.onStderr) await options.onStderr(stderr);
2073
+ if (options.signal?.aborted) return { exitCode, status: "cancelled", stderr };
2074
+ const terminal = complete;
2075
+ if (!terminal) return { exitCode, status: "failed", result: { error: "agent exited without completion" }, stderr };
2076
+ return { exitCode, status: exitCode === 0 ? terminal.status : "failed", result: terminal.result, stderr };
2077
+ } catch (error) {
2078
+ stop("runner_error");
2079
+ await exit.catch(() => 1);
2080
+ throw error;
2081
+ } finally {
2082
+ clearTimeout(timeout);
2083
+ options.signal?.removeEventListener("abort", abort);
2084
+ }
2085
+ }
2086
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
2087
+ var SECRET_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2088
+ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2089
+ const files = [];
2090
+ let bytes = 0;
2091
+ const walk = async (dir) => {
2092
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
2093
+ if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
2094
+ if (!entry.isDirectory() && SECRET_FILE.test(entry.name)) continue;
2095
+ const path = join22(dir, entry.name);
2096
+ if (entry.isSymbolicLink()) continue;
2097
+ if (entry.isDirectory()) {
2098
+ await walk(path);
2099
+ continue;
2100
+ }
2101
+ if (!entry.isFile()) continue;
2102
+ const metadata2 = await stat(path);
2103
+ bytes += metadata2.size;
2104
+ if (files.length + 1 > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2105
+ if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2106
+ files.push({
2107
+ source: path,
2108
+ relativePath: relative2(sourceDir, path),
2109
+ mode: metadata2.mode & 511,
2110
+ bytes: metadata2.size
2111
+ });
2112
+ }
2113
+ };
2114
+ await walk(sourceDir);
2115
+ return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
2116
+ }
2117
+ async function copyTree(files, destination) {
2118
+ for (const file of files) {
2119
+ const target = join22(destination, file.relativePath);
2120
+ await mkdir(resolve3(target, ".."), { recursive: true });
2121
+ await copyFile(file.source, target);
2122
+ await chmod(target, file.mode);
2123
+ }
2124
+ }
2125
+ async function captureGitDiff(root, maxBytes) {
2126
+ const child = spawn22("git", [
2127
+ "diff",
2128
+ "--no-index",
2129
+ "--binary",
2130
+ "--no-ext-diff",
2131
+ "--src-prefix=a/",
2132
+ "--dst-prefix=b/",
2133
+ "--",
2134
+ "baseline",
2135
+ "workspace"
2136
+ ], { cwd: root, stdio: ["ignore", "pipe", "pipe"], shell: false });
2137
+ const stdout = [];
2138
+ const stderr = [];
2139
+ let bytes = 0;
2140
+ child.stdout.on("data", (chunk) => {
2141
+ bytes += chunk.byteLength;
2142
+ if (bytes > maxBytes) child.kill("SIGKILL");
2143
+ else stdout.push(chunk);
2144
+ });
2145
+ child.stderr.on("data", (chunk) => {
2146
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2147
+ });
2148
+ const code = await new Promise((accept, reject) => {
2149
+ child.once("error", reject);
2150
+ child.once("exit", accept);
2151
+ });
2152
+ if (bytes > maxBytes) throw new Error(`patch exceeds ${maxBytes} bytes`);
2153
+ if (code !== 0 && code !== 1) {
2154
+ throw new Error(`git diff failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2155
+ }
2156
+ return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
2157
+ }
2158
+ async function stageWorkspace(source, options = {}) {
2159
+ const sourceDir = await realpath(resolve3(source));
2160
+ const sourceStat = await stat(sourceDir);
2161
+ if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
2162
+ const root = await mkdtemp(join22(options.tempRoot ?? tmpdir(), "odla-harness-"));
2163
+ const baselineDir = join22(root, "baseline");
2164
+ const workspaceDir = join22(root, "workspace");
2165
+ await Promise.all([mkdir(baselineDir), mkdir(workspaceDir)]);
2166
+ try {
2167
+ const files = await sourceFiles(sourceDir, options.maxFiles ?? 2e4, options.maxBytes ?? 512 * 1024 * 1024);
2168
+ await Promise.all([copyTree(files, baselineDir), copyTree(files, workspaceDir)]);
2169
+ return {
2170
+ root,
2171
+ baselineDir,
2172
+ workspaceDir,
2173
+ fileCount: files.length,
2174
+ byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
2175
+ patch: (maxBytes) => captureGitDiff(root, maxBytes),
2176
+ cleanup: () => rm(root, { recursive: true, force: true })
2177
+ };
2178
+ } catch (error) {
2179
+ await rm(root, { recursive: true, force: true });
2180
+ throw error;
2181
+ }
2182
+ }
2183
+
2184
+ // ../camel/dist/chunk-7FHPOQVP.js
2185
+ var CamelError = class extends Error {
2186
+ code;
2187
+ safeMessage;
2188
+ constructor(code, safeMessage, options) {
2189
+ super(safeMessage, options);
2190
+ this.name = "CamelError";
2191
+ this.code = code;
2192
+ this.safeMessage = safeMessage;
2193
+ }
2194
+ };
2195
+
2196
+ // ../camel/dist/chunk-L5DYU2E2.js
2197
+ function canonicalJson(value) {
2198
+ return JSON.stringify(normalize(value));
2199
+ }
2200
+ async function sha256Hex(value) {
2201
+ const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
2202
+ const digest2 = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2203
+ return [...new Uint8Array(digest2)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2204
+ }
2205
+ function utf8Length(value) {
2206
+ if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
2207
+ if (value instanceof Uint8Array) return value.byteLength;
2208
+ try {
2209
+ return new TextEncoder().encode(canonicalJson(value)).byteLength;
2210
+ } catch {
2211
+ throw new CamelError("conversion_rejected", "Unsafe input could not be canonically measured.");
2212
+ }
2213
+ }
2214
+ function normalize(value) {
2215
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
2216
+ if (typeof value === "number") {
2217
+ if (!Number.isFinite(value)) throw new CamelError("state_conflict", "Canonical JSON rejects non-finite numbers.");
2218
+ return value;
2219
+ }
2220
+ if (Array.isArray(value)) return value.map(normalize);
2221
+ if (value instanceof Uint8Array) return { $bytes: [...value] };
2222
+ if (typeof value === "object") {
2223
+ const record5 = value;
2224
+ return Object.fromEntries(Object.keys(record5).filter((key) => record5[key] !== void 0).sort().map((key) => [key, normalize(record5[key])]));
2225
+ }
2226
+ throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
2227
+ }
2228
+ function canRead(readers, readerId) {
2229
+ return readers.kind === "public" || readers.principalIds.includes(readerId);
2230
+ }
2231
+ function dependenciesOf(values, influence = "data") {
2232
+ const result = [];
2233
+ for (const value of values) {
2234
+ result.push(...value.label.dependencies);
2235
+ for (const ref of value.label.provenance) {
2236
+ result.push({ ref, influence, promptSafetyAtUse: value.label.promptSafety });
2237
+ }
2238
+ }
2239
+ const unique3 = /* @__PURE__ */ new Map();
2240
+ for (const dep of result) unique3.set(`${dep.ref.kind}\0${dep.ref.id}\0${dep.influence}\0${dep.promptSafetyAtUse}`, dep);
2241
+ return [...unique3.values()];
2242
+ }
2243
+
2244
+ // ../camel/dist/chunk-S7EVNA2U.js
2245
+ var camelValueBrand = /* @__PURE__ */ Symbol("@odla-ai/camel/value");
2246
+ var authenticCamelValues = /* @__PURE__ */ new WeakSet();
2247
+ function isCamelValue(value) {
2248
+ return typeof value === "object" && value !== null && authenticCamelValues.has(value);
2249
+ }
2250
+ function isSafe(value) {
2251
+ return isCamelValue(value) && value.label.promptSafety === "safe";
2252
+ }
2253
+ function isUnsafe(value) {
2254
+ return isCamelValue(value) && value.label.promptSafety === "unsafe";
2255
+ }
2256
+ function createSafeInternal(value, safeBasis, metadata2) {
2257
+ return createValue(value, {
2258
+ schemaVersion: 1,
2259
+ promptSafety: "safe",
2260
+ safeBasis,
2261
+ ...copyMetadata(metadata2)
2262
+ });
2263
+ }
2264
+ function createUnsafeInternal(value, metadata2) {
2265
+ return createValue(value, {
2266
+ schemaVersion: 1,
2267
+ promptSafety: "unsafe",
2268
+ ...copyMetadata(metadata2)
2269
+ });
2270
+ }
2271
+ function createValue(value, label) {
2272
+ const result = { value, label: Object.freeze(label) };
2273
+ Object.defineProperty(result, camelValueBrand, { value: label.promptSafety, enumerable: false });
2274
+ authenticCamelValues.add(result);
2275
+ return Object.freeze(result);
2276
+ }
2277
+ function copyMetadata(metadata2) {
2278
+ if (metadata2.provenance.length === 0) throw new CamelError("state_conflict", "Label provenance must be non-empty.");
2279
+ const provenance = metadata2.provenance.map(copyRef);
2280
+ const dependencies = (metadata2.dependencies ?? []).map((dependency) => Object.freeze({
2281
+ ref: copyRef(dependency.ref),
2282
+ influence: dependency.influence,
2283
+ promptSafetyAtUse: dependency.promptSafetyAtUse
2284
+ }));
2285
+ return {
2286
+ readers: normalizeReaders(metadata2.readers),
2287
+ provenance: Object.freeze(provenance),
2288
+ dependencies: Object.freeze(dependencies)
2289
+ };
2290
+ }
2291
+ function copyRef(ref) {
2292
+ if (!ref.id || ref.digest !== void 0 && !ref.digest) throw new CamelError("state_conflict", "Provenance references must have non-empty identities.");
2293
+ return Object.freeze({ kind: ref.kind, id: ref.id, ...ref.digest === void 0 ? {} : { digest: ref.digest } });
2294
+ }
2295
+ function normalizeReaders(readers) {
2296
+ if (readers.kind === "public") return Object.freeze({ kind: "public" });
2297
+ const principalIds = [...new Set(readers.principalIds)].sort();
2298
+ if (principalIds.some((id) => !id)) throw new CamelError("reader_mismatch", "Reader principal IDs must be non-empty.");
2299
+ return Object.freeze({ kind: "principals", principalIds: Object.freeze(principalIds) });
2300
+ }
2301
+
2302
+ // ../camel/dist/code.js
2303
+ var DIGEST = /^sha256:[0-9a-f]{64}$/;
2304
+ var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2305
+ var ID = /^[A-Za-z0-9._:-]{1,160}$/;
2306
+ async function digestCodeVerificationReceipt(fields) {
2307
+ validate(fields);
2308
+ const canonical = {
2309
+ schemaVersion: fields.schemaVersion,
2310
+ verificationId: fields.verificationId,
2311
+ trustedBaseCommitSha: fields.trustedBaseCommitSha,
2312
+ trustedBaseDigest: fields.trustedBaseDigest,
2313
+ patchDigest: fields.patchDigest,
2314
+ candidateDigest: fields.candidateDigest,
2315
+ sourceDigest: fields.sourceDigest,
2316
+ policyDigest: fields.policyDigest,
2317
+ recipes: fields.recipes.map((recipe2) => ({
2318
+ recipeId: recipe2.recipeId,
2319
+ recipeDigest: recipe2.recipeDigest,
2320
+ status: recipe2.status,
2321
+ exitCode: recipe2.exitCode,
2322
+ durationMs: recipe2.durationMs,
2323
+ artifacts: recipe2.artifacts.map((artifact) => ({
2324
+ artifactId: artifact.artifactId,
2325
+ status: artifact.status,
2326
+ bytes: artifact.bytes,
2327
+ digest: artifact.digest
2328
+ }))
2329
+ })),
2330
+ changedTestCount: fields.changedTestCount,
2331
+ changedTestSetDigest: fields.changedTestSetDigest,
2332
+ changedTestsRequireReview: fields.changedTestsRequireReview,
2333
+ outcome: fields.outcome
2334
+ };
2335
+ return `sha256:${await sha256Hex(canonicalJson(canonical))}`;
2336
+ }
2337
+ function validate(fields) {
2338
+ if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST.test(fields.trustedBaseDigest) || !DIGEST.test(fields.patchDigest) || !DIGEST.test(fields.candidateDigest) || !DIGEST.test(fields.sourceDigest) || !DIGEST.test(fields.policyDigest) || !DIGEST.test(fields.changedTestSetDigest) || !Number.isSafeInteger(fields.changedTestCount) || fields.changedTestCount < 0 || fields.changedTestCount > 1e4 || fields.changedTestsRequireReview !== fields.changedTestCount > 0 || fields.recipes.length < 1 || fields.recipes.length > 64) {
2339
+ throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
2340
+ }
2341
+ const ids = /* @__PURE__ */ new Set();
2342
+ for (const recipe2 of fields.recipes) {
2343
+ if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !DIGEST.test(recipe2.recipeDigest) || !["passed", "failed", "timed_out", "output_limited"].includes(recipe2.status) || !Number.isSafeInteger(recipe2.exitCode) || recipe2.exitCode < 0 || recipe2.exitCode > 255 || !Number.isSafeInteger(recipe2.durationMs) || recipe2.durationMs < 0 || recipe2.durationMs > 30 * 6e4) {
2344
+ throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
2345
+ }
2346
+ const artifactIds = /* @__PURE__ */ new Set();
2347
+ if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
2348
+ for (const artifact of recipe2.artifacts) {
2349
+ if (typeof artifact.artifactId !== "string" || !ID.test(artifact.artifactId) || artifactIds.has(artifact.artifactId) || !["verified", "missing", "invalid", "too_large"].includes(artifact.status) || artifact.bytes !== null && (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0) || artifact.digest !== null && !DIGEST.test(artifact.digest) || artifact.status === "verified" !== (artifact.bytes !== null && artifact.digest !== null) || ["missing", "invalid"].includes(artifact.status) && (artifact.bytes !== null || artifact.digest !== null) || artifact.status === "too_large" && (artifact.bytes === null || artifact.digest !== null)) {
2350
+ throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
2351
+ }
2352
+ artifactIds.add(artifact.artifactId);
2353
+ }
2354
+ if (recipe2.status === "passed" && (recipe2.exitCode !== 0 || recipe2.artifacts.some((artifact) => artifact.status !== "verified"))) {
2355
+ throw new CamelError("state_conflict", "A passing recipe must verify every registered artifact.");
2356
+ }
2357
+ ids.add(recipe2.recipeId);
2358
+ }
2359
+ const passed = fields.recipes.every((recipe2) => recipe2.status === "passed");
2360
+ if (fields.outcome === "passed" !== passed) {
2361
+ throw new CamelError("state_conflict", "Code verification outcome does not match its recipe receipts.");
2362
+ }
2363
+ }
2364
+ var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2365
+ var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
2366
+ var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
2367
+ var MAX_PATCH_BYTES = 256 * 1024;
2368
+ var MAX_STATE_BYTES = 64 * 1024;
2369
+ async function createCodePortableCheckpoint(input) {
2370
+ const state = normalizeState(input.state);
2371
+ validateBaseAndPatch(input.baseCommitSha, input.patch);
2372
+ const patchDigest = `sha256:${await sha256Hex(input.patch)}`;
2373
+ const stateDigest = `sha256:${await sha256Hex(canonicalJson(state))}`;
2374
+ const fields = { schemaVersion: 1, baseCommitSha: input.baseCommitSha, patchDigest, state, stateDigest };
2375
+ const checkpointDigest = `sha256:${await sha256Hex(canonicalJson(fields))}`;
2376
+ return freeze({ ...fields, patch: input.patch, checkpointDigest });
2377
+ }
2378
+ async function verifyCodePortableCheckpoint(value) {
2379
+ const input = object(value, "checkpoint");
2380
+ exact2(input, ["schemaVersion", "baseCommitSha", "patch", "patchDigest", "state", "stateDigest", "checkpointDigest"]);
2381
+ if (input.schemaVersion !== 1 || typeof input.baseCommitSha !== "string" || typeof input.patch !== "string" || typeof input.patchDigest !== "string" || typeof input.stateDigest !== "string" || typeof input.checkpointDigest !== "string") {
2382
+ throw invalid2("Portable checkpoint fields are malformed.");
2383
+ }
2384
+ const created = await createCodePortableCheckpoint({
2385
+ baseCommitSha: input.baseCommitSha,
2386
+ patch: input.patch,
2387
+ state: normalizeState(input.state)
2388
+ });
2389
+ if (created.patchDigest !== input.patchDigest || created.stateDigest !== input.stateDigest || created.checkpointDigest !== input.checkpointDigest) {
2390
+ throw invalid2("Portable checkpoint digest does not match its content.");
2391
+ }
2392
+ return created;
2393
+ }
2394
+ function normalizeState(value) {
2395
+ const state = object(value, "state");
2396
+ exact2(state, [
2397
+ "planCursor",
2398
+ "conversationRefs",
2399
+ "planningInputDigest",
2400
+ "buildPolicyDigest",
2401
+ "dependencyLayerDigest",
2402
+ "verificationDigest",
2403
+ "reviewDigest",
2404
+ "completedEffects",
2405
+ "unresolvedApprovals",
2406
+ "trustStatus"
2407
+ ]);
2408
+ const planCursor = state.planCursor;
2409
+ const conversations = strings(state.conversationRefs, "conversationRefs", ID2, 256, false);
2410
+ const approvals = strings(state.unresolvedApprovals, "unresolvedApprovals", DIGEST2, 256, true);
2411
+ if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state.planningInputDigest !== "string" || !DIGEST2.test(state.planningInputDigest) || typeof state.buildPolicyDigest !== "string" || !DIGEST2.test(state.buildPolicyDigest) || state.dependencyLayerDigest !== null && (typeof state.dependencyLayerDigest !== "string" || !DIGEST2.test(state.dependencyLayerDigest)) || state.verificationDigest !== null && (typeof state.verificationDigest !== "string" || !DIGEST2.test(state.verificationDigest)) || state.reviewDigest !== null && (typeof state.reviewDigest !== "string" || !DIGEST2.test(state.reviewDigest)) || !["candidate_untrusted", "verified", "reviewed"].includes(String(state.trustStatus)) || !Array.isArray(state.completedEffects) || state.completedEffects.length > 256) {
2412
+ throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
2413
+ }
2414
+ const effects = state.completedEffects.map((item) => {
2415
+ const effect = object(item, "completed effect");
2416
+ exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
2417
+ if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !DIGEST2.test(effect.actionDigest) || typeof effect.receiptDigest !== "string" || !DIGEST2.test(effect.receiptDigest)) {
2418
+ throw invalid2("Portable checkpoint effect receipt is malformed.");
2419
+ }
2420
+ return {
2421
+ effectId: effect.effectId,
2422
+ actionDigest: effect.actionDigest,
2423
+ receiptDigest: effect.receiptDigest
2424
+ };
2425
+ });
2426
+ if (new Set(effects.map((item) => item.effectId)).size !== effects.length) {
2427
+ throw invalid2("Portable checkpoint repeats a completed effect.");
2428
+ }
2429
+ const trustStatus = state.trustStatus;
2430
+ if (trustStatus === "candidate_untrusted" && (state.verificationDigest !== null || state.reviewDigest !== null) || trustStatus === "verified" && (state.verificationDigest === null || state.reviewDigest !== null) || trustStatus === "reviewed" && (state.verificationDigest === null || state.reviewDigest === null)) {
2431
+ throw invalid2("Portable checkpoint trust status does not match its evidence digests.");
2432
+ }
2433
+ const normalized = {
2434
+ planCursor,
2435
+ conversationRefs: conversations,
2436
+ planningInputDigest: state.planningInputDigest,
2437
+ buildPolicyDigest: state.buildPolicyDigest,
2438
+ dependencyLayerDigest: state.dependencyLayerDigest,
2439
+ verificationDigest: state.verificationDigest,
2440
+ reviewDigest: state.reviewDigest,
2441
+ completedEffects: effects,
2442
+ unresolvedApprovals: approvals,
2443
+ trustStatus
2444
+ };
2445
+ if (utf8Length(normalized) > MAX_STATE_BYTES) throw invalid2("Portable checkpoint state exceeds 64 KB.");
2446
+ return normalized;
2447
+ }
2448
+ function validateBaseAndPatch(baseCommitSha, patch2) {
2449
+ if (!SHA2.test(baseCommitSha) || utf8Length(patch2) > MAX_PATCH_BYTES || patch2.includes("\0")) {
2450
+ throw invalid2("Portable checkpoint base or patch is malformed or outside its bounds.");
2451
+ }
2452
+ }
2453
+ function strings(value, name, pattern, maximum, sort) {
2454
+ if (!Array.isArray(value) || value.length > maximum || value.some((item) => typeof item !== "string" || !pattern.test(item)) || new Set(value).size !== value.length) {
2455
+ throw invalid2(`Portable checkpoint ${name} is malformed or outside its bounds.`);
2456
+ }
2457
+ const result = [...value];
2458
+ return sort ? result.sort() : result;
2459
+ }
2460
+ function object(value, name) {
2461
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid2(`Portable ${name} must be an object.`);
2462
+ return value;
2463
+ }
2464
+ function exact2(value, keys) {
2465
+ if (Object.keys(value).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value))) {
2466
+ throw invalid2("Portable checkpoint contains missing or unsupported fields.");
2467
+ }
2468
+ }
2469
+ function freeze(value) {
2470
+ return Object.freeze({
2471
+ ...value,
2472
+ state: Object.freeze({
2473
+ ...value.state,
2474
+ conversationRefs: Object.freeze([...value.state.conversationRefs]),
2475
+ completedEffects: Object.freeze(value.state.completedEffects.map((item) => Object.freeze({ ...item }))),
2476
+ unresolvedApprovals: Object.freeze([...value.state.unresolvedApprovals])
2477
+ })
2478
+ });
2479
+ }
2480
+ function invalid2(message3) {
2481
+ return new CamelError("state_conflict", message3);
2482
+ }
2483
+ var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2484
+ var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
2485
+ var DEFAULT_LIMITS = { maximumFiles: 4e3, maximumBytes: 2 * 1024 * 1024 };
2486
+ async function digestCodeRepositorySnapshot(snapshot, limits = DEFAULT_LIMITS) {
2487
+ validateSnapshot(snapshot, limits);
2488
+ const normalized = {
2489
+ repository: snapshot.repository,
2490
+ commitSha: snapshot.commitSha,
2491
+ files: [...snapshot.files].map((file) => ({ path: file.path, content: file.content })).sort((left, right) => left.path.localeCompare(right.path))
2492
+ };
2493
+ return `sha256:${await sha256Hex(canonicalJson(normalized))}`;
2494
+ }
2495
+ function validateSnapshot(snapshot, limits) {
2496
+ if (!REPOSITORY.test(snapshot.repository) || !SHA4.test(snapshot.commitSha)) {
2497
+ throw new CamelError("state_conflict", "Code snapshot repository or commit is invalid.");
2498
+ }
2499
+ if (!Number.isSafeInteger(limits.maximumFiles) || limits.maximumFiles < 1 || !Number.isSafeInteger(limits.maximumBytes) || limits.maximumBytes < 1 || snapshot.files.length > limits.maximumFiles) {
2500
+ throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
2501
+ }
2502
+ const paths = /* @__PURE__ */ new Set();
2503
+ let bytes = 0;
2504
+ for (const file of snapshot.files) {
2505
+ if (!file.path || file.path.startsWith("/") || file.path.includes("\\") || file.path.split("/").some((part) => !part || part === "." || part === "..") || paths.has(file.path) || typeof file.content !== "string") {
2506
+ throw new CamelError("state_conflict", "Code snapshot contains an invalid or duplicate path.");
2507
+ }
2508
+ paths.add(file.path);
2509
+ bytes += utf8Length(file.path) + utf8Length(file.content);
2510
+ }
2511
+ if (bytes > limits.maximumBytes) {
2512
+ throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
2513
+ }
2514
+ }
2515
+
2516
+ // ../harness/dist/chunk-RVUUURK2.js
2517
+ import { spawn as spawn3 } from "child_process";
2518
+ import { lstat } from "fs/promises";
2519
+ import { resolve as resolve4, sep } from "path";
2520
+ import { spawn as spawn23 } from "child_process";
2521
+ import { getgid as getgid2, getuid as getuid2 } from "process";
2522
+ import { randomUUID } from "crypto";
2523
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
2524
+ import { createReadStream } from "fs";
2525
+ import { lstat as lstat2 } from "fs/promises";
2526
+ import { join as join3 } from "path";
2527
+ import { createHash } from "crypto";
2528
+ import { readFile, readdir as readdir2 } from "fs/promises";
2529
+ import { relative as relative3, resolve as resolve22 } from "path";
2530
+ import { mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "fs/promises";
2531
+ import { tmpdir as tmpdir2 } from "os";
2532
+ import { dirname as dirname4, join as join23, resolve as resolve32, sep as sep2 } from "path";
2533
+ import { readFile as readFile2, readdir as readdir22, stat as stat2 } from "fs/promises";
2534
+ import { relative as relative22, resolve as resolve42 } from "path";
2535
+
2536
+ // ../camel/dist/chunk-LAXU2AVK.js
2537
+ function conversionPolicyDigest(policy) {
2538
+ return sha256Hex(canonicalJson(policy));
2539
+ }
2540
+ function registeredIdRegistryDigest(values) {
2541
+ return sha256Hex(canonicalJson(values));
2542
+ }
2543
+ async function createConversionRegistry(config) {
2544
+ const policies = /* @__PURE__ */ new Map();
2545
+ for (const policy of config.policies) {
2546
+ validatePolicyShape(policy);
2547
+ if (policies.has(policy.conversionId)) throw new CamelError("state_conflict", "Conversion IDs must be unique.");
2548
+ const { digest: _digest, ...definition } = policy;
2549
+ if (await conversionPolicyDigest(definition) !== policy.digest) throw new CamelError("state_conflict", "Conversion policy digest mismatch.");
2550
+ if (policy.output.kind === "registered_id") {
2551
+ const registry = config.registeredIds?.[policy.output.registryId];
2552
+ const validValues = registry && Object.entries(registry.values).every(([candidate, id]) => candidate.length > 0 && typeof id === "string" && id.length > 0);
2553
+ if (!registry || !validValues || registry.digest !== policy.output.registryDigest || await registeredIdRegistryDigest(registry.values) !== registry.digest) {
2554
+ throw new CamelError("state_conflict", "Registered-ID registry digest mismatch.");
2555
+ }
2556
+ }
2557
+ policies.set(policy.conversionId, Object.freeze(policy));
2558
+ }
2559
+ const outputCounts = /* @__PURE__ */ new Map();
2560
+ const get = (id, kind) => {
2561
+ const policy = policies.get(id);
2562
+ if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
2563
+ return policy;
2564
+ };
2565
+ const checked = (source, id, kind) => {
2566
+ const policy = get(id, kind);
2567
+ if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
2568
+ return policy;
2569
+ };
2570
+ const emit = (source, policy, value) => convert(source, policy, value, outputCounts);
2571
+ const operations = Object.freeze({
2572
+ boolean: async (value, id) => {
2573
+ const policy = checked(value, id, "boolean");
2574
+ return emit(value, policy, requireBoolean(value.value));
2575
+ },
2576
+ integer: async (value, id) => {
2577
+ const policy = checked(value, id, "integer");
2578
+ return emit(value, policy, boundedInteger(value.value, policy.output));
2579
+ },
2580
+ finiteNumber: async (value, id) => {
2581
+ const policy = checked(value, id, "finite_number");
2582
+ return emit(value, policy, boundedNumber(value.value, policy.output));
2583
+ },
2584
+ enum: async (value, id) => {
2585
+ const policy = checked(value, id, "enum");
2586
+ return emit(value, policy, enumMember(value.value, policy.output));
2587
+ },
2588
+ date: async (value, id) => {
2589
+ const policy = checked(value, id, "date");
2590
+ return emit(value, policy, canonicalDate(value.value, policy.output));
2591
+ },
2592
+ registeredId: async (value, id) => {
2593
+ const policy = checked(value, id, "registered_id");
2594
+ const registry = config.registeredIds?.[policy.output.registryId];
2595
+ const output = typeof value.value === "string" ? registry?.values[value.value] : void 0;
2596
+ if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
2597
+ return emit(value, policy, output);
2598
+ },
2599
+ digest: async (value, id) => {
2600
+ const policy = checked(value, id, "digest");
2601
+ if (!(value.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
2602
+ return emit(value, policy, await sha256Hex(value.value));
2603
+ },
2604
+ measure: async (value, metric, id) => {
2605
+ const policy = checked(value, id, "integer");
2606
+ const measured = measure(value.value, metric);
2607
+ return emit(value, policy, boundedInteger(measured, policy.output));
2608
+ },
2609
+ test: async (value, predicateId, id) => {
2610
+ const policy = checked(value, id, "boolean");
2611
+ const predicate = config.predicates?.[predicateId];
2612
+ if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
2613
+ return emit(value, policy, evaluatePredicate(value.value, predicate, config.registeredIds));
2614
+ }
2615
+ });
2616
+ return Object.freeze({ operations, policy: (id) => policies.get(id) ?? missingPolicy() });
2617
+ }
2618
+ async function convert(source, policy, value, counts) {
2619
+ const sourceKey = sourceIdentity(source);
2620
+ const countKey = `${policy.conversionId}\0${sourceKey}`;
2621
+ const count = counts.get(countKey) ?? 0;
2622
+ if (count >= policy.maximumOutputsPerArtifact) throw new CamelError("limit_exceeded", "Conversion output count exceeds its per-source bound.");
2623
+ counts.set(countKey, count + 1);
2624
+ return createSafeInternal(value, "atomic_conversion", {
2625
+ readers: source.label.readers,
2626
+ provenance: [...source.label.provenance, { kind: "converter", id: policy.conversionId, digest: policy.digest }],
2627
+ dependencies: dependenciesOf([source])
2628
+ });
2629
+ }
2630
+ function validatePolicyShape(policy) {
2631
+ if (!policy.conversionId || !Number.isSafeInteger(policy.version) || policy.version < 1 || !policy.digest) throw new CamelError("state_conflict", "Conversion policy identity is invalid.");
2632
+ if (!Number.isSafeInteger(policy.maximumSourceBytes) || policy.maximumSourceBytes < 0 || !Number.isSafeInteger(policy.maximumOutputsPerArtifact) || policy.maximumOutputsPerArtifact < 1) throw new CamelError("state_conflict", "Conversion policy bounds are invalid.");
2633
+ const output = policy.output;
2634
+ if ((output.kind === "integer" || output.kind === "finite_number") && (!Number.isFinite(output.minimum) || !Number.isFinite(output.maximum) || output.minimum > output.maximum)) throw new CamelError("state_conflict", "Numeric conversion bounds are invalid.");
2635
+ if (output.kind === "finite_number" && (!Number.isSafeInteger(output.maximumDecimalPlaces) || output.maximumDecimalPlaces < 0 || output.maximumDecimalPlaces > 15)) throw new CamelError("state_conflict", "Decimal-place bound is invalid.");
2636
+ if (output.kind === "enum" && (output.values.length === 0 || output.values.some((value) => typeof value !== "string" || !value) || new Set(output.values).size !== output.values.length)) throw new CamelError("state_conflict", "Enum conversion members must be unique and non-empty.");
2637
+ if (output.kind === "registered_id" && (!output.registryId || !output.registryDigest)) throw new CamelError("state_conflict", "Registered-ID conversion identity is invalid.");
2638
+ if (output.kind === "date" && output.format !== "date" && output.format !== "rfc3339") throw new CamelError("state_conflict", "Date conversion format is invalid.");
2639
+ if (output.kind === "digest" && output.algorithm !== "sha256") throw new CamelError("state_conflict", "Digest conversion algorithm is invalid.");
2640
+ }
2641
+ function requireBoolean(value) {
2642
+ if (typeof value !== "boolean") throw new CamelError("conversion_rejected", "Boolean conversion requires a structured boolean.");
2643
+ return value;
2644
+ }
2645
+ function boundedInteger(value, spec) {
2646
+ if (spec.kind !== "integer" || typeof value !== "number" || !Number.isSafeInteger(value) || value < spec.minimum || value > spec.maximum) throw new CamelError("conversion_rejected", "Integer conversion rejected the structured value.");
2647
+ return value;
2648
+ }
2649
+ function boundedNumber(value, spec) {
2650
+ if (spec.kind !== "finite_number" || typeof value !== "number" || !Number.isFinite(value) || value < spec.minimum || value > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
2651
+ const text = String(value);
2652
+ if (/e/i.test(text) || (text.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
2653
+ return value;
2654
+ }
2655
+ function enumMember(value, spec) {
2656
+ if (spec.kind !== "enum" || typeof value !== "string") throw new CamelError("conversion_rejected", "Enum conversion requires a structured string member.");
2657
+ const member = spec.caseSensitive ? spec.values.find((item) => item === value) : spec.values.find((item) => item.toLocaleLowerCase("en-US") === value.toLocaleLowerCase("en-US"));
2658
+ if (member === void 0) throw new CamelError("conversion_rejected", "Enum conversion rejected a non-member.");
2659
+ return member;
2660
+ }
2661
+ function canonicalDate(value, spec) {
2662
+ if (spec.kind !== "date" || typeof value !== "string") throw new CamelError("conversion_rejected", "Date conversion requires a canonical structured string.");
2663
+ const pattern = spec.format === "date" ? /^\d{4}-\d{2}-\d{2}$/ : /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
2664
+ const instant = Date.parse(spec.format === "date" ? `${value}T00:00:00Z` : value);
2665
+ if (!pattern.test(value) || !Number.isFinite(instant) || spec.format === "date" && new Date(instant).toISOString().slice(0, 10) !== value) throw new CamelError("conversion_rejected", "Date conversion rejected a non-canonical value.");
2666
+ if (spec.earliest && value < spec.earliest || spec.latest && value > spec.latest) throw new CamelError("conversion_rejected", "Date conversion rejected an out-of-range value.");
2667
+ return value;
2668
+ }
2669
+ function measure(value, metric) {
2670
+ if (metric === "byte_length" && (typeof value === "string" || value instanceof Uint8Array)) return utf8Length(value);
2671
+ if (metric === "codepoint_length" && typeof value === "string") return [...value].length;
2672
+ if (metric === "item_count" && Array.isArray(value)) return value.length;
2673
+ throw new CamelError("conversion_rejected", "Measurement input does not match the registered metric.");
2674
+ }
2675
+ function evaluatePredicate(value, predicate, registries) {
2676
+ if (predicate.kind === "has_fields") return typeof value === "object" && value !== null && !Array.isArray(value) && predicate.fields.every((field) => Object.hasOwn(value, field));
2677
+ if (predicate.kind === "registered_id") return typeof value === "string" && registries?.[predicate.registryId]?.values[value] !== void 0;
2678
+ const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
2679
+ return actual === predicate.valueType;
2680
+ }
2681
+ function sourceIdentity(source) {
2682
+ const artifact = [...source.label.provenance, ...source.label.dependencies.map((dependency) => dependency.ref)].find((ref2) => ref2.kind === "artifact");
2683
+ const fallback = source.label.provenance[0];
2684
+ if (!artifact && !fallback) throw new CamelError("conversion_rejected", "Conversion source has no immutable provenance.");
2685
+ const ref = artifact ?? fallback;
2686
+ return `${ref.kind}:${ref.id}:${ref.digest ?? ""}`;
2687
+ }
2688
+ function missingPolicy() {
2689
+ throw new CamelError("conversion_rejected", "Conversion policy is not registered.");
2690
+ }
2691
+
2692
+ // ../camel/dist/chunk-P2WQIAG6.js
2693
+ function createCamelIngress(constants2 = []) {
2694
+ const byId = /* @__PURE__ */ new Map();
2695
+ for (const item of constants2) {
2696
+ if (!item.id || byId.has(item.id)) throw new CamelError("state_conflict", "Control constant IDs must be unique and non-empty.");
2697
+ assertNoUnsafeConstant(item.value);
2698
+ byId.set(item.id, item);
2699
+ }
2700
+ const ingress = {
2701
+ userInstruction: (value, input) => createSafeInternal(value, "user_instruction", metadata("user_instruction", input.id, input.readers)),
2702
+ systemPolicy: (value, input) => createSafeInternal(value, "system_policy", metadata("system_policy", input.id, input.readers)),
2703
+ control: (id) => {
2704
+ const item = byId.get(id);
2705
+ if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
2706
+ return createSafeInternal(item.value, "harness_constant", metadata("harness", id, item.readers));
2707
+ },
2708
+ external: (value, label) => createUnsafeInternal(value, label),
2709
+ quarantinedOutput: (value, input) => {
2710
+ if (!input.runId) throw new CamelError("state_conflict", "Quarantined run IDs must be non-empty.");
2711
+ return createUnsafeInternal(value, {
2712
+ readers: input.readers,
2713
+ dependencies: input.dependencies,
2714
+ provenance: [{ kind: "quarantined_llm", id: input.runId, digest: input.digest }]
2715
+ });
2716
+ }
2717
+ };
2718
+ return Object.freeze(ingress);
2719
+ }
2720
+ function assertNoUnsafeConstant(value, seen = /* @__PURE__ */ new WeakSet()) {
2721
+ if (typeof value !== "object" || value === null || seen.has(value)) return;
2722
+ seen.add(value);
2723
+ if (isCamelValue(value)) {
2724
+ if (isUnsafe(value)) throw new CamelError("unsafe_privileged_flow", "Control constants cannot contain Prompt-Unsafe values.");
2725
+ assertNoUnsafeConstant(value.value, seen);
2726
+ return;
2727
+ }
2728
+ for (const child of Object.values(value)) assertNoUnsafeConstant(child, seen);
2729
+ }
2730
+ function metadata(kind, id, readers) {
2731
+ if (!id) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
2732
+ return { readers, provenance: [{ kind, id }] };
2733
+ }
2734
+
2735
+ // ../camel/dist/policy.js
2736
+ function createEffectPolicy(config = {}) {
2737
+ const approvalEffects = new Set(config.approvalEffects ?? ["irreversible_mutation", "external_send", "financial", "secret_read", "code_execution"]);
2738
+ const registries = copyRegistries(config.destinationRegistries ?? {});
2739
+ return Object.freeze({ evaluate: (input) => evaluate(input, registries, approvalEffects) });
2740
+ }
2741
+ function destinationRegistryDigest(values) {
2742
+ return sha256Hex(canonicalJson([...values].sort()));
2743
+ }
2744
+ async function evaluate(input, registries, approvals) {
2745
+ const actionDigest = await sha256Hex(canonicalJson(input));
2746
+ const decisionId = `decision_${actionDigest.slice(0, 24)}`;
2747
+ const deny = (reasonCode) => ({ outcome: "deny", decisionId, reasonCode });
2748
+ if (!input.planId || !input.tool.name || !input.tool.policyId || !Number.isSafeInteger(input.tool.version)) return deny("invalid_descriptor");
2749
+ const expectedPaths = Object.keys(input.tool.argumentRoles).sort();
2750
+ const actualPaths = Object.keys(input.args).sort();
2751
+ if (expectedPaths.length !== actualPaths.length || expectedPaths.some((path, index) => path !== actualPaths[index])) return deny("argument_shape_mismatch");
2752
+ const confined = /* @__PURE__ */ new Set();
2753
+ for (const [path, arg] of Object.entries(input.args)) {
2754
+ if (arg.role !== input.tool.argumentRoles[path]) return deny("argument_role_mismatch");
2755
+ const invalid3 = await validateArgument(path, arg, input.tool, registries);
2756
+ if (invalid3) return deny(invalid3);
2757
+ if (arg.role === "selector" && !isSafe(arg.value)) confined.add(arg.value);
2758
+ }
2759
+ if (input.controlDependencies.some((value) => !isSafe(value) && !confined.has(value))) return deny("unsafe_control_dependency");
2760
+ if (confined.size > 0) {
2761
+ const fixed = input.tool.unsafeSelectorPolicy?.fixedProviderIds ?? [];
2762
+ const destinations = Object.values(input.args).filter((arg) => arg.role === "destination");
2763
+ if (destinations.length === 0 || destinations.some((arg) => !fixed.includes(arg.value.value))) return deny("unsafe_selector_provider_mismatch");
2764
+ }
2765
+ const readerValues = input.intendedReaderIds ?? [];
2766
+ if (readerValues.some((reader) => !isSafe(reader) || typeof reader.value !== "string" || !reader.value || !isControlOwned(reader))) return deny("invalid_reader");
2767
+ const readers = readerValues.map((reader) => reader.value);
2768
+ for (const arg of Object.values(input.args)) {
2769
+ if (readers.some((reader) => !canRead(arg.value.label.readers, reader))) return deny("reader_mismatch");
2770
+ }
2771
+ const hasUnsafePayload = Object.values(input.args).some((arg) => arg.role === "payload" && !isSafe(arg.value));
2772
+ if (hasUnsafePayload && input.tool.effect === "external_send" && readers.length === 0) return deny("reader_required");
2773
+ if (approvals.has(input.tool.effect)) return { outcome: "require_approval", decisionId, reasonCode: "effect_requires_approval", actionDigest };
2774
+ return { outcome: "allow", decisionId };
2775
+ }
2776
+ async function validateArgument(path, arg, tool, registries) {
2777
+ if (arg.role === "instruction" && !isSafe(arg.value)) return "unsafe_instruction_argument";
2778
+ if (arg.role === "authority") {
2779
+ if (!isSafe(arg.value)) return "unsafe_authority_argument";
2780
+ if (!isControlOwned(arg.value)) return "authority_not_control_owned";
2781
+ }
2782
+ if (arg.role === "destination") {
2783
+ if (!isSafe(arg.value)) return "unsafe_destination_argument";
2784
+ if (!isControlOwned(arg.value)) return "destination_not_control_owned";
2785
+ const registry = registries[arg.registryId];
2786
+ const validValues = registry && registry.values.length > 0 && registry.values.every((value) => typeof value === "string" && value.length > 0) && new Set(registry.values).size === registry.values.length;
2787
+ if (!registry || !validValues || registry.digest !== arg.registryDigest || await destinationRegistryDigest(registry.values) !== registry.digest || !registry.values.includes(arg.value.value)) return "destination_not_registered";
2788
+ }
2789
+ if (arg.role === "selector" && !isSafe(arg.value)) return validateUnsafeSelector(path, arg.value, tool);
2790
+ return void 0;
2791
+ }
2792
+ function isControlOwned(value) {
2793
+ return value.label.safeBasis === "system_policy" || value.label.safeBasis === "harness_constant";
2794
+ }
2795
+ function copyRegistries(registries) {
2796
+ return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id, registry]) => [id, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
2797
+ }
2798
+ function validateUnsafeSelector(path, value, tool) {
2799
+ const policy = tool.unsafeSelectorPolicy;
2800
+ if (!policy || policy.effect !== tool.effect || !policy.selectorPaths.includes(path)) return "unsafe_selector_not_confined";
2801
+ if (!policy.fixedDestination || !policy.unsafeOutput || policy.fixedProviderIds.length === 0) return "unsafe_selector_not_confined";
2802
+ if (![policy.maximumCalls, policy.maximumResultsPerCall, policy.maximumOutputBytes, policy.maximumSelectorBytes].every((bound) => Number.isSafeInteger(bound) && bound > 0)) return "unsafe_selector_not_confined";
2803
+ if (utf8Length(value.value) > policy.maximumSelectorBytes) return "unsafe_selector_too_large";
2804
+ if (typeof value.value === "string" && looksLikeDestination(value.value)) return "unsafe_selector_is_destination";
2805
+ return void 0;
2806
+ }
2807
+ function looksLikeDestination(value) {
2808
+ const text = value.trim();
2809
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
2810
+ }
2811
+
2812
+ // ../harness/dist/chunk-RVUUURK2.js
2813
+ import { createHash as createHash3 } from "crypto";
2814
+ var CODE_RUNTIME_PROTOCOL_VERSION = 1;
2815
+ async function runCodeRuntimeHeartbeatLoop(options) {
2816
+ const heartbeatMs = options.heartbeatMs ?? 15e3;
2817
+ if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3 || heartbeatMs > 3e5) {
2818
+ throw new TypeError("heartbeatMs must be an integer from 1000 to 300000");
2819
+ }
2820
+ let retryMs = 1e3;
2821
+ do {
2822
+ if (options.signal?.aborted) return;
2823
+ try {
2824
+ const snapshot = await options.control.heartbeat(options.runtimeVersion, options.capabilities);
2825
+ await options.onSnapshot?.(snapshot);
2826
+ retryMs = 1e3;
2827
+ if (options.once) return;
2828
+ await wait(heartbeatMs, options.signal);
2829
+ } catch (error) {
2830
+ if (options.signal?.aborted) return;
2831
+ if (options.once || !retryableControlFailure(error)) throw error;
2832
+ await options.onRetry?.(error, retryMs);
2833
+ await wait(retryMs, options.signal);
2834
+ retryMs = Math.min(retryMs * 2, 3e4);
2835
+ }
2836
+ } while (!options.signal?.aborted);
2837
+ }
2838
+ var CodeRuntimeReconciler = class {
2839
+ constructor(control, engine) {
2840
+ this.control = control;
2841
+ this.engine = engine;
2842
+ }
2843
+ control;
2844
+ engine;
2845
+ results = /* @__PURE__ */ new Map();
2846
+ async reconcile(snapshot) {
2847
+ for (const command of snapshot.commands) {
2848
+ let completed = this.results.get(command.commandId);
2849
+ if (!completed) {
2850
+ let result;
2851
+ try {
2852
+ result = await this.engine.execute(command);
2853
+ } catch (error) {
2854
+ result = { status: "failed", message: (error instanceof Error ? error.message : String(error)).slice(0, 2e3) };
2855
+ }
2856
+ completed = { result, notified: false };
2857
+ this.results.set(command.commandId, completed);
2858
+ if (this.results.size > 1024) this.results.delete(this.results.keys().next().value);
2859
+ }
2860
+ await this.control.acknowledge(command.commandId, completed.result);
2861
+ if (!completed.notified) {
2862
+ await this.engine.acknowledged?.(command, completed.result);
2863
+ completed.notified = true;
2864
+ }
2865
+ }
2866
+ }
2867
+ };
2868
+ function retryableControlFailure(value) {
2869
+ if (!value || typeof value !== "object") return false;
2870
+ const failure = value;
2871
+ if (failure.code === "invalid_response" || typeof failure.status !== "number") return false;
2872
+ return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
2873
+ }
2874
+ function wait(ms, signal) {
2875
+ return new Promise((resolve52) => {
2876
+ if (signal?.aborted) return resolve52();
2877
+ const timer = setTimeout(resolve52, ms);
2878
+ signal?.addEventListener("abort", () => {
2879
+ clearTimeout(timer);
2880
+ resolve52();
2881
+ }, { once: true });
2882
+ });
2883
+ }
2884
+ var CodeRuntimeControlError = class extends Error {
2885
+ constructor(message3, status, code = "control_error") {
2886
+ super(message3);
2887
+ this.status = status;
2888
+ this.code = code;
2889
+ }
2890
+ status;
2891
+ code;
2892
+ name = "CodeRuntimeControlError";
2893
+ };
2894
+ function createCodeRuntimeControlClient(options) {
2895
+ const endpoint = validatedEndpoint(options.endpoint);
2896
+ if (!/^odla_code_host_[0-9a-f]{64}$/.test(options.token)) throw new TypeError("invalid Code host credential");
2897
+ const requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
2898
+ if (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1e3 || requestTimeoutMs > 12e4) {
2899
+ throw new TypeError("requestTimeoutMs must be an integer from 1000 to 120000");
2900
+ }
2901
+ const request = options.fetch ?? fetch;
2902
+ const call = async (path, body) => {
2903
+ const timeout = AbortSignal.timeout(requestTimeoutMs);
2904
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
2905
+ let response2;
2906
+ try {
2907
+ response2 = await request(`${endpoint}${path}`, {
2908
+ method: "POST",
2909
+ headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
2910
+ body: JSON.stringify(body),
2911
+ redirect: "error",
2912
+ signal
2913
+ });
2914
+ } catch (cause) {
2915
+ if (options.signal?.aborted) throw cause;
2916
+ throw new CodeRuntimeControlError("Code runtime control plane is unavailable", 503, "transport_unavailable");
2917
+ }
2918
+ const value = await response2.json().catch(() => null);
2919
+ if (!response2.ok) {
2920
+ const problem = record3(record3(value)?.error);
2921
+ throw new CodeRuntimeControlError(
2922
+ typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
2923
+ response2.status,
2924
+ typeof problem?.code === "string" ? problem.code : void 0
2925
+ );
2926
+ }
2927
+ return value;
2928
+ };
2929
+ return {
2930
+ heartbeat: async (version, capabilities) => {
2931
+ validateHeartbeat(version, capabilities);
2932
+ return parseSnapshot(await call("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
2933
+ },
2934
+ acknowledge: async (commandId, result) => {
2935
+ if (!/^ccmd_[0-9a-f]{32}$/.test(commandId)) throw new TypeError("invalid Code runtime command id");
2936
+ await call(`/registry/code/runtime/commands/${commandId}/ack`, result);
2937
+ },
2938
+ source: async (sessionId) => parseSource(
2939
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
2940
+ ),
2941
+ infer: async (sessionId, inference) => {
2942
+ const value = record3(await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`, inference));
2943
+ if (!value || value.requestId !== inference.requestId || !record3(value.response) || !record3(value.receipt)) {
2944
+ throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
2945
+ }
2946
+ return value;
2947
+ },
2948
+ review: async (sessionId, review) => parseReview(
2949
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review)
2950
+ ),
2951
+ submitCandidate: async (sessionId, checkpointId, verification) => {
2952
+ if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
2953
+ return parseCandidate(await call(
2954
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
2955
+ { checkpointId, verification }
2956
+ ));
2957
+ },
2958
+ appendSessionEvent: async (sessionId, eventId, event) => {
2959
+ const serialized = JSON.stringify(event);
2960
+ if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
2961
+ throw new TypeError("invalid Code session event");
2962
+ }
2963
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
2964
+ },
2965
+ reportSessionFailure: async (sessionId, message3) => {
2966
+ if (!message3.trim() || message3.length > 2e3) throw new TypeError("invalid Code session failure");
2967
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message3 });
2968
+ }
2969
+ };
2970
+ }
2971
+ function validatedEndpoint(value) {
2972
+ const endpoint = value.replace(/\/+$/, "");
2973
+ let url;
2974
+ try {
2975
+ url = new URL(endpoint);
2976
+ } catch {
2977
+ throw new TypeError("endpoint must be an HTTPS URL");
2978
+ }
2979
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
2980
+ if (url.username || url.password || url.protocol !== "https:" && !(loopback && url.protocol === "http:")) {
2981
+ throw new TypeError("endpoint must use HTTPS (HTTP is allowed only for loopback testing)");
2982
+ }
2983
+ return endpoint;
2984
+ }
2985
+ function validSessionId(value) {
2986
+ if (!/^csess_[0-9a-f]{32}$/.test(value)) throw new TypeError("invalid Code session id");
2987
+ return value;
2988
+ }
2989
+ function validateHeartbeat(version, capabilities) {
2990
+ if (!version.trim() || version.length > 80) throw new TypeError("runtimeVersion is required and at most 80 characters");
2991
+ if (capabilities.protocolVersion !== CODE_RUNTIME_PROTOCOL_VERSION) throw new TypeError("unsupported Code runtime protocol version");
2992
+ if (capabilities.platform !== "macos" && capabilities.platform !== "linux") throw new TypeError("invalid runtime platform");
2993
+ if (!capabilities.arch || !capabilities.engines.length || !capabilities.engines.every((engine) => ["container", "podman", "docker"].includes(engine))) {
2994
+ throw new TypeError("runtime arch and supported engine are required");
2995
+ }
2996
+ if (!Number.isSafeInteger(capabilities.cpuCount) || capabilities.cpuCount < 1 || !Number.isSafeInteger(capabilities.memoryBytes) || capabilities.memoryBytes < 1) {
2997
+ throw new TypeError("runtime resources must be positive integers");
2998
+ }
2999
+ }
3000
+ function parseSnapshot(value) {
3001
+ const root = record3(value);
3002
+ const host = record3(root?.host);
3003
+ if (!host || typeof host.hostId !== "string" || typeof host.runtimeVersion !== "string" || !Number.isSafeInteger(host.lastSeenAt) || host.revokedAt !== null || !Array.isArray(root?.bindings) || root.bindings.length > 1024 || !Array.isArray(root?.commands) || root.commands.length > 64) throw invalid("heartbeat");
3004
+ const bindingIds = /* @__PURE__ */ new Set();
3005
+ const bindings = root.bindings.map((item) => {
3006
+ const binding = record3(item);
3007
+ if (!binding || typeof binding.bindingId !== "string" || typeof binding.appId !== "string" || binding.env !== "dev" && binding.env !== "prod" || typeof binding.offerId !== "string" || binding.hostId !== host.hostId || !Number.isSafeInteger(binding.generation) || Number(binding.generation) < 1 || binding.revokedAt !== null || bindingIds.has(binding.bindingId)) {
3008
+ throw invalid("binding");
3009
+ }
3010
+ bindingIds.add(binding.bindingId);
3011
+ return binding;
3012
+ });
3013
+ const commandIds = /* @__PURE__ */ new Set();
3014
+ const commandSequences = /* @__PURE__ */ new Set();
3015
+ const commands = root.commands.map((item) => {
3016
+ const command = record3(item);
3017
+ const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
3018
+ const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
3019
+ if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !record3(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
3020
+ commandIds.add(command.commandId);
3021
+ commandSequences.add(sequenceKey);
3022
+ return command;
3023
+ });
3024
+ return { host, bindings, commands };
3025
+ }
3026
+ async function parseSource(value) {
3027
+ const snapshot = record3(record3(value)?.snapshot);
3028
+ if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
3029
+ const files = snapshot.files.map((value2) => {
3030
+ const file = record3(value2);
3031
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
3032
+ return { path: file.path, content: file.content };
3033
+ });
3034
+ const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3035
+ const digest2 = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3036
+ if (digest2 !== snapshot.treeDigest) throw invalid("source digest");
3037
+ return { ...source, treeDigest: digest2 };
3038
+ }
3039
+ function parseReview(value) {
3040
+ const review = record3(record3(value)?.review);
3041
+ if (!review || !["approved", "rejected"].includes(String(review.verdict)) || typeof review.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(review.reviewDigest) || typeof review.provider !== "string" || !review.provider || typeof review.model !== "string" || !review.model || !Number.isSafeInteger(review.policyVersion) || Number(review.policyVersion) < 1) throw invalid("review");
3042
+ return review;
3043
+ }
3044
+ function parseCandidate(value) {
3045
+ const candidate = record3(record3(value)?.candidate);
3046
+ if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
3047
+ throw invalid("candidate");
3048
+ }
3049
+ return { candidateId: candidate.candidateId, status: candidate.status };
3050
+ }
3051
+ var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
3052
+ var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
3053
+ var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3054
+ var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3055
+ var PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
3056
+ var FORBIDDEN = /^(?:GIT binary patch|Binary files |rename (?:from|to) |copy (?:from|to) |similarity index |old mode |new mode |deleted file mode 160000|new file mode 160000)/m;
3057
+ function validateCodePatch(patch2, maxBytes) {
3058
+ if (!patch2 || Buffer.byteLength(patch2) > maxBytes || patch2.includes("\0") || patch2.includes("\r")) {
3059
+ throw new TypeError("patch is empty, malformed, or exceeds its byte limit");
3060
+ }
3061
+ if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
3062
+ throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
3063
+ }
3064
+ const paths = [];
3065
+ const lines = patch2.split("\n");
3066
+ for (let index = 0; index < lines.length; index += 1) {
3067
+ const line = lines[index];
3068
+ if (!line.startsWith("diff --git ")) continue;
3069
+ const match = /^diff --git a\/(\S+) b\/(\S+)$/.exec(line);
3070
+ const path = match?.[1];
3071
+ if (!path || !match?.[2] || path !== match[2]) throw new TypeError("patch must use one unquoted relative path per diff");
3072
+ validateRelativePath(path);
3073
+ const header = lines.slice(index + 1).findIndex((candidate) => candidate.startsWith("diff --git "));
3074
+ const section = lines.slice(index + 1, header < 0 ? lines.length : index + 1 + header);
3075
+ const oldPath = section.find((candidate) => candidate.startsWith("--- "))?.slice(4);
3076
+ const newPath = section.find((candidate) => candidate.startsWith("+++ "))?.slice(4);
3077
+ if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
3078
+ throw new TypeError("patch file headers do not match the declared path");
3079
+ }
3080
+ paths.push(path);
3081
+ }
3082
+ if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
3083
+ return paths;
3084
+ }
3085
+ function validHeaderPath(value, path, prefix) {
3086
+ return value === "/dev/null" || value === `${prefix}/${path}`;
3087
+ }
3088
+ function validateRelativePath(path) {
3089
+ const parts = path.split("/");
3090
+ if (!PATH.test(path) || parts.some((part) => part === "." || part === ".." || RESERVED.has(part)) || parts.some((part) => SECRET.test(part))) {
3091
+ throw new TypeError("path is outside the allowed staged source tree");
3092
+ }
3093
+ }
3094
+ function resolveCodePath(workspaceDir, path) {
3095
+ validateRelativePath(path);
3096
+ const root = resolve4(workspaceDir);
3097
+ const target = resolve4(root, path);
3098
+ if (target !== root && !target.startsWith(`${root}${sep}`)) throw new TypeError("path escapes the staged workspace");
3099
+ return target;
3100
+ }
3101
+ async function applyCodePatch(workspaceDir, patch2, paths) {
3102
+ await gitApply(workspaceDir, patch2, true);
3103
+ await gitApply(workspaceDir, patch2, false);
3104
+ for (const path of paths) {
3105
+ try {
3106
+ const info = await lstat(resolveCodePath(workspaceDir, path));
3107
+ if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
3108
+ throw new TypeError("patch created a non-regular workspace entry");
3109
+ }
3110
+ } catch (reason) {
3111
+ if (reason.code !== "ENOENT") throw reason;
3112
+ }
3113
+ }
3114
+ }
3115
+ function gitApply(cwd, patch2, check) {
3116
+ return new Promise((accept, reject) => {
3117
+ const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
3118
+ const child = spawn3("git", args, {
3119
+ cwd,
3120
+ shell: false,
3121
+ stdio: ["pipe", "ignore", "pipe"],
3122
+ env: { PATH: process.env.PATH ?? "", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null" }
3123
+ });
3124
+ let stderr = "";
3125
+ child.stderr.setEncoding("utf8");
3126
+ child.stderr.on("data", (text) => {
3127
+ if (stderr.length < 4e3) stderr += text.slice(0, 4e3);
3128
+ });
3129
+ child.once("error", reject);
3130
+ child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
3131
+ child.stdin.end(patch2);
3132
+ });
3133
+ }
3134
+ async function createCodeWorkspaceCheckpoint(input) {
3135
+ const maximum = input.maximumPatchBytes ?? 256 * 1024;
3136
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > 256 * 1024) {
3137
+ throw new TypeError("checkpoint patch bound must be from 1 to 262144 bytes");
3138
+ }
3139
+ const patch2 = await input.workspace.patch(maximum);
3140
+ if (patch2) validateCodePatch(patch2, maximum);
3141
+ return createCodePortableCheckpoint({ baseCommitSha: input.baseCommitSha, patch: patch2, state: input.state });
3142
+ }
3143
+ async function restoreCodeWorkspaceCheckpoint(input) {
3144
+ const checkpoint = await verifyCodePortableCheckpoint(input.checkpoint);
3145
+ if (checkpoint.baseCommitSha !== input.trustedBaseCommitSha) {
3146
+ throw new TypeError("checkpoint trusted base does not match the fetched commit");
3147
+ }
3148
+ const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
3149
+ try {
3150
+ if (checkpoint.patch) {
3151
+ const paths = validateCodePatch(checkpoint.patch, 256 * 1024);
3152
+ await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
3153
+ }
3154
+ return { workspace, checkpoint };
3155
+ } catch (error) {
3156
+ await workspace.cleanup();
3157
+ throw error;
3158
+ }
3159
+ }
3160
+ var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
3161
+ var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
3162
+ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${randomUUID().slice(0, 12)}`) {
3163
+ assertCodeBuildRecipe(recipe2);
3164
+ if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
3165
+ const uid = typeof getuid2 === "function" ? getuid2() : 1e3;
3166
+ const gid = typeof getgid2 === "function" ? getgid2() : 1e3;
3167
+ const limits = {
3168
+ cpus: recipe2.cpus ?? 1,
3169
+ memory: recipe2.memory ?? "1g",
3170
+ pids: recipe2.pids ?? 256,
3171
+ tmpfs: recipe2.tmpfsBytes ?? 64 * 1024 * 1024
3172
+ };
3173
+ if (engine === "container") {
3174
+ return [
3175
+ "run",
3176
+ "--rm",
3177
+ `--name=${name}`,
3178
+ "--network=none",
3179
+ "--read-only",
3180
+ "--cap-drop=ALL",
3181
+ `--memory=${limits.memory}`,
3182
+ `--cpus=${limits.cpus}`,
3183
+ `--user=${uid}:${gid}`,
3184
+ "--tmpfs=/tmp",
3185
+ `--mount=type=bind,source=${workspaceDir},target=/workspace`,
3186
+ "--workdir=/workspace",
3187
+ "--env=CI=1",
3188
+ recipe2.image,
3189
+ ...recipe2.command
3190
+ ];
3191
+ }
3192
+ return [
3193
+ "run",
3194
+ "--rm",
3195
+ `--name=${name}`,
3196
+ "--pull=never",
3197
+ "--network=none",
3198
+ "--read-only",
3199
+ "--cap-drop=ALL",
3200
+ "--security-opt=no-new-privileges",
3201
+ `--pids-limit=${limits.pids}`,
3202
+ `--memory=${limits.memory}`,
3203
+ `--cpus=${limits.cpus}`,
3204
+ `--user=${uid}:${gid}`,
3205
+ `--tmpfs=/tmp:rw,noexec,nosuid,nodev,size=${limits.tmpfs}`,
3206
+ `--mount=type=bind,src=${workspaceDir},dst=/workspace`,
3207
+ "--workdir=/workspace",
3208
+ "--env=CI=1",
3209
+ recipe2.image,
3210
+ ...recipe2.command
3211
+ ];
3212
+ }
3213
+ function createContainerRecipeExecutor(engine) {
3214
+ return {
3215
+ async run(input) {
3216
+ await verifyContainerEngineBoundary(engine);
3217
+ const name = `odla-recipe-${randomUUID().slice(0, 12)}`;
3218
+ const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name);
3219
+ return execute(engine, args, name, input.recipe, input.signal);
3220
+ }
3221
+ };
3222
+ }
3223
+ function assertCodeBuildRecipe(recipe2) {
3224
+ const memoryBytes = parseMemory(recipe2.memory ?? "1g");
3225
+ const tmpfsBytes = recipe2.tmpfsBytes ?? 64 * 1024 * 1024;
3226
+ const artifacts = recipe2.expectedArtifacts ?? [];
3227
+ assertPinnedImage(recipe2.image);
3228
+ if (!/^[A-Za-z0-9._:-]{1,120}$/.test(recipe2.id) || recipe2.command.length < 1 || recipe2.command.length > 64 || recipe2.command.some((part) => !part || part.length > 4096 || /[\0\r\n]/.test(part)) || !Number.isSafeInteger(recipe2.timeoutMs) || recipe2.timeoutMs < 1 || recipe2.timeoutMs > 30 * 6e4 || !Number.isSafeInteger(recipe2.maxOutputBytes) || recipe2.maxOutputBytes < 1 || recipe2.maxOutputBytes > 16 * 1024 * 1024 || !Number.isFinite(recipe2.cpus ?? 1) || (recipe2.cpus ?? 1) < 0.1 || (recipe2.cpus ?? 1) > 32 || memoryBytes < 64 * 1024 * 1024 || memoryBytes > 32 * 1024 * 1024 * 1024 || !Number.isSafeInteger(recipe2.pids ?? 256) || (recipe2.pids ?? 256) < 16 || (recipe2.pids ?? 256) > 4096 || !Number.isSafeInteger(tmpfsBytes) || tmpfsBytes < 1024 * 1024 || tmpfsBytes > 1024 * 1024 * 1024 || artifacts.length > 64 || new Set(artifacts.map((item) => item.id)).size !== artifacts.length || artifacts.some((item) => !/^[A-Za-z0-9._:-]{1,120}$/.test(item.id) || !ARTIFACT_PATH.test(item.path) || item.path.split("/").some((part) => PRIVATE_ARTIFACT_PART.test(part)) || !Number.isSafeInteger(item.maximumBytes) || item.maximumBytes < 1 || item.maximumBytes > 512 * 1024 * 1024)) {
3229
+ throw new TypeError("build recipe is malformed or exceeds its control bounds");
3230
+ }
3231
+ }
3232
+ function parseMemory(value) {
3233
+ const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(value.toLowerCase());
3234
+ if (!match?.[1] || !match[2]) return 0;
3235
+ const scale = match[2] === "k" ? 1024 : match[2] === "m" ? 1024 ** 2 : 1024 ** 3;
3236
+ return Number(match[1]) * scale;
3237
+ }
3238
+ function execute(engine, args, name, recipe2, signal) {
3239
+ return new Promise((accept, reject) => {
3240
+ const started = Date.now();
3241
+ const child = spawn23(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3242
+ const stdout = [];
3243
+ const stderr = [];
3244
+ let bytes = 0;
3245
+ let outputLimitExceeded = false;
3246
+ let timedOut = false;
3247
+ let stopping = false;
3248
+ const stop = (reason) => {
3249
+ if (stopping) return;
3250
+ stopping = true;
3251
+ timedOut = reason === "timeout";
3252
+ outputLimitExceeded = reason === "output";
3253
+ const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
3254
+ const killer = spawn23(engine, remove, { shell: false, stdio: "ignore" });
3255
+ killer.unref();
3256
+ child.kill("SIGTERM");
3257
+ };
3258
+ const collect = (target) => (chunk) => {
3259
+ bytes += chunk.byteLength;
3260
+ if (bytes > recipe2.maxOutputBytes) stop("output");
3261
+ else target.push(chunk);
3262
+ };
3263
+ child.stdout.on("data", collect(stdout));
3264
+ child.stderr.on("data", collect(stderr));
3265
+ const abort = () => stop("abort");
3266
+ signal?.addEventListener("abort", abort, { once: true });
3267
+ if (signal?.aborted) abort();
3268
+ const timer = setTimeout(() => stop("timeout"), recipe2.timeoutMs);
3269
+ child.once("error", (error) => {
3270
+ clearTimeout(timer);
3271
+ signal?.removeEventListener("abort", abort);
3272
+ reject(error);
3273
+ });
3274
+ child.once("exit", (code) => {
3275
+ clearTimeout(timer);
3276
+ signal?.removeEventListener("abort", abort);
3277
+ accept({
3278
+ exitCode: code ?? 1,
3279
+ stdout: Buffer.concat(stdout).toString("utf8"),
3280
+ stderr: Buffer.concat(stderr).toString("utf8"),
3281
+ durationMs: Date.now() - started,
3282
+ outputLimitExceeded,
3283
+ timedOut
3284
+ });
3285
+ });
3286
+ });
3287
+ }
3288
+ async function digestStagedWorkspace(root, limits) {
3289
+ const files = [];
3290
+ const walk = async (directory) => {
3291
+ const entries = await readdir2(directory, { withFileTypes: true });
3292
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3293
+ if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3294
+ const target = resolve22(directory, entry.name);
3295
+ if (entry.isDirectory()) await walk(target);
3296
+ else if (entry.isFile()) {
3297
+ files.push({ path: relative3(root, target).split("\\").join("/"), target });
3298
+ if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3299
+ }
3300
+ }
3301
+ };
3302
+ await walk(resolve22(root));
3303
+ const hash = createHash("sha256");
3304
+ let bytes = 0;
3305
+ for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3306
+ const content = await readFile(file.target);
3307
+ bytes += Buffer.byteLength(file.path) + content.byteLength;
3308
+ if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3309
+ hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3310
+ hash.update(content);
3311
+ }
3312
+ return `sha256:${hash.digest("hex")}`;
3313
+ }
3314
+ var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
3315
+ var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
3316
+ var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
3317
+ var RULE = /^[A-Za-z0-9_@+.,/-]{1,160}$/;
3318
+ var DEFAULT_PREFIXES = ["test/", "tests/", "__tests__/"];
3319
+ var DEFAULT_SUFFIXES = [".test.js", ".test.ts", ".test.tsx", ".spec.js", ".spec.ts", ".spec.tsx"];
3320
+ async function verifyCodeCandidate(input) {
3321
+ const policy = validate2(input);
3322
+ const limits = { maxFiles: policy.maximumFiles, maxBytes: policy.maximumBytes };
3323
+ const staged = await stageWorkspace(input.trustedBaseDir, limits);
3324
+ try {
3325
+ const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
3326
+ if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
3327
+ const paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
3328
+ await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
3329
+ const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
3330
+ const policyDigest = digestPolicy(policy);
3331
+ const patchDigest = digestBytes(input.candidatePatch);
3332
+ const candidateDigest = digestJson({
3333
+ trustedBaseCommitSha: input.trustedBaseCommitSha,
3334
+ trustedBaseDigest: input.trustedBaseDigest,
3335
+ patchDigest
3336
+ });
3337
+ const changedTests = changedTestPaths(paths, policy);
3338
+ if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
3339
+ const recipes = [];
3340
+ const logs = [];
3341
+ for (const recipe2 of policy.recipes) {
3342
+ const clean = await stageWorkspace(staged.workspaceDir, limits);
3343
+ try {
3344
+ if (await digestStagedWorkspace(clean.workspaceDir, limits) !== sourceDigest) {
3345
+ throw new TypeError("clean verifier source changed before execution");
3346
+ }
3347
+ const result = checkedResult(await input.recipeExecutor.run({
3348
+ workspaceDir: clean.workspaceDir,
3349
+ recipe: recipe2,
3350
+ signal: input.signal
3351
+ }), recipe2.maxOutputBytes);
3352
+ const artifacts = await inspectArtifacts(clean.workspaceDir, recipe2);
3353
+ recipes.push(recipeReceipt(recipe2, result, artifacts));
3354
+ logs.push({ recipeId: recipe2.id, ...boundedLogs(result, recipe2.maxOutputBytes) });
3355
+ } finally {
3356
+ await clean.cleanup();
3357
+ }
3358
+ }
3359
+ const fields = {
3360
+ schemaVersion: 1,
3361
+ verificationId: input.verificationId ?? `verify-${randomUUID2()}`,
3362
+ trustedBaseCommitSha: input.trustedBaseCommitSha,
3363
+ trustedBaseDigest: input.trustedBaseDigest,
3364
+ patchDigest,
3365
+ candidateDigest,
3366
+ sourceDigest,
3367
+ policyDigest,
3368
+ recipes,
3369
+ changedTestCount: changedTests.length,
3370
+ changedTestSetDigest: digestJson(changedTests),
3371
+ changedTestsRequireReview: changedTests.length > 0,
3372
+ outcome: recipes.every((recipe2) => recipe2.status === "passed") ? "passed" : "failed"
3373
+ };
3374
+ return {
3375
+ receipt: { ...fields, receiptDigest: await digestCodeVerificationReceipt(fields) },
3376
+ changedTests: Object.freeze(changedTests),
3377
+ logs: Object.freeze(logs)
3378
+ };
3379
+ } finally {
3380
+ await staged.cleanup();
3381
+ }
3382
+ }
3383
+ function validate2(input) {
3384
+ const policy = input.policy;
3385
+ if (!SHA3.test(input.trustedBaseCommitSha) || !DIGEST3.test(input.trustedBaseDigest) || !ID3.test(input.verificationId ?? "verify-generated") || !ID3.test(policy.policyId) || policy.recipes.length < 1 || policy.recipes.length > 64 || new Set(policy.recipes.map((recipe2) => recipe2.id)).size !== policy.recipes.length) {
3386
+ throw new TypeError("clean verification input is malformed");
3387
+ }
3388
+ for (const recipe2 of policy.recipes) assertCodeBuildRecipe(recipe2);
3389
+ const result = {
3390
+ policyId: policy.policyId,
3391
+ recipes: policy.recipes,
3392
+ testPathPrefixes: policy.testPathPrefixes ?? DEFAULT_PREFIXES,
3393
+ testPathSuffixes: policy.testPathSuffixes ?? DEFAULT_SUFFIXES,
3394
+ maximumChangedTests: policy.maximumChangedTests ?? 1e3,
3395
+ maximumPatchBytes: policy.maximumPatchBytes ?? 256 * 1024,
3396
+ maximumFiles: policy.maximumFiles ?? 2e4,
3397
+ maximumBytes: policy.maximumBytes ?? 512 * 1024 * 1024
3398
+ };
3399
+ if ([...result.testPathPrefixes, ...result.testPathSuffixes].some((rule) => !RULE.test(rule)) || !integer(result.maximumChangedTests, 0, 1e4) || !integer(result.maximumPatchBytes, 1, 4 * 1024 * 1024) || !integer(result.maximumFiles, 1, 1e5) || !integer(result.maximumBytes, 1, 2 * 1024 * 1024 * 1024)) {
3400
+ throw new TypeError("clean verification policy exceeds its bounds");
3401
+ }
3402
+ return result;
3403
+ }
3404
+ function changedTestPaths(paths, policy) {
3405
+ return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
3406
+ }
3407
+ function recipeReceipt(recipe2, result, artifacts) {
3408
+ const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
3409
+ return {
3410
+ recipeId: recipe2.id,
3411
+ recipeDigest: digestRecipe(recipe2),
3412
+ status,
3413
+ exitCode: result.exitCode,
3414
+ durationMs: result.durationMs,
3415
+ artifacts
3416
+ };
3417
+ }
3418
+ async function inspectArtifacts(workspaceDir, recipe2) {
3419
+ const receipts = [];
3420
+ for (const artifact of recipe2.expectedArtifacts ?? []) {
3421
+ try {
3422
+ const path = join3(workspaceDir, artifact.path);
3423
+ const info = await lstat2(path);
3424
+ if (!info.isFile() || info.isSymbolicLink()) {
3425
+ receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
3426
+ } else if (info.size > artifact.maximumBytes) {
3427
+ receipts.push({ artifactId: artifact.id, status: "too_large", bytes: info.size, digest: null });
3428
+ } else {
3429
+ receipts.push({ artifactId: artifact.id, status: "verified", bytes: info.size, digest: await hashFile(path) });
3430
+ }
3431
+ } catch (reason) {
3432
+ if (reason.code !== "ENOENT") throw reason;
3433
+ receipts.push({ artifactId: artifact.id, status: "missing", bytes: null, digest: null });
3434
+ }
3435
+ }
3436
+ return receipts;
3437
+ }
3438
+ function hashFile(path) {
3439
+ return new Promise((accept, reject) => {
3440
+ const hash = createHash2("sha256");
3441
+ const stream = createReadStream(path);
3442
+ stream.on("data", (chunk) => {
3443
+ hash.update(chunk);
3444
+ });
3445
+ stream.once("error", reject);
3446
+ stream.once("end", () => accept(`sha256:${hash.digest("hex")}`));
3447
+ });
3448
+ }
3449
+ function checkedResult(result, maximumOutputBytes) {
3450
+ if (!integer(result.exitCode, 0, 255) || !integer(result.durationMs, 0, 30 * 6e4) || typeof result.stdout !== "string" || typeof result.stderr !== "string" || typeof result.outputLimitExceeded !== "boolean" || typeof result.timedOut !== "boolean") {
3451
+ throw new TypeError("recipe executor returned an invalid result");
3452
+ }
3453
+ const bytes = Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr);
3454
+ return bytes > maximumOutputBytes ? { ...result, outputLimitExceeded: true } : result;
3455
+ }
3456
+ function boundedLogs(result, maximum) {
3457
+ const stdout = Buffer.from(result.stdout);
3458
+ const stderr = Buffer.from(result.stderr);
3459
+ const first = stdout.subarray(0, maximum);
3460
+ return {
3461
+ stdout: first.toString("utf8"),
3462
+ stderr: stderr.subarray(0, Math.max(0, maximum - first.byteLength)).toString("utf8")
3463
+ };
3464
+ }
3465
+ function digestPolicy(policy) {
3466
+ return digestJson({
3467
+ policyId: policy.policyId,
3468
+ recipes: policy.recipes.map((recipe2) => normalizedRecipe(recipe2)),
3469
+ testPathPrefixes: [...policy.testPathPrefixes].sort(),
3470
+ testPathSuffixes: [...policy.testPathSuffixes].sort(),
3471
+ maximumChangedTests: policy.maximumChangedTests,
3472
+ maximumPatchBytes: policy.maximumPatchBytes,
3473
+ maximumFiles: policy.maximumFiles,
3474
+ maximumBytes: policy.maximumBytes
3475
+ });
3476
+ }
3477
+ function digestRecipe(recipe2) {
3478
+ return digestJson(normalizedRecipe(recipe2));
3479
+ }
3480
+ function normalizedRecipe(recipe2) {
3481
+ return {
3482
+ id: recipe2.id,
3483
+ image: recipe2.image,
3484
+ command: [...recipe2.command],
3485
+ timeoutMs: recipe2.timeoutMs,
3486
+ maxOutputBytes: recipe2.maxOutputBytes,
3487
+ cpus: recipe2.cpus ?? 1,
3488
+ memory: recipe2.memory ?? "1g",
3489
+ pids: recipe2.pids ?? 256,
3490
+ tmpfsBytes: recipe2.tmpfsBytes ?? 64 * 1024 * 1024,
3491
+ expectedArtifacts: [...recipe2.expectedArtifacts ?? []].sort((left, right) => left.id.localeCompare(right.id)).map((artifact) => ({ id: artifact.id, path: artifact.path, maximumBytes: artifact.maximumBytes }))
3492
+ };
3493
+ }
3494
+ function digestJson(value) {
3495
+ return digestBytes(JSON.stringify(value));
3496
+ }
3497
+ function digestBytes(value) {
3498
+ return `sha256:${createHash2("sha256").update(value).digest("hex")}`;
3499
+ }
3500
+ function integer(value, minimum, maximum) {
3501
+ return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
3502
+ }
3503
+ async function prepareRuntimeCheckpoint(input) {
3504
+ const patch2 = await input.workspace.patch(256 * 1024);
3505
+ let verification = null;
3506
+ let review = null;
3507
+ let note = patch2 ? "Candidate remains untrusted" : "Checkpoint has no source changes";
3508
+ if (patch2 && input.role === "coding") {
3509
+ try {
3510
+ const evidence = await verifyCodeCandidate({
3511
+ verificationId: `verify-${input.sessionId.slice("csess_".length)}`,
3512
+ trustedBaseDir: input.workspace.baselineDir,
3513
+ trustedBaseCommitSha: input.baseCommitSha,
3514
+ trustedBaseDigest: input.trustedBaseDigest,
3515
+ candidatePatch: patch2,
3516
+ policy: {
3517
+ policyId: "code.runtime",
3518
+ recipes: input.recipes,
3519
+ maximumFiles: 1e4,
3520
+ maximumBytes: 16 * 1024 * 1024
3521
+ },
3522
+ recipeExecutor: input.recipeExecutor
3523
+ });
3524
+ if (evidence.receipt.outcome === "passed") {
3525
+ verification = evidence.receipt;
3526
+ review = await input.review(patch2, verification);
3527
+ note = review.verdict === "approved" ? "Clean verification and independent review passed" : "Clean verification passed; independent review rejected the candidate";
3528
+ } else {
3529
+ note = `Clean verification failed: ${evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed").map((recipe2) => `${recipe2.recipeId}=${recipe2.status}`).join(", ")}`;
3530
+ }
3531
+ } catch (cause) {
3532
+ note = `Candidate verification or review failed closed: ${message(cause)}`;
3533
+ }
3534
+ }
3535
+ const reviewed = verification && review?.verdict === "approved";
3536
+ const checkpoint = await createCodeWorkspaceCheckpoint({
3537
+ workspace: input.workspace,
3538
+ baseCommitSha: input.baseCommitSha,
3539
+ state: {
3540
+ planCursor: null,
3541
+ conversationRefs: input.conversationRefs,
3542
+ planningInputDigest: input.planningInputDigest,
3543
+ buildPolicyDigest: verification?.policyDigest ?? input.fallbackPolicyDigest,
3544
+ dependencyLayerDigest: null,
3545
+ verificationDigest: verification?.receiptDigest ?? null,
3546
+ reviewDigest: reviewed && review ? review.reviewDigest : null,
3547
+ completedEffects: [],
3548
+ unresolvedApprovals: [],
3549
+ trustStatus: reviewed ? "reviewed" : verification ? "verified" : "candidate_untrusted"
3550
+ }
3551
+ });
3552
+ return { checkpoint, verification, review, note };
3553
+ }
3554
+ var message = (value) => (value instanceof Error ? value.message : String(value)).slice(0, 500);
3555
+ var CodeRuntimeCheckpointManager = class {
3556
+ constructor(options) {
3557
+ this.options = options;
3558
+ }
3559
+ options;
3560
+ #pending = /* @__PURE__ */ new Map();
3561
+ async prepare(command, active) {
3562
+ active.abort.abort("checkpoint_stop");
3563
+ await active.done;
3564
+ const prepared = await prepareRuntimeCheckpoint({
3565
+ sessionId: command.sessionId,
3566
+ role: active.role,
3567
+ workspace: active.workspace,
3568
+ baseCommitSha: active.baseCommitSha,
3569
+ trustedBaseDigest: active.trustedBaseDigest,
3570
+ planningInputDigest: active.planningInputDigest,
3571
+ conversationRefs: active.conversationRefs,
3572
+ fallbackPolicyDigest: this.options.fallbackPolicyDigest,
3573
+ recipes: this.options.recipes,
3574
+ recipeExecutor: this.options.recipeExecutor,
3575
+ review: (patch2, verification) => this.options.control.review(command.sessionId, { patch: patch2, verification })
3576
+ });
3577
+ if (prepared.review?.verdict === "approved" && prepared.verification) {
3578
+ this.#pending.set(command.commandId, { verification: prepared.verification, refs: active.conversationRefs });
3579
+ }
3580
+ await this.options.event(command, { type: "message", actor: "system", body: prepared.note }, active.conversationRefs).catch(() => void 0);
3581
+ await active.workspace.cleanup();
3582
+ await this.options.event(command, { type: "status", status: "checkpointed" }, active.conversationRefs).catch(() => void 0);
3583
+ return { status: "checkpointed", checkpoint: prepared.checkpoint, message: "Pi stopped at a portable checkpoint" };
3584
+ }
3585
+ async acknowledged(command, result) {
3586
+ if (command.kind !== "checkpoint_stop" || result.status !== "checkpointed") return false;
3587
+ const pending = this.#pending.get(command.commandId);
3588
+ if (!pending) return true;
3589
+ const checkpointId = `cpoint_${command.commandId.slice("ccmd_".length)}`;
3590
+ const candidate = await this.options.control.submitCandidate(command.sessionId, checkpointId, pending.verification);
3591
+ await this.options.event(command, {
3592
+ type: "message",
3593
+ actor: "system",
3594
+ body: `Candidate ${candidate.candidateId} is ready for exact owner publication approval`
3595
+ }, pending.refs);
3596
+ this.#pending.delete(command.commandId);
3597
+ return true;
3598
+ }
3599
+ };
3600
+ var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3601
+ var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3602
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir2()) {
3603
+ if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
3604
+ const root = await mkdtemp2(join23(tempRoot, "odla-code-source-"));
3605
+ const sourceDir = join23(root, "source");
3606
+ await mkdir2(sourceDir);
3607
+ const seen = /* @__PURE__ */ new Set();
3608
+ let bytes = 0;
3609
+ try {
3610
+ for (const file of snapshot.files) {
3611
+ validatePath(file.path);
3612
+ if (seen.has(file.path)) throw new TypeError("Code source repeats a path");
3613
+ seen.add(file.path);
3614
+ bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
3615
+ if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
3616
+ const target = resolve32(sourceDir, file.path);
3617
+ if (!target.startsWith(`${resolve32(sourceDir)}${sep2}`)) throw new TypeError("Code source path escapes its root");
3618
+ await mkdir2(dirname4(target), { recursive: true });
3619
+ await writeFile(target, file.content, { flag: "wx", mode: 420 });
3620
+ }
3621
+ return { sourceDir, cleanup: () => rm2(root, { recursive: true, force: true }) };
3622
+ } catch (cause) {
3623
+ await rm2(root, { recursive: true, force: true });
3624
+ throw cause;
3625
+ }
3626
+ }
3627
+ function validatePath(path) {
3628
+ const parts = path.split("/");
3629
+ if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
3630
+ throw new TypeError("Code source contains an unsafe path");
3631
+ }
3632
+ }
3633
+ var DESTINATIONS = "code-workspaces.v1";
3634
+ var READ = descriptor("sandbox.read", "scoped_data_read", {
3635
+ workspace: "destination",
3636
+ authority: "authority",
3637
+ path: "selector",
3638
+ startLine: "selector",
3639
+ endLine: "selector"
3640
+ });
3641
+ var PATCH = descriptor("sandbox.apply_patch", "reversible_mutation", {
3642
+ workspace: "destination",
3643
+ authority: "authority",
3644
+ patch: "payload"
3645
+ });
3646
+ var RECIPE = descriptor("sandbox.run_recipe", "code_execution", {
3647
+ workspace: "destination",
3648
+ authority: "authority",
3649
+ recipeId: "selector",
3650
+ sourceDigest: "payload"
3651
+ });
3652
+ function createCodePolicyGate(options) {
3653
+ return {
3654
+ read: async (input) => {
3655
+ const base = await environment(input, options, "sandbox.read");
3656
+ const conversions = await conversionRegistry([
3657
+ await registeredPolicy("code.path.v1", "code.paths.v1", input.paths),
3658
+ await conversionPolicy("code.line.v1", { kind: "integer", minimum: 1, maximum: 1e6 })
3659
+ ], { "code.paths.v1": input.paths });
3660
+ const path = await conversions.operations.registeredId(unsafe(base, input.path, "path"), "code.path.v1");
3661
+ const start = await conversions.operations.integer(unsafe(base, input.startLine, "start"), "code.line.v1");
3662
+ const end = await conversions.operations.integer(unsafe(base, input.endLine, "end"), "code.line.v1");
3663
+ if (end.value < start.value) return false;
3664
+ return authorize(input, options, base, READ, {
3665
+ ...base.fixedArgs,
3666
+ path: { role: "selector", value: path },
3667
+ startLine: { role: "selector", value: start },
3668
+ endLine: { role: "selector", value: end }
3669
+ }, [path, start, end]);
3670
+ },
3671
+ patch: async (input) => {
3672
+ const base = await environment(input, options, "sandbox.apply_patch");
3673
+ const patch2 = unsafe(base, input.patch, "patch");
3674
+ return authorize(input, options, base, PATCH, {
3675
+ ...base.fixedArgs,
3676
+ patch: { role: "payload", value: patch2 }
3677
+ }, []);
3678
+ },
3679
+ recipe: async (input) => {
3680
+ const base = await environment(input, options, "sandbox.run_recipe");
3681
+ const conversions = await conversionRegistry([
3682
+ await registeredPolicy("code.recipe.v1", "code.recipes.v1", input.recipeIds)
3683
+ ], { "code.recipes.v1": input.recipeIds });
3684
+ const recipe2 = await conversions.operations.registeredId(unsafe(base, input.recipeId, "recipe"), "code.recipe.v1");
3685
+ const source = unsafe(base, input.sourceDigest, "source");
3686
+ return authorize(input, options, base, RECIPE, {
3687
+ ...base.fixedArgs,
3688
+ recipeId: { role: "selector", value: recipe2 },
3689
+ sourceDigest: { role: "payload", value: source }
3690
+ }, [recipe2]);
3691
+ }
3692
+ };
3693
+ }
3694
+ function descriptor(name, effect, argumentRoles) {
3695
+ return { name, version: 1, effect, inputSchema: { type: "object" }, argumentRoles, policyId: `odla.code.${name}.v1` };
3696
+ }
3697
+ async function conversionPolicy(id, output) {
3698
+ const definition = {
3699
+ conversionId: id,
3700
+ version: 1,
3701
+ output,
3702
+ maximumSourceBytes: 1e6,
3703
+ maximumOutputsPerArtifact: 4,
3704
+ presentation: "json_scalar"
3705
+ };
3706
+ return { ...definition, digest: await conversionPolicyDigest(definition) };
3707
+ }
3708
+ async function registeredPolicy(id, registryId, values) {
3709
+ const mapping = Object.fromEntries(values.map((value) => [value, value]));
3710
+ return conversionPolicy(id, {
3711
+ kind: "registered_id",
3712
+ registryId,
3713
+ registryDigest: await registeredIdRegistryDigest(mapping)
3714
+ });
3715
+ }
3716
+ async function conversionRegistry(policies, values) {
3717
+ const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id, entries]) => {
3718
+ const mapping = Object.fromEntries(entries.map((value) => [value, value]));
3719
+ return [id, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
3720
+ })));
3721
+ return createConversionRegistry({ policies, registeredIds });
3722
+ }
3723
+ async function environment(input, options, tool) {
3724
+ const ingress = createCamelIngress([
3725
+ { id: "workspace", value: input.workspaceId, readers: input.readers },
3726
+ { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
3727
+ { id: "reader", value: options.readerId, readers: input.readers }
3728
+ ]);
3729
+ const digest2 = await destinationRegistryDigest([input.workspaceId]);
3730
+ const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
3731
+ if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
3732
+ const policy = createEffectPolicy({
3733
+ destinationRegistries: { [DESTINATIONS]: { digest: digest2, values: [input.workspaceId] } },
3734
+ approvalEffects: approvals
3735
+ });
3736
+ const fixedArgs = {
3737
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest2 },
3738
+ authority: { role: "authority", value: ingress.control("authority") }
3739
+ };
3740
+ return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
3741
+ }
3742
+ function unsafe(base, value, field) {
3743
+ return base.ingress.quarantinedOutput(value, {
3744
+ readers: base.reader.label.readers,
3745
+ runId: `${base.runId}:${field}`
3746
+ });
3747
+ }
3748
+ async function authorize(input, options, base, tool, args, controlDependencies) {
3749
+ const policy = await base.policy.evaluate({
3750
+ planId: input.lease.task.taskId,
3751
+ tool,
3752
+ args,
3753
+ controlDependencies,
3754
+ intendedReaderIds: [base.reader]
3755
+ });
3756
+ let approvalConsumed = false;
3757
+ if (policy.outcome === "require_approval" && options.consumeApproval) {
3758
+ approvalConsumed = await options.consumeApproval(decision(input, policy, false, tool.name, policy.actionDigest));
3759
+ }
3760
+ await options.onDecision?.(decision(input, policy, approvalConsumed, tool.name));
3761
+ return policy.outcome === "allow" || approvalConsumed;
3762
+ }
3763
+ function decision(input, policy, approvalConsumed, tool, actionDigest) {
3764
+ return {
3765
+ lease: input.lease,
3766
+ request: input.request,
3767
+ tool,
3768
+ policy,
3769
+ approvalConsumed,
3770
+ actionDigest: actionDigest ?? (policy.outcome === "require_approval" ? policy.actionDigest : "")
3771
+ };
3772
+ }
3773
+ function createCodeToolBroker(options) {
3774
+ validateOptions(options);
3775
+ const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
3776
+ const policy = createCodePolicyGate(options);
3777
+ let tail = Promise.resolve();
3778
+ return {
3779
+ execute(context, request) {
3780
+ const result = tail.then(() => route(context, request, options, recipes, policy));
3781
+ tail = result.then(() => void 0, () => void 0);
3782
+ return result;
3783
+ }
3784
+ };
3785
+ }
3786
+ async function route(context, request, options, recipes, policy) {
3787
+ try {
3788
+ if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
3789
+ if (request.tool === "sandbox.read") return await read(context, request, options, policy);
3790
+ if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy);
3791
+ return await recipe(context, request, options, recipes, policy);
3792
+ } catch (reason) {
3793
+ return response(request, false, reason instanceof TypeError ? reason.message : "tool failed closed");
3794
+ }
3795
+ }
3796
+ async function read(context, request, options, policy) {
3797
+ exactKeys(request.input, ["path", "startLine", "endLine"]);
3798
+ const path = stringField(request.input, "path");
3799
+ const startLine = optionalInteger(request.input.startLine) ?? 1;
3800
+ const endLine = optionalInteger(request.input.endLine) ?? startLine + (options.maxReadLines ?? 2e3) - 1;
3801
+ if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
3802
+ throw new TypeError("requested line range exceeds its bound");
3803
+ }
3804
+ const paths = await registeredFiles(context.workspaceDir, 2e4);
3805
+ const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
3806
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
3807
+ const target = resolveCodePath(context.workspaceDir, path);
3808
+ const info = await stat2(target);
3809
+ if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
3810
+ throw new TypeError("file is not a bounded regular source file");
3811
+ }
3812
+ const source = await readFile2(target);
3813
+ if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
3814
+ const lines = source.toString("utf8").split("\n");
3815
+ const content = lines.slice(startLine - 1, endLine).join("\n");
3816
+ if (Buffer.byteLength(content) > (options.maxReadBytes ?? 128 * 1024)) {
3817
+ throw new TypeError("read result exceeds its byte bound");
3818
+ }
3819
+ return response(request, true, content, { path, startLine, endLine: Math.min(endLine, lines.length) });
3820
+ }
3821
+ async function patch(context, request, options, policy) {
3822
+ exactKeys(request.input, ["patch"]);
3823
+ const value = stringField(request.input, "patch");
3824
+ const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
3825
+ const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
3826
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
3827
+ await applyCodePatch(context.workspaceDir, value, paths);
3828
+ return response(request, true, `Applied patch to ${paths.length} file(s).`, { paths });
3829
+ }
3830
+ async function recipe(context, request, options, recipes, policy) {
3831
+ exactKeys(request.input, ["recipeId"]);
3832
+ const recipeId = stringField(request.input, "recipeId");
3833
+ const digestLimits = {
3834
+ maxFiles: options.maxRecipeWorkspaceFiles ?? 2e4,
3835
+ maxBytes: options.maxRecipeWorkspaceBytes ?? 512 * 1024 * 1024
3836
+ };
3837
+ const sourceDigest = await digestStagedWorkspace(context.workspaceDir, digestLimits);
3838
+ const allowed = await policy.recipe(policyContext(context, request, options, {
3839
+ recipeIds: [...recipes.keys()].sort(),
3840
+ recipeId,
3841
+ sourceDigest
3842
+ }));
3843
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
3844
+ const selected = recipes.get(recipeId);
3845
+ if (!selected) return response(request, false, "build recipe is not registered");
3846
+ const staged = await stageWorkspace(context.workspaceDir, {
3847
+ maxFiles: digestLimits.maxFiles,
3848
+ maxBytes: digestLimits.maxBytes
3849
+ });
3850
+ try {
3851
+ if (await digestStagedWorkspace(staged.workspaceDir, digestLimits) !== sourceDigest) {
3852
+ throw new TypeError("workspace changed after recipe authorization");
3853
+ }
3854
+ const result = await options.recipeExecutor.run({
3855
+ workspaceDir: staged.workspaceDir,
3856
+ recipe: selected,
3857
+ signal: context.signal
3858
+ });
3859
+ const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
3860
+ const ok = result.exitCode === 0 && !result.outputLimitExceeded && !result.timedOut;
3861
+ const status = result.timedOut ? "timed out" : result.outputLimitExceeded ? "exceeded output limit" : ok ? "passed" : `failed with exit ${result.exitCode}`;
3862
+ return response(request, ok, `Recipe ${recipeId} ${status}.${output ? `
3863
+ ${output}` : ""}`, {
3864
+ recipeId,
3865
+ exitCode: result.exitCode,
3866
+ durationMs: result.durationMs,
3867
+ outputLimitExceeded: result.outputLimitExceeded,
3868
+ timedOut: result.timedOut
3869
+ });
3870
+ } finally {
3871
+ await staged.cleanup();
3872
+ }
3873
+ }
3874
+ function policyContext(context, request, options, extra) {
3875
+ return {
3876
+ lease: context.lease,
3877
+ request,
3878
+ workspaceId: `workspace:${context.lease.task.attemptId}`,
3879
+ readers: { kind: "principals", principalIds: [options.readerId] },
3880
+ ...extra
3881
+ };
3882
+ }
3883
+ async function registeredFiles(root, limit) {
3884
+ const paths = [];
3885
+ const walk = async (directory) => {
3886
+ for (const entry of await readdir22(directory, { withFileTypes: true })) {
3887
+ if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
3888
+ const target = resolve42(directory, entry.name);
3889
+ if (entry.isDirectory()) await walk(target);
3890
+ else if (entry.isFile()) {
3891
+ const path = relative22(root, target).split("\\").join("/");
3892
+ try {
3893
+ validateRelativePath(path);
3894
+ } catch {
3895
+ continue;
3896
+ }
3897
+ paths.push(path);
3898
+ if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
3899
+ }
3900
+ }
3901
+ };
3902
+ await walk(resolve42(root));
3903
+ return paths.sort();
3904
+ }
3905
+ function validateOptions(options) {
3906
+ if (!options.readerId || !options.recipes.length || new Set(options.recipes.map((item) => item.id)).size !== options.recipes.length) {
3907
+ throw new TypeError("Code tool broker requires a reader and unique registered recipes");
3908
+ }
3909
+ for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
3910
+ }
3911
+ function exactKeys(input, allowed) {
3912
+ if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
3913
+ }
3914
+ function stringField(input, name) {
3915
+ const value = input[name];
3916
+ if (typeof value !== "string" || !value) throw new TypeError(`${name} must be a non-empty string`);
3917
+ return value;
3918
+ }
3919
+ function optionalInteger(value) {
3920
+ if (value === void 0) return void 0;
3921
+ if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("line bounds must be positive integers");
3922
+ return value;
3923
+ }
3924
+ function response(request, ok, content, details) {
3925
+ return { requestId: request.requestId, ok, content, ...details ? { details } : {} };
3926
+ }
3927
+ function codeCommandMetadata(payload, resume) {
3928
+ const trusted = record22(payload.trustedBase);
3929
+ const role = payload.role;
3930
+ const title = payload.title;
3931
+ const prompt = payload.prompt;
3932
+ if (role !== "coding" && role !== "review" || typeof title !== "string" || typeof prompt !== "string") {
3933
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} metadata`);
3934
+ }
3935
+ const planning = trusted?.planningInputDigest;
3936
+ const attestation = trusted?.attestationDigest;
3937
+ return {
3938
+ role,
3939
+ title,
3940
+ prompt,
3941
+ planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
3942
+ attestationDigest: typeof attestation === "string" ? attestation : "resume"
3943
+ };
3944
+ }
3945
+ function codeCheckpointPayload(payload) {
3946
+ const value = payload.checkpoint;
3947
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
3948
+ return value;
3949
+ }
3950
+ function fakeCodeLease(command, metadata2) {
3951
+ return {
3952
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
3953
+ leaseId: `code:${command.commandId}`,
3954
+ generation: command.bindingGeneration,
3955
+ expiresAt: Date.now() + 24 * 60 * 6e4,
3956
+ task: {
3957
+ taskId: command.sessionId,
3958
+ attemptId: command.instanceId,
3959
+ title: metadata2.title,
3960
+ prompt: metadata2.prompt,
3961
+ workspace: command.appId,
3962
+ aiRoute: metadata2.role,
3963
+ policy: {
3964
+ network: "none",
3965
+ timeoutMs: 30 * 6e4,
3966
+ maxOutputBytes: 4 * 1024 * 1024,
3967
+ maxPatchBytes: 256 * 1024
3968
+ }
3969
+ }
3970
+ };
3971
+ }
3972
+ var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
3973
+ var CodePiRuntimeEngine = class {
3974
+ constructor(options) {
3975
+ this.options = options;
3976
+ this.#run = options.runAttempt ?? runContainerAttempt;
3977
+ this.#buildPolicyDigest = digest(JSON.stringify(options.recipes));
3978
+ this.#checkpoints = new CodeRuntimeCheckpointManager({
3979
+ control: options.control,
3980
+ recipes: options.recipes,
3981
+ recipeExecutor: options.recipeExecutor ?? createContainerRecipeExecutor(options.engine),
3982
+ fallbackPolicyDigest: this.#buildPolicyDigest,
3983
+ event: (command, event, refs) => this.#event(command, event, refs)
3984
+ });
3985
+ }
3986
+ options;
3987
+ #active = /* @__PURE__ */ new Map();
3988
+ #run;
3989
+ #buildPolicyDigest;
3990
+ #checkpoints;
3991
+ execute(command) {
3992
+ if (command.kind === "checkpoint_stop") return this.#checkpoint(command);
3993
+ if (command.kind === "prompt") return this.#prompt(command);
3994
+ return this.#start(command, command.kind === "resume");
3995
+ }
3996
+ async acknowledged(command, result) {
3997
+ if (await this.#checkpoints.acknowledged(command, result)) return;
3998
+ const active = this.#active.get(command.sessionId);
3999
+ if (!active || result.status !== "running") return;
4000
+ active.acknowledged = true;
4001
+ if (active.failure) await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
4002
+ }
4003
+ async close() {
4004
+ const sessions = [...this.#active.values()];
4005
+ for (const session of sessions) session.abort.abort("runtime_shutdown");
4006
+ await Promise.allSettled(sessions.map((session) => session.done));
4007
+ await Promise.allSettled(sessions.map((session) => session.workspace.cleanup()));
4008
+ this.#active.clear();
4009
+ }
4010
+ async #start(command, resume) {
4011
+ if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
4012
+ const source = await this.options.control.source(command.sessionId);
4013
+ const materialized = await materializeCodeRuntimeSource(source);
4014
+ let workspace;
4015
+ try {
4016
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4017
+ trustedBaseDir: materialized.sourceDir,
4018
+ trustedBaseCommitSha: source.commitSha,
4019
+ checkpoint: codeCheckpointPayload(command.payload)
4020
+ })).workspace : await stageWorkspace(materialized.sourceDir);
4021
+ } finally {
4022
+ await materialized.cleanup();
4023
+ }
4024
+ const metadata2 = codeCommandMetadata(command.payload, resume);
4025
+ const abort = new AbortController();
4026
+ const conversationRefs = [];
4027
+ const active = {
4028
+ workspace,
4029
+ abort,
4030
+ conversationRefs,
4031
+ acknowledged: false,
4032
+ role: metadata2.role,
4033
+ title: metadata2.title,
4034
+ baseCommitSha: source.commitSha,
4035
+ trustedBaseDigest: await digestStagedWorkspace(workspace.baselineDir, {
4036
+ maxFiles: 1e4,
4037
+ maxBytes: 16 * 1024 * 1024
4038
+ }),
4039
+ planningInputDigest: metadata2.planningInputDigest ?? digest(
4040
+ JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: source.treeDigest })
4041
+ ),
4042
+ done: Promise.resolve(null)
4043
+ };
4044
+ this.#active.set(command.sessionId, active);
4045
+ active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
4046
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` }, conversationRefs).catch(() => void 0);
4047
+ await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
4048
+ await this.#failure(command, active, message2(cause));
4049
+ return null;
4050
+ });
4051
+ return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
4052
+ }
4053
+ async #prompt(command) {
4054
+ const active = this.#active.get(command.sessionId);
4055
+ const prompt = command.payload.prompt;
4056
+ if (!active || typeof prompt !== "string" || !prompt.trim() || prompt.length > 2e4) {
4057
+ throw new TypeError("prompt requires an active Code session and bounded text");
4058
+ }
4059
+ await active.done;
4060
+ active.abort = new AbortController();
4061
+ active.acknowledged = false;
4062
+ active.failure = void 0;
4063
+ active.done = this.#runAttempt(command, {
4064
+ role: active.role,
4065
+ title: active.title,
4066
+ prompt,
4067
+ planningInputDigest: active.planningInputDigest,
4068
+ attestationDigest: "follow-up"
4069
+ }, active).catch(async (cause) => {
4070
+ await this.#event(
4071
+ command,
4072
+ { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` },
4073
+ active.conversationRefs
4074
+ ).catch(() => void 0);
4075
+ await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
4076
+ await this.#failure(command, active, message2(cause));
4077
+ return null;
4078
+ });
4079
+ return { status: "running", message: "Pi accepted the owner prompt" };
4080
+ }
4081
+ async #runAttempt(command, metadata2, active) {
4082
+ const lease = fakeCodeLease(command, metadata2);
4083
+ const broker = this.#toolBroker(lease, metadata2.role);
4084
+ const startedAt = Date.now();
4085
+ let completionSeen = false;
4086
+ const result = await this.#run({
4087
+ engine: this.options.engine,
4088
+ image: this.options.image,
4089
+ workspaceDir: active.workspace.workspaceDir,
4090
+ workspaceAccess: "none",
4091
+ task: lease.task,
4092
+ limits: this.options.limits,
4093
+ signal: active.abort.signal,
4094
+ onStderr: (text) => this.#event(command, {
4095
+ type: "message",
4096
+ actor: "system",
4097
+ body: text.slice(0, 4e3)
4098
+ }, active.conversationRefs),
4099
+ onMessage: async (output) => {
4100
+ if (output.type === "inference.request") {
4101
+ const inferenceStarted = Date.now();
4102
+ const response2 = await this.options.control.infer(command.sessionId, {
4103
+ requestId: output.requestId,
4104
+ call: output.call
4105
+ });
4106
+ await this.#event(command, {
4107
+ type: "usage",
4108
+ provider: response2.receipt.provider,
4109
+ model: response2.receipt.model,
4110
+ inputTokens: response2.receipt.inputTokens,
4111
+ outputTokens: response2.receipt.outputTokens,
4112
+ durationMs: Date.now() - inferenceStarted
4113
+ }, active.conversationRefs).catch(() => void 0);
4114
+ return {
4115
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
4116
+ type: "inference.response",
4117
+ requestId: output.requestId,
4118
+ response: response2.response
4119
+ };
4120
+ }
4121
+ if (output.type === "tool.request") {
4122
+ const toolStarted = Date.now();
4123
+ await this.#event(
4124
+ command,
4125
+ { type: "tool", phase: "started", tool: output.tool },
4126
+ active.conversationRefs
4127
+ ).catch(() => void 0);
4128
+ const response2 = await broker.execute({
4129
+ lease,
4130
+ workspaceDir: active.workspace.workspaceDir,
4131
+ signal: active.abort.signal
4132
+ }, output);
4133
+ await this.#event(command, {
4134
+ type: "tool",
4135
+ phase: "completed",
4136
+ tool: output.tool,
4137
+ ok: response2.ok,
4138
+ durationMs: Date.now() - toolStarted
4139
+ }, active.conversationRefs).catch(() => void 0);
4140
+ return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
4141
+ }
4142
+ if (output.type === "event") {
4143
+ const payload = object2(output.payload);
4144
+ if (output.kind === "pi.started") {
4145
+ await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
4146
+ } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
4147
+ await this.#event(command, {
4148
+ type: "thinking",
4149
+ available: true,
4150
+ durationMs: Math.min(Number(payload.durationMs), 864e5)
4151
+ }, active.conversationRefs);
4152
+ } else {
4153
+ await this.#event(command, {
4154
+ type: "message",
4155
+ actor: "system",
4156
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${json(output.payload)}`}`
4157
+ }, active.conversationRefs);
4158
+ }
4159
+ } else if (output.type === "attempt.complete") {
4160
+ completionSeen = true;
4161
+ const body = resultText(output.result) ?? `Pi ${output.status}.`;
4162
+ await this.#event(command, {
4163
+ type: "message",
4164
+ actor: output.status === "completed" ? "agent" : "system",
4165
+ body
4166
+ }, active.conversationRefs);
4167
+ await this.#event(command, {
4168
+ type: "status",
4169
+ status: output.status === "completed" ? "idle" : "failed",
4170
+ durationMs: Date.now() - startedAt
4171
+ }, active.conversationRefs);
4172
+ }
4173
+ }
4174
+ });
4175
+ if (result.status === "failed" && result.stderr) {
4176
+ await this.#event(command, { type: "message", actor: "system", body: result.stderr.slice(0, 4e3) }, active.conversationRefs);
4177
+ }
4178
+ if (!completionSeen) await this.#event(command, {
4179
+ type: "status",
4180
+ status: result.status === "completed" ? "idle" : "failed",
4181
+ durationMs: Date.now() - startedAt
4182
+ }, active.conversationRefs).catch(() => void 0);
4183
+ if (result.status === "failed") {
4184
+ await this.#failure(command, active, result.stderr || "Pi container failed");
4185
+ }
4186
+ return result;
4187
+ }
4188
+ #toolBroker(lease, role) {
4189
+ const broker = createCodeToolBroker({
4190
+ recipes: this.options.recipes,
4191
+ recipeExecutor: createContainerRecipeExecutor(this.options.engine),
4192
+ recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
4193
+ readerId: `code-session:${lease.task.taskId}`
4194
+ });
4195
+ return role === "coding" ? broker : { execute: (context, request) => request.tool === "sandbox.read" ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
4196
+ }
4197
+ async #checkpoint(command) {
4198
+ const active = this.#active.get(command.sessionId);
4199
+ if (!active) throw new TypeError("Code session workspace is not active on this runtime");
4200
+ const result = await this.#checkpoints.prepare(command, active);
4201
+ this.#active.delete(command.sessionId);
4202
+ return result;
4203
+ }
4204
+ async #failure(command, active, value) {
4205
+ active.failure = value.slice(0, 2e3);
4206
+ if (active.acknowledged) {
4207
+ await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
4208
+ }
4209
+ }
4210
+ async #event(command, event, refs) {
4211
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4212
+ refs.push(eventId);
4213
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4214
+ await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
4215
+ }
4216
+ };
4217
+ var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4218
+ var digest = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
4219
+ var message2 = (value) => value instanceof Error ? value.message : String(value);
4220
+ var json = (value) => {
4221
+ try {
4222
+ return JSON.stringify(value).slice(0, 1e4);
4223
+ } catch {
4224
+ return "[event]";
4225
+ }
4226
+ };
4227
+ function resultText(value) {
4228
+ const record32 = object2(value);
4229
+ return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4230
+ }
4231
+
4232
+ // src/help.ts
4233
+ import { readFileSync as readFileSync3 } from "fs";
4234
+ function cliVersion() {
4235
+ const pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
4236
+ return pkg.version ?? "unknown";
4237
+ }
4238
+ function printHelp() {
4239
+ console.log(`odla-ai
4240
+
4241
+ Usage:
4242
+ odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
4243
+ odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
4244
+ odla-ai doctor [--config odla.config.mjs]
4245
+ odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
4246
+ odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
4247
+ odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
4248
+ odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
4249
+ odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
4250
+ odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
4251
+ odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
4252
+ odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
4253
+ odla-ai app owners add <email> [--email <odla-account>] [--json]
4254
+ odla-ai app owners remove <email> [--email <odla-account>] [--json]
4255
+ odla-ai capabilities [--json]
4256
+ odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
4257
+ odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
4258
+ odla-ai admin ai models [--provider <id>] [--json]
4259
+ odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
4260
+ odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
4261
+ --validation-provider <id> --validation-model <id> [--enabled|--no-enabled]
4262
+ odla-ai admin ai credentials [--json]
4263
+ odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
4264
+ odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
4265
+ odla-ai admin ai audit [--limit <1-200>] [--json]
4266
+ odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
4267
+ odla-ai security github disconnect --source <id> [--env dev] [--yes]
4268
+ odla-ai security plan [--env dev] [--json]
4269
+ odla-ai security sources [--env dev] [--json]
4270
+ odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
4271
+ odla-ai security status <job-id> [--follow] [--json]
4272
+ odla-ai security report <job-id> [--json]
4273
+ odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
4274
+ odla-ai security run [target] --self --ack-redacted-source
4275
+ odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
4276
+ odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
4277
+ odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
4278
+ odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
4279
+ odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
4280
+ odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
4281
+ odla-ai version
4282
+
4283
+ Commands:
4284
+ setup Install offline odla runbooks for common coding-agent harnesses.
4285
+ init Create a generic odla.config.mjs plus starter schema/rules files.
4286
+ doctor Validate and summarize the project config without network calls.
4287
+ calendar Inspect, connect, or disconnect the live Google booking connection.
4288
+ app Archive (suspend, data retained), restore, export, or manage the
4289
+ co-owners of the app. Archiving takes every environment's data
4290
+ plane down until restored; permanent deletion has NO CLI \u2014 it
4291
+ requires a signed-in owner in Studio, and no machine or agent
4292
+ credential can purge. "app export" downloads a portable
4293
+ gzipped-JSONL snapshot of one environment's database (--fresh
4294
+ takes a new snapshot first) \u2014 your data is always yours to take.
4295
+ "app owners add <email>" grants a signed-up odla member the SAME
4296
+ full access as you; they then run their own "provision" to mint
4297
+ their own credentials for the shared db (prod included) \u2014 no
4298
+ secret is ever copied between people.
4299
+ capabilities Show what the CLI automates vs agent edits and human checkpoints.
4300
+ code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
4301
+ admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
4302
+ security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
4303
+ provision Register services, compose integrations, persist credentials, optionally push secrets.
4304
+ smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
4305
+ skill Same installer; --agent accepts all, claude, codex, cursor,
4306
+ copilot, gemini, or agents (repeatable or comma-separated).
4307
+ secrets Push configured db/o11y secrets into the Worker via wrangler
4308
+ stdin; set stores a tenant-vault secret and set-clerk-key the
4309
+ reserved Clerk secret key, write-only from stdin or an env var.
4310
+ version Print the CLI version.
4311
+
4312
+ Safety:
4313
+ New projects target dev only. Add prod explicitly to odla.config.mjs and pass
4314
+ --yes to provision it; use --dry-run first to inspect the resolved plan.
4315
+ Provision caches the approved developer token and service credentials under
4316
+ .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
4317
+ preflights Wrangler before any shown-once issuance or destructive rotation.
4318
+ Provision opens the approval page in your browser automatically whenever the
4319
+ machine can show one, including agent-driven runs; only CI, SSH, and
4320
+ display-less hosts skip it. Use --open to force or --no-open to suppress.
4321
+ Browser launch is best-effort: the printed approval URL is authoritative and
4322
+ agents must relay it to the human verbatim. A started handshake is persisted
4323
+ under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
4324
+ the same code. Outside an interactive terminal the wait is capped (90s by
4325
+ default, --wait <seconds> to change); a still-pending handshake then exits
4326
+ with code 75: relay the URL, wait for approval, and re-run to collect.
4327
+ A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
4328
+ The email is a non-secret identity hint: never provide a password or session
4329
+ token. The matching account must already exist, be signed in, explicitly
4330
+ review the exact code, and finish any current request before claiming another.
4331
+ Run Code from a GitHub checkout already connected to an app in Studio; an
4332
+ odla.config.mjs may select the app explicitly but is not required. Code host
4333
+ approval and credential hashes live in odla-ai/db. The host
4334
+ credential is never written under .odla/; it exists only in the foreground
4335
+ "code connect" process and is rotated by the next approved connection.
4336
+ Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
4337
+ OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
4338
+ GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
4339
+ for a PAT or provider key. GitHub read access is separate from the explicit
4340
+ --ack-redacted-source consent and the exact --plan-digest printed by security
4341
+ plan are required before bounded snippets reach System AI.
4342
+ Run security plan first to inspect the admin-selected providers, models,
4343
+ per-route bounds, credential readiness, retention, no-execution boundary,
4344
+ and digest that binds consent to that exact plan.
4345
+ `);
4346
+ }
4347
+
4348
+ // src/code-connect.ts
4349
+ var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:713db58428bc05cb486167c5747de8fd826669c9b283ff78e0139ac194c3d058";
4350
+ var CODE_BUILD_RECIPES = Object.freeze([{
4351
+ id: "node-test",
4352
+ image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
4353
+ command: ["node", "--test"],
4354
+ timeoutMs: 12e4,
4355
+ maxOutputBytes: 1024 * 1024,
4356
+ cpus: 1,
4357
+ memory: "512m",
4358
+ pids: 128
4359
+ }]);
4360
+ async function codeConnect(options) {
4361
+ const cwd = options.cwd ?? process.cwd();
4362
+ const configPath = resolve5(cwd, options.configPath);
4363
+ const cfg = existsSync4(configPath) ? await loadProjectConfig(configPath) : null;
4364
+ const repository = cfg ? null : await inferGitHubRepository(cwd, options.readGitOrigin);
4365
+ const platform = cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai";
4366
+ const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
4367
+ if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
4368
+ throw new Error(cfg ? `Code env "${appEnv ?? ""}" must be dev or prod and declared in ${options.configPath}` : `Code env "${appEnv ?? ""}" must be dev or prod`);
4369
+ }
4370
+ if (process.platform !== "darwin" && process.platform !== "linux") {
4371
+ throw new Error("odla Code hosts require macOS or Linux");
4372
+ }
4373
+ const slots = options.slots ?? 1;
4374
+ if (!Number.isSafeInteger(slots) || slots < 1 || slots > 64) {
4375
+ throw new Error("--slots must be an integer from 1 to 64");
4376
+ }
4377
+ const heartbeatMs = options.heartbeatMs ?? 15e3;
4378
+ if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3 || heartbeatMs > 3e5) {
4379
+ throw new Error("--heartbeat-ms must be an integer from 1000 to 300000");
4380
+ }
4381
+ const out = options.stdout ?? console;
4382
+ const doFetch = options.fetch ?? fetch;
4383
+ const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
4384
+ const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
4385
+ const hostName = (options.name ?? hostname()).trim();
4386
+ if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
4387
+ const approval = await (options.getToken ?? getScopedPlatformToken)({
4388
+ platform,
4389
+ scope: "platform:code:host:connect",
4390
+ email: options.email,
4391
+ open: options.open,
4392
+ fetch: doFetch,
4393
+ stdout: out,
4394
+ openApprovalUrl: options.openApprovalUrl,
4395
+ cache: false,
4396
+ label: `odla CLI Code host for ${cfg?.app.id ?? repository}/${appEnv}`
4397
+ });
4398
+ const target = cfg ? { appId: cfg.app.id } : { repository };
4399
+ const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4400
+ method: "POST",
4401
+ headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4402
+ body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4403
+ redirect: "error",
4404
+ signal: options.signal
4405
+ });
4406
+ const raw = await response2.json().catch(() => null);
4407
+ if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4408
+ const connection = parseConnection(raw, cfg?.app.id, appEnv);
4409
+ const capabilities = {
4410
+ protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4411
+ platform: hostPlatform,
4412
+ arch: process.arch,
4413
+ engines: [engine],
4414
+ cpuCount: cpus().length,
4415
+ memoryBytes: totalmem()
4416
+ };
4417
+ out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
4418
+ out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
4419
+ out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
4420
+ await (options.runRuntime ?? runCodeRuntime)({
4421
+ endpoint: platform,
4422
+ token: connection.token,
4423
+ engine,
4424
+ capabilities,
4425
+ heartbeatMs,
4426
+ once: options.once === true,
4427
+ signal: options.signal,
4428
+ stdout: out,
4429
+ fetch: doFetch
4430
+ });
4431
+ }
4432
+ async function runCodeRuntime(input) {
4433
+ const controller = new AbortController();
4434
+ const abort = () => controller.abort(input.signal?.reason ?? "runtime_shutdown");
4435
+ input.signal?.addEventListener("abort", abort, { once: true });
4436
+ const signal = input.signal ? AbortSignal.any([input.signal, controller.signal]) : controller.signal;
4437
+ const handlers = ["SIGINT", "SIGTERM"].map((name) => {
4438
+ const handler = () => controller.abort(name);
4439
+ process.once(name, handler);
4440
+ return [name, handler];
4441
+ });
4442
+ const control = createCodeRuntimeControlClient({
4443
+ endpoint: input.endpoint,
4444
+ token: input.token,
4445
+ signal,
4446
+ fetch: input.fetch
4447
+ });
4448
+ const commandEngine = new CodePiRuntimeEngine({
4449
+ control,
4450
+ engine: input.engine,
4451
+ image: CODE_PI_IMAGE,
4452
+ recipes: CODE_BUILD_RECIPES,
4453
+ recipeAuthorization: "registered_recipe"
4454
+ });
4455
+ const reconciler = new CodeRuntimeReconciler(control, commandEngine);
4456
+ try {
4457
+ await runCodeRuntimeHeartbeatLoop({
4458
+ control,
4459
+ runtimeVersion: `odla-ai-cli/${cliVersion()}`,
4460
+ capabilities: input.capabilities,
4461
+ heartbeatMs: input.heartbeatMs,
4462
+ once: input.once,
4463
+ signal,
4464
+ onSnapshot: async (snapshot) => {
4465
+ input.stdout.log(
4466
+ `online: ${snapshot.host.hostId} \xB7 ${snapshot.bindings.length} binding(s) \xB7 ${snapshot.commands.length} command(s)`
4467
+ );
4468
+ if (input.once && snapshot.commands.length) {
4469
+ throw new Error("--once is diagnostic-only and refuses pending Code commands; run without --once");
4470
+ }
4471
+ await reconciler.reconcile(snapshot);
4472
+ },
4473
+ onRetry: (error, delayMs) => input.stdout.error(
4474
+ `Code control plane unavailable; retrying in ${delayMs}ms \xB7 ${error instanceof Error ? error.message : String(error)}`
4475
+ )
4476
+ });
4477
+ } finally {
4478
+ await commandEngine.close();
4479
+ input.signal?.removeEventListener("abort", abort);
4480
+ for (const [name, handler] of handlers) process.removeListener(name, handler);
4481
+ }
4482
+ }
4483
+ function parseConnection(value, appId, appEnv) {
4484
+ const root = record4(value);
4485
+ const host = record4(root?.host);
4486
+ const offer = record4(root?.offer);
4487
+ const binding = record4(root?.binding);
4488
+ if (!root || typeof root.token !== "string" || !/^odla_code_host_[0-9a-f]{64}$/.test(root.token) || typeof root.resumed !== "boolean" || !host || !/^chost_[0-9a-f]{32}$/.test(String(host.hostId)) || typeof host.name !== "string" || !offer || !Number.isSafeInteger(offer.slots) || !binding || typeof binding.appId !== "string" || !binding.appId || appId && binding.appId !== appId || binding.env !== appEnv || !Number.isSafeInteger(binding.generation)) {
4489
+ throw new Error("connect Code host returned an invalid response");
4490
+ }
4491
+ return root;
4492
+ }
4493
+ function apiFailure(action, status, value) {
4494
+ const message3 = record4(record4(value)?.error)?.message;
4495
+ return `${action} failed (${status})${typeof message3 === "string" ? `: ${message3}` : ""}`;
4496
+ }
4497
+ function record4(value) {
4498
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4499
+ }
4500
+
4501
+ // src/doctor-checks.ts
4502
+ import { execFileSync } from "child_process";
4503
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
4504
+ import { join as join5, resolve as resolve6 } from "path";
4505
+
4506
+ // src/wrangler.ts
4507
+ import { spawn as spawn4 } from "child_process";
4508
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
4509
+ import { join as join4 } from "path";
4510
+ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4511
+ const child = spawn4(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4512
+ let stdout = "";
4513
+ let stderr = "";
4514
+ child.stdout.on("data", (chunk) => stdout += chunk.toString());
4515
+ child.stderr.on("data", (chunk) => stderr += chunk.toString());
4516
+ child.on("error", reject);
4517
+ child.on("close", (code) => resolvePromise({ code: code ?? 1, stdout, stderr }));
4518
+ child.stdin.end(opts?.input ?? "");
4519
+ });
4520
+ var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
4521
+ function findWranglerConfig(rootDir) {
4522
+ for (const name of WRANGLER_CONFIG_FILES) {
4523
+ const path = join4(rootDir, name);
4524
+ if (existsSync5(path)) return path;
4525
+ }
4526
+ return null;
4527
+ }
4528
+ function readWranglerConfig(path) {
4529
+ if (path.endsWith(".toml")) return null;
4530
+ try {
4531
+ return JSON.parse(stripJsonComments(readFileSync4(path, "utf8")));
4532
+ } catch {
4533
+ return null;
4534
+ }
4535
+ }
4536
+ function stripJsonComments(text) {
4537
+ let result = "";
4538
+ let inString = false;
4539
+ for (let i = 0; i < text.length; i++) {
4540
+ const ch = text[i];
4541
+ if (inString) {
4542
+ result += ch;
4543
+ if (ch === "\\") {
4544
+ result += text[i + 1] ?? "";
4545
+ i++;
4546
+ } else if (ch === '"') {
4547
+ inString = false;
4548
+ }
4549
+ continue;
4550
+ }
4551
+ if (ch === '"') {
4552
+ inString = true;
4553
+ result += ch;
4554
+ continue;
4555
+ }
4556
+ if (ch === "/" && text[i + 1] === "/") {
4557
+ while (i < text.length && text[i] !== "\n") i++;
4558
+ result += "\n";
4559
+ continue;
4560
+ }
4561
+ if (ch === "/" && text[i + 1] === "*") {
4562
+ i += 2;
4563
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
4564
+ i++;
4565
+ continue;
4566
+ }
4567
+ result += ch;
4568
+ }
4569
+ return result;
4570
+ }
4571
+ async function wranglerLoggedIn(run, cwd) {
4572
+ try {
4573
+ const result = await run("npx", ["wrangler", "whoami"], { cwd });
4574
+ return result.code === 0 && !/not authenticated/i.test(`${result.stdout}${result.stderr}`);
4575
+ } catch {
4576
+ return false;
4577
+ }
4578
+ }
4579
+ function wranglerPutSecret(run, opts) {
4580
+ const args = ["wrangler", "secret", "put", opts.name, ...opts.env ? ["--env", opts.env] : []];
4581
+ return run("npx", args, { input: opts.value, cwd: opts.cwd });
4582
+ }
4583
+
4584
+ // src/doctor-checks.ts
4585
+ function lintRules(rules, entities, publicRead) {
4586
+ const warnings = [];
4587
+ if (!rules) return warnings;
4588
+ for (const [ns, actions] of Object.entries(rules)) {
4589
+ for (const [action, expr] of Object.entries(actions)) {
4590
+ if (typeof expr !== "string" || expr.trim() !== "true") continue;
4591
+ if (action === "view" && publicRead.includes(ns)) continue;
4592
+ warnings.push(
4593
+ action === "view" ? `rules.${ns}.view is "true" \u2014 public read; add "${ns}" to db.publicRead if intended` : `rules.${ns}.${action} is "true" \u2014 any client can ${action} ${ns} rows`
4594
+ );
4595
+ }
4596
+ }
4597
+ for (const entity of entities) {
4598
+ if (!(entity in rules)) warnings.push(`schema entity "${entity}" has no rules entry (all client access denied)`);
4599
+ }
4600
+ return warnings;
4601
+ }
4602
+ var defaultExec = (cmd, args, cwd) => execFileSync(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
4603
+ function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
4604
+ let output;
4605
+ try {
4606
+ const custom = localPaths.map((path) => gitignoreEntry(rootDir, path)).filter((path) => !!path);
4607
+ output = exec("git", ["ls-files", "--", ".dev.vars", ".odla", ...custom], rootDir);
4608
+ } catch {
4609
+ return [];
4610
+ }
4611
+ return output.split(/\r?\n/).filter(Boolean).map((path) => `${path} is tracked by git \u2014 run "git rm --cached ${path}" and commit; it belongs in .gitignore`);
4612
+ }
4613
+ function wranglerWarnings(rootDir) {
4614
+ const configPath = findWranglerConfig(rootDir);
4615
+ if (!configPath) return [];
4616
+ const config = readWranglerConfig(configPath);
4617
+ if (!config) return [];
4618
+ const warnings = [];
4619
+ const blocks = [{ label: "", block: config }];
4620
+ const envs = config.env;
4621
+ if (envs && typeof envs === "object") {
4622
+ for (const [name, block] of Object.entries(envs)) {
4623
+ if (block && typeof block === "object") blocks.push({ label: `env.${name}.`, block });
4624
+ }
4625
+ }
4626
+ for (const { label, block } of blocks) {
4627
+ const assets = block.assets;
4628
+ if (assets?.directory) {
4629
+ const dir = resolve6(rootDir, assets.directory);
4630
+ if (dir === resolve6(rootDir)) {
4631
+ warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
4632
+ } else if (existsSync6(join5(dir, "node_modules"))) {
4633
+ warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
4634
+ }
4635
+ }
4636
+ const vars = block.vars;
4637
+ if (vars && typeof vars === "object") {
4638
+ for (const [name, value] of Object.entries(vars)) {
4639
+ if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value === "string" && looksSecret(value)) {
4640
+ warnings.push(`wrangler ${label}vars.${name} looks like a secret \u2014 use "odla-ai secrets push" / wrangler secret put, never vars`);
4641
+ }
4642
+ }
4643
+ }
4644
+ }
4645
+ const pkg = readPackageJson(rootDir);
4646
+ if (pkg && typeof pkg.scripts === "object" && pkg.scripts && "deploy" in pkg.scripts) {
4647
+ warnings.push(`package.json has a script named "deploy" \u2014 CI may auto-deploy it; rename to deploy:app`);
4648
+ }
4649
+ return warnings;
4650
+ }
4651
+ function o11yProjectWarnings(rootDir) {
4652
+ const warnings = [];
4653
+ const pkg = readPackageJson(rootDir);
4654
+ const dependencies = pkg?.dependencies;
4655
+ const devDependencies = pkg?.devDependencies;
4656
+ if (!dependencies?.["@odla-ai/o11y"]) {
4657
+ warnings.push(
4658
+ devDependencies?.["@odla-ai/o11y"] ? "@odla-ai/o11y is a devDependency \u2014 move it to dependencies so the Worker can import it" : 'Worker instrumentation is missing @odla-ai/o11y \u2014 install it and wrap the handler with "withObservability"'
4659
+ );
4660
+ }
4661
+ const configPath = findWranglerConfig(rootDir);
4662
+ const config = configPath ? readWranglerConfig(configPath) : null;
4663
+ if (!configPath || !config) {
4664
+ warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
4665
+ return warnings;
4666
+ }
4667
+ const main = typeof config.main === "string" ? resolve6(rootDir, config.main) : null;
4668
+ if (!main || !existsSync6(main)) {
4669
+ warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
4670
+ } else {
4671
+ let source = "";
4672
+ try {
4673
+ source = readFileSync5(main, "utf8");
4674
+ } catch {
4675
+ }
4676
+ if (!/\bwithObservability\b/.test(source)) {
4677
+ warnings.push(`wrangler entrypoint ${config.main} does not reference withObservability`);
4678
+ }
4679
+ }
4680
+ const flags = Array.isArray(config.compatibility_flags) ? config.compatibility_flags : [];
4681
+ if (!flags.includes("nodejs_compat")) {
4682
+ warnings.push('wrangler compatibility_flags is missing "nodejs_compat" required by @odla-ai/o11y');
4683
+ }
4684
+ return warnings;
4685
+ }
4686
+ function calendarProjectWarnings(rootDir) {
4687
+ const pkg = readPackageJson(rootDir);
4688
+ const dependencies = pkg?.dependencies;
4689
+ const devDependencies = pkg?.devDependencies;
4690
+ if (dependencies?.["@odla-ai/calendar"]) return [];
4691
+ return [
4692
+ devDependencies?.["@odla-ai/calendar"] ? "@odla-ai/calendar is a devDependency \u2014 move it to dependencies so trusted Worker code can import it" : "calendar service is enabled but @odla-ai/calendar is not installed in dependencies"
4693
+ ];
4694
+ }
4695
+ function readPackageJson(rootDir) {
4696
+ try {
4697
+ return JSON.parse(readFileSync5(join5(rootDir, "package.json"), "utf8"));
4698
+ } catch {
4699
+ return null;
4700
+ }
4701
+ }
4702
+
4703
+ // src/integrations.ts
4704
+ import { isDeepStrictEqual } from "util";
4705
+ async function resolveDatabaseConfig(cfg, options = {}) {
4706
+ const integrations = cfg.integrations ?? [];
4707
+ if (!cfg.services.includes("db")) return { schema: void 0, rules: void 0, integrations };
4708
+ const authoredSchema = await resolveDataExport(cfg, cfg.db?.schema, ["schema", "SCHEMA"]);
4709
+ const schema = mergeSchemas(authoredSchema, integrations);
4710
+ if (options.validateSeeds !== false) assertSeedContracts(integrations, schema);
4711
+ const configuredRules = await resolveDataExport(cfg, cfg.db?.rules, ["rules", "RULES"]);
4712
+ const explicitRules = mergeRules(configuredRules, integrations);
4713
+ const rules = configuredRules === void 0 && schema && cfg.db?.defaultRules !== false ? { ...rulesFromSchema(schema), ...explicitRules ?? {} } : explicitRules;
4714
+ return { schema, rules, integrations };
4715
+ }
4716
+ function integrationWarnings(integrations, schema, rules) {
4717
+ const warnings = [];
4718
+ const entities = new Set(serializedEntities(schema));
1775
4719
  for (const integration of integrations) {
1776
4720
  const namespaces = Object.keys(integration.schema?.entities ?? {});
1777
4721
  for (const ns of namespaces) {
@@ -1932,13 +4876,13 @@ async function doctor(options) {
1932
4876
  }
1933
4877
 
1934
4878
  // src/init.ts
1935
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
1936
- import { dirname as dirname4, resolve as resolve4 } from "path";
4879
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
4880
+ import { dirname as dirname5, resolve as resolve7 } from "path";
1937
4881
  function initProject(options) {
1938
4882
  const out = options.stdout ?? console;
1939
- const rootDir = resolve4(options.rootDir ?? process.cwd());
1940
- const configPath = resolve4(rootDir, options.configPath ?? "odla.config.mjs");
1941
- if (existsSync6(configPath) && !options.force) {
4883
+ const rootDir = resolve7(options.rootDir ?? process.cwd());
4884
+ const configPath = resolve7(rootDir, options.configPath ?? "odla.config.mjs");
4885
+ if (existsSync7(configPath) && !options.force) {
1942
4886
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
1943
4887
  }
1944
4888
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -1948,19 +4892,19 @@ function initProject(options) {
1948
4892
  const services = options.services?.length ? options.services : ["db", "ai"];
1949
4893
  if (services.includes("calendar") && !services.includes("db")) throw new Error("--services calendar requires db");
1950
4894
  const aiProvider = options.aiProvider ?? "anthropic";
1951
- mkdirSync2(dirname4(configPath), { recursive: true });
1952
- mkdirSync2(resolve4(rootDir, "src/odla"), { recursive: true });
1953
- mkdirSync2(resolve4(rootDir, ".odla"), { recursive: true });
4895
+ mkdirSync2(dirname5(configPath), { recursive: true });
4896
+ mkdirSync2(resolve7(rootDir, "src/odla"), { recursive: true });
4897
+ mkdirSync2(resolve7(rootDir, ".odla"), { recursive: true });
1954
4898
  writeFileSync2(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
1955
- writeIfMissing(resolve4(rootDir, "src/odla/schema.mjs"), schemaTemplate());
1956
- writeIfMissing(resolve4(rootDir, "src/odla/rules.mjs"), rulesTemplate());
4899
+ writeIfMissing(resolve7(rootDir, "src/odla/schema.mjs"), schemaTemplate());
4900
+ writeIfMissing(resolve7(rootDir, "src/odla/rules.mjs"), rulesTemplate());
1957
4901
  ensureGitignore(rootDir);
1958
4902
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
1959
4903
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
1960
4904
  out.log("updated .gitignore for local odla credentials");
1961
4905
  }
1962
4906
  function writeIfMissing(path, text) {
1963
- if (existsSync6(path)) return;
4907
+ if (existsSync7(path)) return;
1964
4908
  writeFileSync2(path, text);
1965
4909
  }
1966
4910
  function configTemplate(input) {
@@ -2432,10 +5376,10 @@ async function provision(options) {
2432
5376
  stdout: out
2433
5377
  });
2434
5378
  } catch (error) {
2435
- const message = error instanceof Error ? error.message : String(error);
5379
+ const message3 = error instanceof Error ? error.message : String(error);
2436
5380
  const consent = env === "prod" || env === "production" ? " --yes" : "";
2437
5381
  throw new Error(
2438
- `${message}
5382
+ `${message3}
2439
5383
  ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
2440
5384
  { cause: error }
2441
5385
  );
@@ -2498,8 +5442,8 @@ async function readRegistryApp(cfg, token, doFetch) {
2498
5442
  });
2499
5443
  if (res.status === 404) return null;
2500
5444
  if (!res.ok) return null;
2501
- const json = await res.json();
2502
- return json.app ?? null;
5445
+ const json2 = await res.json();
5446
+ return json2.app ?? null;
2503
5447
  }
2504
5448
  async function postJson2(doFetch, url, bearer, body) {
2505
5449
  const res = await doFetch(url, {
@@ -2586,7 +5530,7 @@ async function resolveVaultWrite(options) {
2586
5530
  }
2587
5531
 
2588
5532
  // src/security.ts
2589
- import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve5, sep } from "path";
5533
+ import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve8, sep as sep3 } from "path";
2590
5534
  import {
2591
5535
  cloudflareAppProfile,
2592
5536
  createPlatformSecurityReasoners,
@@ -2605,9 +5549,9 @@ async function runHostedSecurity(options) {
2605
5549
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
2606
5550
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
2607
5551
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
2608
- const target = resolve5(options.target ?? cfg?.rootDir ?? ".");
2609
- const output = resolve5(options.out ?? resolve5(target, ".odla/security/hosted"));
2610
- const outputRelative = relative2(target, output).split(sep).join("/");
5552
+ const target = resolve8(options.target ?? cfg?.rootDir ?? ".");
5553
+ const output = resolve8(options.out ?? resolve8(target, ".odla/security/hosted"));
5554
+ const outputRelative = relative4(target, output).split(sep3).join("/");
2611
5555
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
2612
5556
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
2613
5557
  const tokenRequest = {
@@ -2634,280 +5578,66 @@ async function runHostedSecurity(options) {
2634
5578
  ...selfAudit ? { selfAudit: { enabled: true, subject: "service:odla-security-self-audit" } } : {},
2635
5579
  fetch: options.fetch,
2636
5580
  signal: options.signal
2637
- });
2638
- const harness = createSecurityHarness({
2639
- profile,
2640
- store: new FileRunStore(resolve5(output, "state")),
2641
- discoveryReasoner: hosted.discoveryReasoner,
2642
- validationReasoner: hosted.validationReasoner,
2643
- policy: {
2644
- modelSourceDisclosure: "redacted",
2645
- active: false,
2646
- allowNetwork: false
2647
- }
2648
- });
2649
- const report = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
2650
- await writeSecurityArtifacts(output, report);
2651
- const reportDigest = await securityFingerprint(report);
2652
- await hosted.complete({
2653
- reportDigest,
2654
- coverageStatus: report.coverageStatus,
2655
- confirmed: report.metrics.confirmed,
2656
- candidates: report.metrics.candidates
2657
- }, { signal: options.signal });
2658
- printSummary(options.stdout ?? console, appId, env, hosted.run, report, output);
2659
- return Object.freeze({ report, run: hosted.run, output });
2660
- }
2661
- function selectEnv(requested, declared, configPath, rootDir) {
2662
- const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
2663
- if (!env || !declared.includes(env)) {
2664
- const shown = relative2(rootDir, configPath) || configPath;
2665
- throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
2666
- }
2667
- return env;
2668
- }
2669
- async function injectedToken(options, request) {
2670
- const value = options.token ?? await options.getToken?.(Object.freeze({ ...request }));
2671
- if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
2672
- throw new Error(
2673
- request.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
2674
- );
2675
- }
2676
- return value;
2677
- }
2678
- function profileFor(name, maxHuntTasks) {
2679
- const profile = name === "odla" ? odlaProfile() : name === "cloudflare-app" ? cloudflareAppProfile() : name === "generic" ? genericProfile() : void 0;
2680
- if (!profile) throw new Error(`unknown security profile "${String(name)}"`);
2681
- if (maxHuntTasks === void 0) return profile;
2682
- if (!Number.isSafeInteger(maxHuntTasks) || maxHuntTasks < 1) throw new Error("maxHuntTasks must be a positive integer");
2683
- return { ...profile, maxHuntTasks };
2684
- }
2685
- function printSummary(out, appId, env, run, report, output) {
2686
- const complete = report.coverage.filter((cell) => cell.state === "complete").length;
2687
- out.log(`security: ${appId}/${env} run=${run.runId} profile=${run.profileVersion}`);
2688
- out.log(` discovery: ${run.discovery.identity.provider}/${run.discovery.identity.model}`);
2689
- out.log(` validation: ${run.validation.identity.provider}/${run.validation.identity.model}`);
2690
- out.log(` coverage: ${report.coverageStatus} ${complete}/${report.coverage.length} blocked=${report.metrics.blockedCells} shallow=${report.metrics.shallowCells} unscheduled=${report.metrics.unscheduledCells} budget_exhausted=${report.metrics.budgetExhaustedCells}`);
2691
- if (report.callBudget) out.log(` calls: discovery=${formatBudget(report.callBudget.discovery)} validation=${formatBudget(report.callBudget.validation)}`);
2692
- out.log(` findings: confirmed=${report.metrics.confirmed} needs_reproduction=${report.metrics.needsReproduction} candidates=${report.metrics.candidates}`);
2693
- out.log(` report: ${resolve5(output, "REPORT.md")}`);
2694
- }
2695
- function formatBudget(usage) {
2696
- return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
2697
- }
2698
-
2699
- // src/security-hosted-github.ts
2700
- import { execFile } from "child_process";
2701
- import { promisify } from "util";
2702
-
2703
- // src/security-hosted-request.ts
2704
- async function requestHostedSecurityJson(options, path, init, action) {
2705
- const platform = platformAudience(options.platform);
2706
- const token = hostedSecurityCredential(options.token);
2707
- const requestInit = {
2708
- ...init,
2709
- redirect: "error",
2710
- credentials: "omit",
2711
- cache: "no-store",
2712
- signal: options.signal,
2713
- headers: {
2714
- authorization: `Bearer ${token}`,
2715
- "content-type": "application/json",
2716
- ...init.headers
2717
- }
2718
- };
2719
- const response = await (options.fetch ?? fetch)(new URL(path, platform), requestInit);
2720
- const body = await response.json().catch(() => ({}));
2721
- if (!response.ok) {
2722
- const code = optionalHostedText(body.error?.code, "error code", 128);
2723
- const message = optionalHostedText(body.error?.message, "error message", 300);
2724
- throw new Error(`${action} failed (${response.status})${code ? ` ${code}` : ""}${message ? `: ${message}` : ""}`);
2725
- }
2726
- return body;
2727
- }
2728
- function hostedIdentifier(value, name) {
2729
- const result = optionalHostedText(value, name, 180);
2730
- if (!result || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(result)) {
2731
- throw new Error(`${name} is invalid`);
2732
- }
2733
- return result;
2734
- }
2735
- function positivePolicyVersion(value, name) {
2736
- if (!Number.isSafeInteger(value) || value < 1) {
2737
- throw new Error(`${name} is invalid`);
2738
- }
2739
- return value;
2740
- }
2741
- function optionalHostedText(value, name, maxLength) {
2742
- if (value === void 0 || value === null || value === "") return void 0;
2743
- if (typeof value !== "string" || value.length > maxLength || /[\u0000-\u001f\u007f]/.test(value)) {
2744
- throw new Error(`${name} is invalid`);
2745
- }
2746
- return value;
2747
- }
2748
- function githubRepositoryName(value) {
2749
- if (typeof value !== "string" || value.length > 201) {
2750
- throw new Error("repository must be owner/name");
2751
- }
2752
- const parts = value.split("/");
2753
- const owner = parts[0] ?? "";
2754
- const name = (parts[1] ?? "").replace(/\.git$/i, "");
2755
- if (parts.length !== 2 || !/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,98}[A-Za-z0-9])?$/.test(owner) || !/^[A-Za-z0-9_.-]{1,100}$/.test(name) || name === "." || name === "..") {
2756
- throw new Error("repository must be owner/name");
2757
- }
2758
- return `${owner}/${name}`;
2759
- }
2760
- function trustedGitHubInstallUrl(value) {
2761
- let url;
2762
- try {
2763
- url = new URL(value);
2764
- } catch {
2765
- throw new Error("odla.ai returned an invalid GitHub installation URL");
2766
- }
2767
- if (url.protocol !== "https:" || url.hostname !== "github.com" || url.username || url.password) {
2768
- throw new Error("odla.ai returned an untrusted GitHub installation URL");
2769
- }
2770
- return url.toString();
2771
- }
2772
- function hostedPollInterval(value = 2e3) {
2773
- if (!Number.isSafeInteger(value) || value < 100 || value > 3e4) {
2774
- throw new Error("poll interval must be 100-30000ms");
2775
- }
2776
- return value;
2777
- }
2778
- function hostedPollTimeout(value = 10 * 6e4) {
2779
- if (!Number.isSafeInteger(value) || value < 1e3 || value > 60 * 6e4) {
2780
- throw new Error("poll timeout must be 1000-3600000ms");
2781
- }
2782
- return value;
2783
- }
2784
- async function waitForHostedPoll(milliseconds, signal) {
2785
- if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
2786
- await new Promise((resolve7, reject) => {
2787
- const timer = setTimeout(resolve7, milliseconds);
2788
- signal?.addEventListener("abort", () => {
2789
- clearTimeout(timer);
2790
- reject(signal.reason ?? new DOMException("aborted", "AbortError"));
2791
- }, { once: true });
2792
- });
2793
- }
2794
- function isValidHostedSecurityPlan(value, env) {
2795
- if (!value || value.env !== env || !/^sha256:[a-f0-9]{64}$/.test(value.planDigest) || value.consentContract !== "odla.hosted-security-consent.v1" || value.reportProjection !== "odla.hosted-security-report.v1" || value.redactionContract !== "odla.best-effort-credential-pattern-redaction.v1" || typeof value.promptBundle !== "string" || value.promptBundle.length < 1 || value.promptBundle.length > 100 || typeof value.ready !== "boolean" || typeof value.independent !== "boolean" || value.sourceDisclosure !== "redacted" || value.targetExecution !== false || !Number.isSafeInteger(value.reportRetentionDays) || value.reportRetentionDays < 1) return false;
2796
- const validRoute = (route, purpose) => !!route && route.purpose === purpose && typeof route.enabled === "boolean" && typeof route.credentialReady === "boolean" && typeof route.provider === "string" && route.provider.length > 0 && route.provider.length <= 100 && typeof route.model === "string" && route.model.length > 0 && route.model.length <= 200 && Number.isSafeInteger(route.policyVersion) && route.policyVersion >= 1 && Number.isSafeInteger(route.maxCallsPerRun) && route.maxCallsPerRun >= 1 && Number.isSafeInteger(route.maxInputBytes) && route.maxInputBytes >= 1 && Number.isSafeInteger(route.maxOutputTokens) && route.maxOutputTokens >= 1;
2797
- return validRoute(value.routes?.discovery, "security.discovery") && validRoute(value.routes?.validation, "security.validation");
2798
- }
2799
- function hostedSecurityCredential(value) {
2800
- if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
2801
- throw new Error("Hosted security requires an injected odla developer token");
2802
- }
2803
- return value;
2804
- }
2805
-
2806
- // src/security-hosted-github.ts
2807
- async function connectGitHubSecuritySource(options) {
2808
- const out = options.stdout ?? console;
2809
- const repository = githubRepositoryName(options.repository);
2810
- const attempt = await requestHostedSecurityJson(
2811
- options,
2812
- "/registry/github/connect",
2813
- {
2814
- method: "POST",
2815
- body: JSON.stringify({
2816
- appId: hostedIdentifier(options.appId, "appId"),
2817
- env: hostedIdentifier(options.env, "env"),
2818
- repository
2819
- })
2820
- },
2821
- "start GitHub connection"
2822
- );
2823
- const serverExpiry = Date.parse(attempt?.expiresAt ?? "");
2824
- if (!attempt?.attemptId || !attempt.installUrl || !Number.isFinite(serverExpiry)) {
2825
- throw new Error("odla.ai returned an invalid GitHub connection attempt");
2826
- }
2827
- const installUrl = trustedGitHubInstallUrl(attempt.installUrl);
2828
- out.log(`GitHub approval: ${installUrl}`);
2829
- if (options.open !== false) {
2830
- await (options.openInstallUrl ?? openUrl)(installUrl);
2831
- out.log("github: opened installation approval in your browser");
2832
- }
2833
- const interval = hostedPollInterval(options.pollIntervalMs);
2834
- const now = options.now ?? Date.now;
2835
- const deadline = Math.min(serverExpiry, now() + hostedPollTimeout(options.pollTimeoutMs));
2836
- const wait = options.wait ?? waitForHostedPoll;
2837
- while (true) {
2838
- const state = await requestHostedSecurityJson(
2839
- options,
2840
- `/registry/github/connect/${encodeURIComponent(attempt.attemptId)}`,
2841
- {},
2842
- "check GitHub connection"
2843
- );
2844
- if (state.status !== "pending") {
2845
- if (state.status === "connected") return state;
2846
- throw new Error(`GitHub connection ${state.status}${state.failureCode ? `: ${state.failureCode}` : ""}`);
5581
+ });
5582
+ const harness = createSecurityHarness({
5583
+ profile,
5584
+ store: new FileRunStore(resolve8(output, "state")),
5585
+ discoveryReasoner: hosted.discoveryReasoner,
5586
+ validationReasoner: hosted.validationReasoner,
5587
+ policy: {
5588
+ modelSourceDisclosure: "redacted",
5589
+ active: false,
5590
+ allowNetwork: false
2847
5591
  }
2848
- if (now() >= deadline) throw new Error("GitHub connection approval timed out");
2849
- await wait(Math.min(interval, Math.max(0, deadline - now())), options.signal);
2850
- }
2851
- }
2852
- async function listGitHubSecuritySources(options) {
2853
- const appId = hostedIdentifier(options.appId, "appId");
2854
- const env = hostedIdentifier(options.env, "env");
2855
- const body = await requestHostedSecurityJson(
2856
- options,
2857
- `/registry/apps/${encodeURIComponent(appId)}/github/sources?env=${encodeURIComponent(env)}`,
2858
- {},
2859
- "list GitHub security sources"
2860
- );
2861
- return Array.isArray(body.sources) ? body.sources : [];
2862
- }
2863
- async function disconnectGitHubSecuritySource(options) {
2864
- const appId = hostedIdentifier(options.appId, "appId");
2865
- const sourceId = hostedIdentifier(options.sourceId, "sourceId");
2866
- await requestHostedSecurityJson(
2867
- options,
2868
- `/registry/apps/${encodeURIComponent(appId)}/github/sources/${encodeURIComponent(sourceId)}`,
2869
- { method: "DELETE" },
2870
- "disconnect GitHub security source"
2871
- );
5592
+ });
5593
+ const report2 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
5594
+ await writeSecurityArtifacts(output, report2);
5595
+ const reportDigest = await securityFingerprint(report2);
5596
+ await hosted.complete({
5597
+ reportDigest,
5598
+ coverageStatus: report2.coverageStatus,
5599
+ confirmed: report2.metrics.confirmed,
5600
+ candidates: report2.metrics.candidates
5601
+ }, { signal: options.signal });
5602
+ printSummary(options.stdout ?? console, appId, env, hosted.run, report2, output);
5603
+ return Object.freeze({ report: report2, run: hosted.run, output });
2872
5604
  }
2873
- function repositoryFromGitRemote(remoteInput) {
2874
- const remote = remoteInput.trim();
2875
- const scp = /^git@github\.com:([^/\s]+)\/([^/\s]+?)\/?$/.exec(remote);
2876
- if (scp) return githubRepositoryName(`${scp[1]}/${scp[2].replace(/\.git$/, "")}`);
2877
- let url;
2878
- try {
2879
- url = new URL(remote);
2880
- } catch {
2881
- throw new Error("origin must be a github.com HTTPS or SSH repository URL");
2882
- }
2883
- if (url.hostname !== "github.com" || url.protocol !== "https:" && url.protocol !== "ssh:") {
2884
- throw new Error("origin must be a github.com HTTPS or SSH repository URL");
2885
- }
2886
- if (url.password || url.protocol === "https:" && url.username || url.search || url.hash) {
2887
- throw new Error("credential-bearing GitHub origin URLs are not accepted");
2888
- }
2889
- const parts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
2890
- if (parts.length !== 2) {
2891
- throw new Error("origin must identify one GitHub owner/name repository");
5605
+ function selectEnv(requested, declared, configPath, rootDir) {
5606
+ const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
5607
+ if (!env || !declared.includes(env)) {
5608
+ const shown = relative4(rootDir, configPath) || configPath;
5609
+ throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
2892
5610
  }
2893
- return githubRepositoryName(`${parts[0]}/${parts[1].replace(/\.git$/, "")}`);
5611
+ return env;
2894
5612
  }
2895
- async function inferGitHubRepository(cwd = process.cwd(), readOrigin = defaultReadOrigin) {
2896
- let remote;
2897
- try {
2898
- remote = await readOrigin(cwd);
2899
- } catch {
2900
- throw new Error("Could not read git origin; pass --repo owner/name");
5613
+ async function injectedToken(options, request) {
5614
+ const value = options.token ?? await options.getToken?.(Object.freeze({ ...request }));
5615
+ if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
5616
+ throw new Error(
5617
+ request.selfAudit ? "Self-audit requires an injected, scoped platform security token" : "Hosted security requires an injected app developer token or getToken callback"
5618
+ );
2901
5619
  }
2902
- return repositoryFromGitRemote(remote);
5620
+ return value;
2903
5621
  }
2904
- async function defaultReadOrigin(cwd) {
2905
- const result = await promisify(execFile)(
2906
- "git",
2907
- ["remote", "get-url", "origin"],
2908
- { cwd, encoding: "utf8" }
2909
- );
2910
- return result.stdout;
5622
+ function profileFor(name, maxHuntTasks) {
5623
+ const profile = name === "odla" ? odlaProfile() : name === "cloudflare-app" ? cloudflareAppProfile() : name === "generic" ? genericProfile() : void 0;
5624
+ if (!profile) throw new Error(`unknown security profile "${String(name)}"`);
5625
+ if (maxHuntTasks === void 0) return profile;
5626
+ if (!Number.isSafeInteger(maxHuntTasks) || maxHuntTasks < 1) throw new Error("maxHuntTasks must be a positive integer");
5627
+ return { ...profile, maxHuntTasks };
5628
+ }
5629
+ function printSummary(out, appId, env, run, report2, output) {
5630
+ const complete = report2.coverage.filter((cell) => cell.state === "complete").length;
5631
+ out.log(`security: ${appId}/${env} run=${run.runId} profile=${run.profileVersion}`);
5632
+ out.log(` discovery: ${run.discovery.identity.provider}/${run.discovery.identity.model}`);
5633
+ out.log(` validation: ${run.validation.identity.provider}/${run.validation.identity.model}`);
5634
+ out.log(` coverage: ${report2.coverageStatus} ${complete}/${report2.coverage.length} blocked=${report2.metrics.blockedCells} shallow=${report2.metrics.shallowCells} unscheduled=${report2.metrics.unscheduledCells} budget_exhausted=${report2.metrics.budgetExhaustedCells}`);
5635
+ if (report2.callBudget) out.log(` calls: discovery=${formatBudget(report2.callBudget.discovery)} validation=${formatBudget(report2.callBudget.validation)}`);
5636
+ out.log(` findings: confirmed=${report2.metrics.confirmed} needs_reproduction=${report2.metrics.needsReproduction} candidates=${report2.metrics.candidates}`);
5637
+ out.log(` report: ${resolve8(output, "REPORT.md")}`);
5638
+ }
5639
+ function formatBudget(usage) {
5640
+ return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
2911
5641
  }
2912
5642
 
2913
5643
  // src/security-hosted-intent.ts
@@ -2919,19 +5649,19 @@ async function getHostedSecurityIntent(options) {
2919
5649
  const query = new URLSearchParams({ env, sourceId });
2920
5650
  if (ref) query.set("ref", ref);
2921
5651
  if (options.profile) query.set("profile", options.profile);
2922
- const response = await requestHostedSecurityJson(
5652
+ const response2 = await requestHostedSecurityJson(
2923
5653
  options,
2924
5654
  `/registry/apps/${encodeURIComponent(appId)}/security/intent?${query.toString()}`,
2925
5655
  {},
2926
5656
  "read hosted security execution intent"
2927
5657
  );
2928
- if (!isValidHostedSecurityPlan(response.plan, env) || !isValidIntent(response.intent, { appId, env, sourceId })) {
5658
+ if (!isValidHostedSecurityPlan(response2.plan, env) || !isValidIntent(response2.intent, { appId, env, sourceId })) {
2929
5659
  throw new Error("odla.ai returned an invalid hosted security execution intent");
2930
5660
  }
2931
- if (response.intent.planDigest !== response.plan.planDigest) {
5661
+ if (response2.intent.planDigest !== response2.plan.planDigest) {
2932
5662
  throw new Error("odla.ai returned a hosted security intent for a different processing plan");
2933
5663
  }
2934
- return response;
5664
+ return response2;
2935
5665
  }
2936
5666
  function isValidIntent(value, expected) {
2937
5667
  return !!value && value.executionContract === "odla.hosted-security-execution-consent.v1" && /^sha256:[a-f0-9]{64}$/.test(value.executionDigest) && /^sha256:[a-f0-9]{64}$/.test(value.planDigest) && value.appId === expected.appId && value.env === expected.env && value.sourceId === expected.sourceId && typeof value.repository === "string" && value.repository.length > 2 && value.repository.length <= 201 && typeof value.defaultBranch === "string" && value.defaultBranch.length > 0 && value.defaultBranch.length <= 255 && typeof value.requestedRef === "string" && value.requestedRef.length > 0 && value.requestedRef.length <= 255 && !/[\u0000-\u001f\u007f]/.test(value.requestedRef) && ["odla", "cloudflare-app", "generic"].includes(value.profile) && value.sourceDisclosure === "redacted";
@@ -2994,8 +5724,8 @@ async function startHostedSecurityJob(options) {
2994
5724
  "start hosted security job"
2995
5725
  );
2996
5726
  } catch (error) {
2997
- const message = error instanceof Error ? error.message : String(error);
2998
- if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message)) {
5727
+ const message3 = error instanceof Error ? error.message : String(error);
5728
+ if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message3)) {
2999
5729
  throw new Error("Hosted security plan or execution intent changed or became unavailable after review; fetch a fresh intent and explicitly acknowledge its digest before enqueueing again", { cause: error });
3000
5730
  }
3001
5731
  throw error;
@@ -3030,7 +5760,7 @@ async function followHostedSecurityJob(options) {
3030
5760
  const interval = hostedPollInterval(options.pollIntervalMs);
3031
5761
  const now = options.now ?? Date.now;
3032
5762
  const deadline = now() + hostedPollTimeout(options.pollTimeoutMs ?? 60 * 6e4);
3033
- const wait = options.wait ?? waitForHostedPoll;
5763
+ const wait2 = options.wait ?? waitForHostedPoll;
3034
5764
  let prior = "";
3035
5765
  while (true) {
3036
5766
  const job = await getHostedSecurityJob(options);
@@ -3041,7 +5771,7 @@ async function followHostedSecurityJob(options) {
3041
5771
  if (now() >= deadline) {
3042
5772
  throw new Error(`Hosted security job ${job.jobId} did not finish before the polling deadline`);
3043
5773
  }
3044
- await wait(Math.min(interval, Math.max(0, deadline - now())), options.signal);
5774
+ await wait2(Math.min(interval, Math.max(0, deadline - now())), options.signal);
3045
5775
  }
3046
5776
  }
3047
5777
  async function getHostedSecurityReport(options) {
@@ -3074,9 +5804,9 @@ function securityExecutionDigest(value) {
3074
5804
  }
3075
5805
 
3076
5806
  // src/skill.ts
3077
- import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync, writeFileSync as writeFileSync3 } from "fs";
5807
+ import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync, writeFileSync as writeFileSync3 } from "fs";
3078
5808
  import { homedir } from "os";
3079
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join4, relative as relative3, resolve as resolve6, sep as sep2 } from "path";
5809
+ import { dirname as dirname6, isAbsolute as isAbsolute4, join as join6, relative as relative5, resolve as resolve9, sep as sep4 } from "path";
3080
5810
  import { fileURLToPath } from "url";
3081
5811
 
3082
5812
  // src/skill-adapters.ts
@@ -3137,8 +5867,8 @@ function installSkill(options = {}) {
3137
5867
  const files = listFiles(sourceDir);
3138
5868
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
3139
5869
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
3140
- const root = resolve6(options.dir ?? process.cwd());
3141
- const home = resolve6(options.homeDir ?? homedir());
5870
+ const root = resolve9(options.dir ?? process.cwd());
5871
+ const home = resolve9(options.homeDir ?? homedir());
3142
5872
  const plans = /* @__PURE__ */ new Map();
3143
5873
  const targets = /* @__PURE__ */ new Map();
3144
5874
  const rememberTarget = (harness, target) => {
@@ -3152,48 +5882,48 @@ function installSkill(options = {}) {
3152
5882
  plans.set(target, { target, content, boundary, managedMerge });
3153
5883
  };
3154
5884
  const planSkillTree = (targetDir2, boundary = root) => {
3155
- for (const rel of files) plan(join4(targetDir2, rel), readFileSync5(join4(sourceDir, rel), "utf8"), false, boundary);
5885
+ for (const rel of files) plan(join6(targetDir2, rel), readFileSync6(join6(sourceDir, rel), "utf8"), false, boundary);
3156
5886
  };
3157
5887
  let targetDir;
3158
5888
  if (options.global) {
3159
- const claudeRoot = join4(home, ".claude", "skills");
3160
- const codexRoot = resolve6(options.codexHomeDir ?? process.env.CODEX_HOME ?? join4(home, ".codex"), "skills");
5889
+ const claudeRoot = join6(home, ".claude", "skills");
5890
+ const codexRoot = resolve9(options.codexHomeDir ?? process.env.CODEX_HOME ?? join6(home, ".codex"), "skills");
3161
5891
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
3162
5892
  for (const harness of harnesses) {
3163
5893
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
3164
- planSkillTree(skillRoot, harness === "claude" ? home : dirname5(dirname5(codexRoot)));
5894
+ planSkillTree(skillRoot, harness === "claude" ? home : dirname6(dirname6(codexRoot)));
3165
5895
  rememberTarget(harness, skillRoot);
3166
5896
  }
3167
5897
  } else {
3168
- const sharedRoot = join4(root, ".agents", "skills");
5898
+ const sharedRoot = join6(root, ".agents", "skills");
3169
5899
  planSkillTree(sharedRoot);
3170
- const claudeRoot = join4(root, ".claude", "skills");
5900
+ const claudeRoot = join6(root, ".claude", "skills");
3171
5901
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
3172
5902
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
3173
5903
  if (harnesses.includes("claude")) {
3174
5904
  for (const skill of skillNames(files)) {
3175
- const canonical = readFileSync5(join4(sourceDir, skill, "SKILL.md"), "utf8");
3176
- plan(join4(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
5905
+ const canonical = readFileSync6(join6(sourceDir, skill, "SKILL.md"), "utf8");
5906
+ plan(join6(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3177
5907
  }
3178
5908
  rememberTarget("claude", claudeRoot);
3179
5909
  }
3180
5910
  if (harnesses.includes("cursor")) {
3181
- const cursorRule = join4(root, ".cursor", "rules", "odla.mdc");
5911
+ const cursorRule = join6(root, ".cursor", "rules", "odla.mdc");
3182
5912
  plan(cursorRule, CURSOR_RULE);
3183
5913
  rememberTarget("cursor", cursorRule);
3184
5914
  }
3185
5915
  if (harnesses.includes("agents")) {
3186
- const agentsFile = join4(root, "AGENTS.md");
5916
+ const agentsFile = join6(root, "AGENTS.md");
3187
5917
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3188
5918
  rememberTarget("agents", agentsFile);
3189
5919
  }
3190
5920
  if (harnesses.includes("copilot")) {
3191
- const copilotFile = join4(root, ".github", "copilot-instructions.md");
5921
+ const copilotFile = join6(root, ".github", "copilot-instructions.md");
3192
5922
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3193
5923
  rememberTarget("copilot", copilotFile);
3194
5924
  }
3195
5925
  if (harnesses.includes("gemini")) {
3196
- const geminiFile = join4(root, "GEMINI.md");
5926
+ const geminiFile = join6(root, "GEMINI.md");
3197
5927
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3198
5928
  rememberTarget("gemini", geminiFile);
3199
5929
  }
@@ -3207,11 +5937,11 @@ function installSkill(options = {}) {
3207
5937
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
3208
5938
  continue;
3209
5939
  }
3210
- if (!existsSync7(file.target)) {
5940
+ if (!existsSync8(file.target)) {
3211
5941
  writtenPaths.add(file.target);
3212
5942
  continue;
3213
5943
  }
3214
- const current = readFileSync5(file.target, "utf8");
5944
+ const current = readFileSync6(file.target, "utf8");
3215
5945
  if (current === file.content) {
3216
5946
  unchangedPaths.add(file.target);
3217
5947
  } else if (file.managedMerge || options.force) {
@@ -3228,8 +5958,8 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3228
5958
  );
3229
5959
  }
3230
5960
  for (const file of plans.values()) {
3231
- if (!existsSync7(file.target) || readFileSync5(file.target, "utf8") !== file.content) {
3232
- mkdirSync3(dirname5(file.target), { recursive: true });
5961
+ if (!existsSync8(file.target) || readFileSync6(file.target, "utf8") !== file.content) {
5962
+ mkdirSync3(dirname6(file.target), { recursive: true });
3233
5963
  writeFileSync3(file.target, file.content);
3234
5964
  }
3235
5965
  }
@@ -3249,7 +5979,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3249
5979
  };
3250
5980
  }
3251
5981
  function pathsUnder(root, paths) {
3252
- return [...paths].map((path) => relative3(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep2}`) && !isAbsolute4(path)).sort();
5982
+ return [...paths].map((path) => relative5(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute4(path)).sort();
3253
5983
  }
3254
5984
  function normalizeHarnesses(values, global) {
3255
5985
  const requested = values?.length ? values : ["claude"];
@@ -3271,9 +6001,9 @@ function normalizeHarnesses(values, global) {
3271
6001
  function managedFileContent(path, block, force, boundary) {
3272
6002
  const symlink = symlinkedComponent(boundary, path);
3273
6003
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
3274
- if (!existsSync7(path)) return `${block}
6004
+ if (!existsSync8(path)) return `${block}
3275
6005
  `;
3276
- const current = readFileSync5(path, "utf8");
6006
+ const current = readFileSync6(path, "utf8");
3277
6007
  const start = "<!-- odla-ai agent setup:start -->";
3278
6008
  const end = "<!-- odla-ai agent setup:end -->";
3279
6009
  const startAt = current.indexOf(start);
@@ -3294,13 +6024,13 @@ function managedFileContent(path, block, force, boundary) {
3294
6024
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
3295
6025
  }
3296
6026
  function symlinkedComponent(boundary, target) {
3297
- const rel = relative3(boundary, target);
3298
- if (rel === ".." || rel.startsWith(`..${sep2}`) || isAbsolute4(rel)) {
6027
+ const rel = relative5(boundary, target);
6028
+ if (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute4(rel)) {
3299
6029
  throw new Error(`agent setup target escapes its install root: ${target}`);
3300
6030
  }
3301
6031
  let current = boundary;
3302
- for (const part of rel.split(sep2).filter(Boolean)) {
3303
- current = join4(current, part);
6032
+ for (const part of rel.split(sep4).filter(Boolean)) {
6033
+ current = join6(current, part);
3304
6034
  try {
3305
6035
  if (lstatSync(current).isSymbolicLink()) return current;
3306
6036
  } catch (error) {
@@ -3313,13 +6043,13 @@ function skillNames(files) {
3313
6043
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
3314
6044
  }
3315
6045
  function listFiles(dir) {
3316
- if (!existsSync7(dir)) return [];
6046
+ if (!existsSync8(dir)) return [];
3317
6047
  const results = [];
3318
6048
  const walk = (current) => {
3319
6049
  for (const entry of readdirSync(current, { withFileTypes: true })) {
3320
- const path = join4(current, entry.name);
6050
+ const path = join6(current, entry.name);
3321
6051
  if (entry.isDirectory()) walk(path);
3322
- else results.push(relative3(dir, path));
6052
+ else results.push(relative5(dir, path));
3323
6053
  }
3324
6054
  };
3325
6055
  walk(dir);
@@ -3632,6 +6362,93 @@ async function appExport(options) {
3632
6362
  return { file, backup: newest };
3633
6363
  }
3634
6364
 
6365
+ // src/app-owners.ts
6366
+ var sink = (options) => options.stdout ?? console;
6367
+ async function ownersRequest(method, suffix, options, body) {
6368
+ const cfg = await loadProjectConfig(options.configPath);
6369
+ const doFetch = options.fetch ?? fetch;
6370
+ const token = await getDeveloperToken(
6371
+ cfg,
6372
+ { configPath: cfg.configPath, token: options.token, email: options.email, open: false },
6373
+ doFetch,
6374
+ sink(options)
6375
+ );
6376
+ const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/owners${suffix}`, {
6377
+ method,
6378
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
6379
+ body: body === void 0 ? void 0 : JSON.stringify(body)
6380
+ });
6381
+ const data = await res.json().catch(() => ({}));
6382
+ if (!res.ok) {
6383
+ throw new Error(
6384
+ `owners ${method} failed${data.error?.code ? ` (${data.error.code})` : ""}: ` + (data.error?.message ?? `registry returned ${res.status}`)
6385
+ );
6386
+ }
6387
+ return data.owners ?? [];
6388
+ }
6389
+ function report(options, owners, headline) {
6390
+ const out = sink(options);
6391
+ if (options.json === true) {
6392
+ out.log(JSON.stringify(owners, null, 2));
6393
+ return;
6394
+ }
6395
+ if (headline) out.log(headline);
6396
+ out.log(`owners (${owners.length}):`);
6397
+ for (const o of owners) {
6398
+ out.log(` ${o.primary ? "\u2605" : "\xB7"} ${o.email ?? o.ownerId}${o.primary ? " (primary)" : ""}`);
6399
+ }
6400
+ }
6401
+ async function ownersList(options) {
6402
+ report(options, await ownersRequest("GET", "", options));
6403
+ }
6404
+ async function ownersAdd(email, options) {
6405
+ const owners = await ownersRequest("POST", "", options, { email });
6406
+ report(
6407
+ options,
6408
+ owners,
6409
+ `added ${email} as a co-owner \u2014 they share full access. They can now run \`odla-ai provision\` to mint their own credentials (no secret sharing).`
6410
+ );
6411
+ }
6412
+ async function ownersRemove(target, options) {
6413
+ let ownerId = target;
6414
+ if (target.includes("@")) {
6415
+ const owners2 = await ownersRequest("GET", "", options);
6416
+ const match = owners2.find((o) => o.email?.toLowerCase() === target.toLowerCase());
6417
+ if (!match) throw new Error(`no co-owner with email ${target}`);
6418
+ if (match.primary) throw new Error("can't remove the primary owner");
6419
+ ownerId = match.ownerId;
6420
+ }
6421
+ const owners = await ownersRequest("DELETE", `/${encodeURIComponent(ownerId)}`, options);
6422
+ report(options, owners, `removed ${target}`);
6423
+ }
6424
+ async function appOwnersCommand(parsed, dependencies = {}) {
6425
+ const sub = parsed.positionals[2] ?? "list";
6426
+ const options = {
6427
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
6428
+ token: stringOpt(parsed.options.token),
6429
+ email: stringOpt(parsed.options.email),
6430
+ json: parsed.options.json === true,
6431
+ fetch: dependencies.fetch,
6432
+ stdout: dependencies.stdout
6433
+ };
6434
+ const allowed = ["config", "token", "email", "json"];
6435
+ if (sub === "list") {
6436
+ assertArgs(parsed, allowed, 3);
6437
+ await ownersList(options);
6438
+ return;
6439
+ }
6440
+ if (sub === "add" || sub === "remove") {
6441
+ assertArgs(parsed, allowed, 4);
6442
+ const email = parsed.positionals[3];
6443
+ if (!email) throw new Error(`"app owners ${sub}" needs an email \u2014 try "odla-ai app owners ${sub} teammate@example.com".`);
6444
+ await (sub === "add" ? ownersAdd(email, options) : ownersRemove(email, options));
6445
+ return;
6446
+ }
6447
+ throw new Error(
6448
+ `unknown app owners subcommand "${sub}". Try "odla-ai app owners list", "odla-ai app owners add <email>", or "odla-ai app owners remove <email>".`
6449
+ );
6450
+ }
6451
+
3635
6452
  // src/app-lifecycle.ts
3636
6453
  async function lifecycleCall(action, options) {
3637
6454
  const cfg = await loadProjectConfig(options.configPath);
@@ -3674,6 +6491,10 @@ async function appRestore(options) {
3674
6491
  }
3675
6492
  async function appCommand(parsed, dependencies = {}) {
3676
6493
  const sub = parsed.positionals[1];
6494
+ if (sub === "owners") {
6495
+ await appOwnersCommand(parsed, dependencies);
6496
+ return;
6497
+ }
3677
6498
  if (sub === "export") {
3678
6499
  assertArgs(parsed, ["config", "env", "token", "email", "fresh", "out", "json"], 2);
3679
6500
  await appExport({
@@ -3691,7 +6512,7 @@ async function appCommand(parsed, dependencies = {}) {
3691
6512
  }
3692
6513
  if (sub !== "archive" && sub !== "restore") {
3693
6514
  throw new Error(
3694
- `unknown app subcommand "${sub ?? ""}". Try "odla-ai app archive --yes", "odla-ai app restore", or "odla-ai app export". (Permanent deletion has no CLI: it requires a signed-in owner in Studio.)`
6515
+ `unknown app subcommand "${sub ?? ""}". Try "odla-ai app archive --yes", "odla-ai app restore", "odla-ai app export", or "odla-ai app owners <list|add|remove>". (Permanent deletion has no CLI: it requires a signed-in owner in Studio.)`
3695
6516
  );
3696
6517
  }
3697
6518
  assertArgs(parsed, ["config", "token", "email", "yes", "json"], 2);
@@ -3708,106 +6529,42 @@ async function appCommand(parsed, dependencies = {}) {
3708
6529
  else await appRestore(options);
3709
6530
  }
3710
6531
 
3711
- // src/help.ts
3712
- import { readFileSync as readFileSync6 } from "fs";
3713
- function cliVersion() {
3714
- const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
3715
- return pkg.version ?? "unknown";
3716
- }
3717
- function printHelp() {
3718
- console.log(`odla-ai
3719
-
3720
- Usage:
3721
- odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
3722
- odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
3723
- odla-ai doctor [--config odla.config.mjs]
3724
- odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
3725
- odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
3726
- odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
3727
- odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
3728
- odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
3729
- odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
3730
- odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
3731
- odla-ai capabilities [--json]
3732
- odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
3733
- odla-ai admin ai models [--provider <id>] [--json]
3734
- odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
3735
- odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
3736
- --validation-provider <id> --validation-model <id> [--enabled|--no-enabled]
3737
- odla-ai admin ai credentials [--json]
3738
- odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
3739
- odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
3740
- odla-ai admin ai audit [--limit <1-200>] [--json]
3741
- odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
3742
- odla-ai security github disconnect --source <id> [--env dev] [--yes]
3743
- odla-ai security plan [--env dev] [--json]
3744
- odla-ai security sources [--env dev] [--json]
3745
- odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
3746
- odla-ai security status <job-id> [--follow] [--json]
3747
- odla-ai security report <job-id> [--json]
3748
- odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
3749
- odla-ai security run [target] --self --ack-redacted-source
3750
- odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
3751
- odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
3752
- odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
3753
- odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
3754
- odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
3755
- odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
3756
- odla-ai version
3757
-
3758
- Commands:
3759
- setup Install offline odla runbooks for common coding-agent harnesses.
3760
- init Create a generic odla.config.mjs plus starter schema/rules files.
3761
- doctor Validate and summarize the project config without network calls.
3762
- calendar Inspect, connect, or disconnect the live Google booking connection.
3763
- app Archive (suspend, data retained), restore, or export the app.
3764
- Archiving takes every environment's data plane down until restored;
3765
- permanent deletion has NO CLI \u2014 it requires a signed-in owner in
3766
- Studio, and no machine or agent credential can purge. "app export"
3767
- downloads a portable gzipped-JSONL snapshot of one environment's
3768
- database (--fresh takes a new snapshot first) \u2014 your data is
3769
- always yours to take.
3770
- capabilities Show what the CLI automates vs agent edits and human checkpoints.
3771
- admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
3772
- security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
3773
- provision Register services, compose integrations, persist credentials, optionally push secrets.
3774
- smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
3775
- skill Same installer; --agent accepts all, claude, codex, cursor,
3776
- copilot, gemini, or agents (repeatable or comma-separated).
3777
- secrets Push configured db/o11y secrets into the Worker via wrangler
3778
- stdin; set stores a tenant-vault secret and set-clerk-key the
3779
- reserved Clerk secret key, write-only from stdin or an env var.
3780
- version Print the CLI version.
3781
-
3782
- Safety:
3783
- New projects target dev only. Add prod explicitly to odla.config.mjs and pass
3784
- --yes to provision it; use --dry-run first to inspect the resolved plan.
3785
- Provision caches the approved developer token and service credentials under
3786
- .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
3787
- preflights Wrangler before any shown-once issuance or destructive rotation.
3788
- Provision opens the approval page in your browser automatically whenever the
3789
- machine can show one, including agent-driven runs; only CI, SSH, and
3790
- display-less hosts skip it. Use --open to force or --no-open to suppress.
3791
- Browser launch is best-effort: the printed approval URL is authoritative and
3792
- agents must relay it to the human verbatim. A started handshake is persisted
3793
- under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
3794
- the same code. Outside an interactive terminal the wait is capped (90s by
3795
- default, --wait <seconds> to change); a still-pending handshake then exits
3796
- with code 75: relay the URL, wait for approval, and re-run to collect.
3797
- A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
3798
- The email is a non-secret identity hint: never provide a password or session
3799
- token. The matching account must already exist, be signed in, explicitly
3800
- review the exact code, and finish any current request before claiming another.
3801
- Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
3802
- OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
3803
- GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
3804
- for a PAT or provider key. GitHub read access is separate from the explicit
3805
- --ack-redacted-source consent and the exact --plan-digest printed by security
3806
- plan are required before bounded snippets reach System AI.
3807
- Run security plan first to inspect the admin-selected providers, models,
3808
- per-route bounds, credential readiness, retention, no-execution boundary,
3809
- and digest that binds consent to that exact plan.
3810
- `);
6532
+ // src/code-command.ts
6533
+ async function codeCommand(parsed, dependencies) {
6534
+ const sub = parsed.positionals[1];
6535
+ if (sub !== "connect") {
6536
+ throw new Error(`unknown code subcommand "${sub ?? ""}". Try "odla-ai code connect --env dev".`);
6537
+ }
6538
+ assertArgs(parsed, [
6539
+ "config",
6540
+ "env",
6541
+ "email",
6542
+ "open",
6543
+ "name",
6544
+ "slots",
6545
+ "engine",
6546
+ "heartbeat-ms",
6547
+ "once"
6548
+ ], 2);
6549
+ const engine = stringOpt(parsed.options.engine) ?? "auto";
6550
+ if (!["auto", "container", "podman", "docker"].includes(engine)) {
6551
+ throw new Error("--engine must be auto, container, podman, or docker");
6552
+ }
6553
+ await (dependencies.codeConnect ?? codeConnect)({
6554
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
6555
+ env: stringOpt(parsed.options.env),
6556
+ email: stringOpt(parsed.options.email),
6557
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
6558
+ name: stringOpt(parsed.options.name),
6559
+ slots: numberOpt(parsed.options.slots, "--slots"),
6560
+ engine,
6561
+ heartbeatMs: numberOpt(parsed.options["heartbeat-ms"], "--heartbeat-ms"),
6562
+ once: parsed.options.once === true,
6563
+ fetch: dependencies.fetch,
6564
+ stdout: dependencies.stdout,
6565
+ openApprovalUrl: dependencies.openUrl,
6566
+ readGitOrigin: dependencies.readGitOrigin
6567
+ });
3811
6568
  }
3812
6569
 
3813
6570
  // src/harness-options.ts
@@ -3853,12 +6610,12 @@ async function hostedSecurityContext(parsed, dependencies) {
3853
6610
  );
3854
6611
  return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
3855
6612
  }
3856
- async function interactiveConfirmation(message, dependencies) {
3857
- if (dependencies.confirm) return dependencies.confirm(message);
6613
+ async function interactiveConfirmation(message3, dependencies) {
6614
+ if (dependencies.confirm) return dependencies.confirm(message3);
3858
6615
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
3859
6616
  const prompt = createInterface({ input: process.stdin, output: process.stdout });
3860
6617
  try {
3861
- const answer = await prompt.question(`${message} [y/N] `);
6618
+ const answer = await prompt.question(`${message3} [y/N] `);
3862
6619
  return /^y(?:es)?$/i.test(answer.trim());
3863
6620
  } finally {
3864
6621
  prompt.close();
@@ -3900,9 +6657,9 @@ function printHostedSecurityIntent(out, intent) {
3900
6657
  }
3901
6658
  function assertHostedSecurityPlanReady(plan) {
3902
6659
  const reasons = [];
3903
- for (const [label, route] of Object.entries(plan.routes)) {
3904
- if (!route.enabled) reasons.push(`${label} is disabled`);
3905
- if (!route.credentialReady) reasons.push(`${label} provider credential is unavailable`);
6660
+ for (const [label, route2] of Object.entries(plan.routes)) {
6661
+ if (!route2.enabled) reasons.push(`${label} is disabled`);
6662
+ if (!route2.credentialReady) reasons.push(`${label} provider credential is unavailable`);
3906
6663
  }
3907
6664
  if (!plan.independent) reasons.push("discovery and validation are not independently routed");
3908
6665
  if (plan.ready && reasons.length === 0) return;
@@ -3925,54 +6682,51 @@ function printHostedJob(out, job, platform, appId) {
3925
6682
  out.log(` findings: confirmed=${job.counts.confirmed} needs_reproduction=${job.counts.needsReproduction} candidates=${job.counts.candidates}`);
3926
6683
  }
3927
6684
  if (job.errorCode) out.log(` failure: ${job.errorCode}`);
3928
- const url = new URL("/studio", platform);
3929
- url.searchParams.set("app", appId);
3930
- url.searchParams.set("env", job.env);
3931
- url.searchParams.set("tab", "security");
6685
+ const url = new URL(`/studio/apps/${encodeURIComponent(appId)}/${encodeURIComponent(job.env)}/security`, platform);
3932
6686
  url.searchParams.set("job", job.jobId);
3933
6687
  out.log(` Studio: ${url.toString()}`);
3934
6688
  }
3935
- function printHostedReport(out, report) {
3936
- out.log(`security report ${report.jobId}: ${report.repository}@${report.revision}`);
3937
- out.log(` coverage: ${report.coverageStatus} cells=${report.metrics.coverageCells} shallow=${report.metrics.shallowCells} blocked=${report.metrics.blockedCells} unscheduled=${report.metrics.unscheduledCells} budget_exhausted=${report.metrics.budgetExhaustedCells}`);
3938
- out.log(` findings: confirmed=${report.metrics.confirmed} needs_reproduction=${report.metrics.needsReproduction} candidates=${report.metrics.candidates} rejected=${report.metrics.rejected}`);
3939
- out.log(` discovery: ${report.provenance.discovery?.provider ?? "unknown"}/${report.provenance.discovery?.model ?? "unknown"}`);
3940
- out.log(` validation: ${report.provenance.validation?.provider ?? "unknown"}/${report.provenance.validation?.model ?? "unknown"} independent=${String(report.provenance.independentValidation)}`);
3941
- for (const finding of report.findings) {
6689
+ function printHostedReport(out, report2) {
6690
+ out.log(`security report ${report2.jobId}: ${report2.repository}@${report2.revision}`);
6691
+ out.log(` coverage: ${report2.coverageStatus} cells=${report2.metrics.coverageCells} shallow=${report2.metrics.shallowCells} blocked=${report2.metrics.blockedCells} unscheduled=${report2.metrics.unscheduledCells} budget_exhausted=${report2.metrics.budgetExhaustedCells}`);
6692
+ out.log(` findings: confirmed=${report2.metrics.confirmed} needs_reproduction=${report2.metrics.needsReproduction} candidates=${report2.metrics.candidates} rejected=${report2.metrics.rejected}`);
6693
+ out.log(` discovery: ${report2.provenance.discovery?.provider ?? "unknown"}/${report2.provenance.discovery?.model ?? "unknown"}`);
6694
+ out.log(` validation: ${report2.provenance.validation?.provider ?? "unknown"}/${report2.provenance.validation?.model ?? "unknown"} independent=${String(report2.provenance.independentValidation)}`);
6695
+ for (const finding of report2.findings) {
3942
6696
  const location = finding.locations[0];
3943
6697
  out.log(` [${finding.severity}] ${finding.title}${location ? ` (${location.path}:${location.line})` : ""} \xB7 ${finding.disposition}`);
3944
6698
  }
3945
- for (const limitation of report.limitations) out.log(` limitation: ${limitation}`);
6699
+ for (const limitation of report2.limitations) out.log(` limitation: ${limitation}`);
3946
6700
  }
3947
- function enforceHostedReportGate(report, parsed, out, emitSuccess) {
6701
+ function enforceHostedReportGate(report2, parsed, out, emitSuccess) {
3948
6702
  const failOn = hostedSeverity(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
3949
6703
  const candidateValue = parsed.options["fail-on-candidates"];
3950
6704
  const failOnCandidates = candidateValue === false ? void 0 : hostedSeverity(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
3951
6705
  const atOrAbove = (severity, threshold) => HOSTED_SEVERITIES.indexOf(severity) >= HOSTED_SEVERITIES.indexOf(threshold);
3952
- const confirmed = report.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
3953
- const leads = failOnCandidates ? report.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
3954
- const incomplete = report.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
6706
+ const confirmed = report2.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
6707
+ const leads = failOnCandidates ? report2.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
6708
+ const incomplete = report2.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
3955
6709
  if (confirmed.length || leads.length || incomplete) {
3956
- throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report.coverageStatus}` : ""}`);
6710
+ throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report2.coverageStatus}` : ""}`);
3957
6711
  }
3958
6712
  if (emitSuccess) {
3959
- out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report.coverageStatus}. This is not proof that the application is secure.`);
6713
+ out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report2.coverageStatus}. This is not proof that the application is secure.`);
3960
6714
  }
3961
6715
  }
3962
- function printHostedSecurityPlanRoute(out, label, route) {
3963
- const readiness = route.enabled && route.credentialReady ? "ready" : [
3964
- route.enabled ? void 0 : "disabled",
3965
- route.credentialReady ? void 0 : "credential unavailable"
6716
+ function printHostedSecurityPlanRoute(out, label, route2) {
6717
+ const readiness = route2.enabled && route2.credentialReady ? "ready" : [
6718
+ route2.enabled ? void 0 : "disabled",
6719
+ route2.credentialReady ? void 0 : "credential unavailable"
3966
6720
  ].filter(Boolean).join(", ");
3967
- out.log(` ${label}: ${route.provider}/${route.model} \xB7 policy v${route.policyVersion} \xB7 ${readiness}`);
3968
- out.log(` bounds: ${route.maxCallsPerRun} calls/run \xB7 ${route.maxInputBytes} input bytes/call \xB7 ${route.maxOutputTokens} output tokens/call`);
6721
+ out.log(` ${label}: ${route2.provider}/${route2.model} \xB7 policy v${route2.policyVersion} \xB7 ${readiness}`);
6722
+ out.log(` bounds: ${route2.maxCallsPerRun} calls/run \xB7 ${route2.maxInputBytes} input bytes/call \xB7 ${route2.maxOutputTokens} output tokens/call`);
3969
6723
  }
3970
6724
  function printHostedCoverage(out, job) {
3971
6725
  const coverage = job.coverage;
3972
6726
  out.log(` coverage: ${job.coverageStatus ?? "pending"}${coverage?.completeCells !== void 0 ? ` ${coverage.completeCells}/${coverage.totalCells ?? "?"}` : ""}${coverage?.shallowCells ? ` shallow=${coverage.shallowCells}` : ""}${coverage?.blockedCells ? ` blocked=${coverage.blockedCells}` : ""}${coverage?.unscheduledCells ? ` unscheduled=${coverage.unscheduledCells}` : ""}${coverage?.budgetExhaustedCells ? ` budget_exhausted=${coverage.budgetExhaustedCells}` : ""}`);
3973
6727
  }
3974
- function routeLabel(route) {
3975
- return `${route.provider}/${route.model}${route.policyVersion ? ` policy v${route.policyVersion}` : ""}`;
6728
+ function routeLabel(route2) {
6729
+ return `${route2.provider}/${route2.model}${route2.policyVersion ? ` policy v${route2.policyVersion}` : ""}`;
3976
6730
  }
3977
6731
  var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
3978
6732
  function hostedSeverity(value, flag) {
@@ -4060,13 +6814,13 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
4060
6814
  }
4061
6815
  throw new Error(`hosted security job ${result.jobId} ended ${result.status}${result.errorCode ? `: ${result.errorCode}` : ""}`);
4062
6816
  }
4063
- const report = await getHostedSecurityReport({ ...context, jobId: result.jobId });
6817
+ const report2 = await getHostedSecurityReport({ ...context, jobId: result.jobId });
4064
6818
  if (parsed.options.json === true) {
4065
- context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report }, null, 2));
6819
+ context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report: report2 }, null, 2));
4066
6820
  } else {
4067
- printHostedReport(context.stdout, report);
6821
+ printHostedReport(context.stdout, report2);
4068
6822
  }
4069
- enforceHostedReportGate(report, parsed, context.stdout, parsed.options.json !== true);
6823
+ enforceHostedReportGate(report2, parsed, context.stdout, parsed.options.json !== true);
4070
6824
  }
4071
6825
  async function runLocalSecurityCommand(parsed, dependencies) {
4072
6826
  if (parsed.options.source === true) {
@@ -4133,13 +6887,13 @@ async function runLocalSecurityCommand(parsed, dependencies) {
4133
6887
  });
4134
6888
  enforceLocalGate(result.report, parsed);
4135
6889
  }
4136
- function enforceLocalGate(report, parsed) {
6890
+ function enforceLocalGate(report2, parsed) {
4137
6891
  const failOn = severityOpt(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
4138
6892
  const candidateValue = parsed.options["fail-on-candidates"];
4139
6893
  const failOnCandidates = candidateValue === false ? void 0 : severityOpt(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
4140
- const confirmed = findingsAtOrAbove(report, failOn);
4141
- const leads = failOnCandidates ? findingsAtOrAbove(report, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
4142
- const incomplete = report.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
6894
+ const confirmed = findingsAtOrAbove(report2, failOn);
6895
+ const leads = failOnCandidates ? findingsAtOrAbove(report2, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
6896
+ const incomplete = report2.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
4143
6897
  if (confirmed.length || leads.length || incomplete) {
4144
6898
  throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
4145
6899
  }
@@ -4178,9 +6932,9 @@ async function securityCommand(parsed, dependencies) {
4178
6932
  assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 3);
4179
6933
  const jobId = requiredSecurityPositional(parsed, 2, "job id");
4180
6934
  const context = await hostedSecurityContext(parsed, dependencies);
4181
- const report = await getHostedSecurityReport({ ...context, jobId });
4182
- if (parsed.options.json === true) context.stdout.log(JSON.stringify(report, null, 2));
4183
- else printHostedReport(context.stdout, report);
6935
+ const report2 = await getHostedSecurityReport({ ...context, jobId });
6936
+ if (parsed.options.json === true) context.stdout.log(JSON.stringify(report2, null, 2));
6937
+ else printHostedReport(context.stdout, report2);
4184
6938
  return;
4185
6939
  }
4186
6940
  if (sub !== "run") {
@@ -4314,6 +7068,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
4314
7068
  printCapabilities(parsed.options.json === true);
4315
7069
  return;
4316
7070
  }
7071
+ if (command === "code") {
7072
+ await codeCommand(parsed, dependencies);
7073
+ return;
7074
+ }
4317
7075
  if (command === "admin") {
4318
7076
  await adminCommand(parsed);
4319
7077
  return;
@@ -4481,6 +7239,15 @@ export {
4481
7239
  calendarCalendars,
4482
7240
  calendarConnect,
4483
7241
  calendarDisconnect,
7242
+ connectGitHubSecuritySource,
7243
+ listGitHubSecuritySources,
7244
+ disconnectGitHubSecuritySource,
7245
+ repositoryFromGitRemote,
7246
+ inferGitHubRepository,
7247
+ CODE_PI_IMAGE,
7248
+ CODE_BUILD_RECIPES,
7249
+ codeConnect,
7250
+ runCodeRuntime,
4484
7251
  doctor,
4485
7252
  initProject,
4486
7253
  secretsPush,
@@ -4488,11 +7255,6 @@ export {
4488
7255
  secretsSet,
4489
7256
  secretsSetClerkKey,
4490
7257
  runHostedSecurity,
4491
- connectGitHubSecuritySource,
4492
- listGitHubSecuritySources,
4493
- disconnectGitHubSecuritySource,
4494
- repositoryFromGitRemote,
4495
- inferGitHubRepository,
4496
7258
  getHostedSecurityIntent,
4497
7259
  getHostedSecurityPlan,
4498
7260
  startHostedSecurityJob,
@@ -4507,4 +7269,4 @@ export {
4507
7269
  exitCodeFor,
4508
7270
  runCli
4509
7271
  };
4510
- //# sourceMappingURL=chunk-I7XDJZB6.js.map
7272
+ //# sourceMappingURL=chunk-3AC74CD2.js.map