@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.
package/dist/index.cjs CHANGED
@@ -33,6 +33,8 @@ var index_exports = {};
33
33
  __export(index_exports, {
34
34
  AGENT_HARNESSES: () => AGENT_HARNESSES,
35
35
  CAPABILITIES: () => CAPABILITIES,
36
+ CODE_BUILD_RECIPES: () => CODE_BUILD_RECIPES,
37
+ CODE_PI_IMAGE: () => CODE_PI_IMAGE,
36
38
  GOOGLE_CALENDAR_EVENTS_SCOPE: () => GOOGLE_CALENDAR_EVENTS_SCOPE,
37
39
  SYSTEM_AI_PURPOSES: () => SYSTEM_AI_PURPOSES,
38
40
  adminAi: () => adminAi,
@@ -42,6 +44,7 @@ __export(index_exports, {
42
44
  calendarDisconnect: () => calendarDisconnect,
43
45
  calendarServiceConfig: () => calendarServiceConfig,
44
46
  calendarStatus: () => calendarStatus,
47
+ codeConnect: () => codeConnect,
45
48
  connectGitHubSecuritySource: () => connectGitHubSecuritySource,
46
49
  disconnectGitHubSecuritySource: () => disconnectGitHubSecuritySource,
47
50
  doctor: () => doctor,
@@ -63,6 +66,7 @@ __export(index_exports, {
63
66
  redactSecrets: () => redactSecrets,
64
67
  repositoryFromGitRemote: () => repositoryFromGitRemote,
65
68
  runCli: () => runCli,
69
+ runCodeRuntime: () => runCodeRuntime,
66
70
  runHostedSecurity: () => runHostedSecurity,
67
71
  secretsPush: () => secretsPush,
68
72
  secretsSet: () => secretsSet,
@@ -254,7 +258,7 @@ var import_node_process2 = __toESM(require("process"), 1);
254
258
  async function openUrl(url, options = {}) {
255
259
  const command = openerFor(options.platform ?? import_node_process2.default.platform);
256
260
  const doSpawn = options.spawnImpl ?? import_node_child_process.spawn;
257
- await new Promise((resolve7, reject) => {
261
+ await new Promise((resolve10, reject) => {
258
262
  const child = doSpawn(command.cmd, [...command.args, url], {
259
263
  stdio: "ignore",
260
264
  detached: true
@@ -262,7 +266,7 @@ async function openUrl(url, options = {}) {
262
266
  child.once("error", reject);
263
267
  child.once("spawn", () => {
264
268
  child.unref();
265
- resolve7();
269
+ resolve10();
266
270
  });
267
271
  });
268
272
  }
@@ -495,7 +499,7 @@ function audienceBoundEnvToken(token, platform) {
495
499
  async function scopedToken(platform, scope, options, doFetch, out) {
496
500
  const audience = platformAudience(platform);
497
501
  const tokenFile = options.tokenFile ?? `${import_node_process5.default.cwd()}/.odla/admin-token.local.json`;
498
- const cache = readJsonFile(tokenFile);
502
+ const cache = options.cache === false ? null : readJsonFile(tokenFile);
499
503
  const cached = cache?.platform === audience ? cache.tokens?.[scope] : void 0;
500
504
  if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
501
505
  out.log(`auth: using cached ${scope} grant (${tokenFile})`);
@@ -505,7 +509,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
505
509
  const { token, expiresAt } = await (0, import_db2.requestToken)({
506
510
  endpoint: audience,
507
511
  email,
508
- label: `odla CLI admin AI (${scope})`,
512
+ label: options.label ?? `odla CLI admin AI (${scope})`,
509
513
  scopes: [scope],
510
514
  fetch: doFetch,
511
515
  onCode: async ({ userCode, expiresIn, verificationUriComplete }) => {
@@ -516,11 +520,15 @@ async function scopedToken(platform, scope, options, doFetch, out) {
516
520
  }
517
521
  }
518
522
  });
519
- const tokens = cache?.platform === audience ? { ...cache.tokens ?? {} } : {};
520
- tokens[scope] = { token, expiresAt };
521
- if ((0, import_node_fs3.existsSync)(`${import_node_process5.default.cwd()}/.git`)) ensureGitignore(import_node_process5.default.cwd(), [tokenFile]);
522
- writePrivateJson(tokenFile, { platform: audience, email, tokens });
523
- out.log(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
523
+ if (options.cache !== false) {
524
+ const tokens = cache?.platform === audience ? { ...cache.tokens ?? {} } : {};
525
+ tokens[scope] = { token, expiresAt };
526
+ if ((0, import_node_fs3.existsSync)(`${import_node_process5.default.cwd()}/.git`)) ensureGitignore(import_node_process5.default.cwd(), [tokenFile]);
527
+ writePrivateJson(tokenFile, { platform: audience, email, tokens });
528
+ out.log(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
529
+ } else {
530
+ out.log(`auth: ${scope} grant is in memory only; its credential record remains in odla-ai/db`);
531
+ }
524
532
  return token;
525
533
  }
526
534
 
@@ -533,11 +541,11 @@ function adminAiAuditQuery(filters) {
533
541
  return `?limit=${filters.limit}`;
534
542
  }
535
543
  async function readAdminAiAudit(request) {
536
- const response = await request.fetch(`${request.platform}/registry/platform/ai-audit${request.query}`, {
544
+ const response2 = await request.fetch(`${request.platform}/registry/platform/ai-audit${request.query}`, {
537
545
  headers: request.headers
538
546
  });
539
- const body = await responseBody(response);
540
- if (!response.ok) throw new Error(apiError(response.status, body));
547
+ const body = await responseBody(response2);
548
+ if (!response2.ok) throw new Error(apiError(response2.status, body));
541
549
  if (request.json) {
542
550
  request.stdout.log(JSON.stringify(body, null, 2));
543
551
  return;
@@ -547,18 +555,18 @@ async function readAdminAiAudit(request) {
547
555
  for (const event of events) {
548
556
  const before = isRecord(event.oldPolicy) ? event.oldPolicy : void 0;
549
557
  const after = isRecord(event.newPolicy) ? event.newPolicy : void 0;
550
- 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";
558
+ 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";
551
559
  request.stdout.log([
552
560
  timestamp(event.createdAt),
553
561
  String(event.changeKind ?? ""),
554
562
  String(event.purpose ?? event.provider ?? ""),
555
- route,
563
+ route2,
556
564
  `${String(event.actorType ?? "")}:${String(event.actorId ?? "")}`
557
565
  ].join(" "));
558
566
  }
559
567
  }
560
- async function responseBody(response) {
561
- const text = await response.text();
568
+ async function responseBody(response2) {
569
+ const text = await response2.text();
562
570
  if (!text) return {};
563
571
  try {
564
572
  return JSON.parse(text);
@@ -568,8 +576,8 @@ async function responseBody(response) {
568
576
  }
569
577
  function apiError(status, body) {
570
578
  const error = isRecord(body) && isRecord(body.error) ? body.error : void 0;
571
- const message = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
572
- return `read System AI admin changes failed (${status}): ${message}`;
579
+ const message3 = error && typeof error.message === "string" ? error.message : isRecord(body) && typeof body.message === "string" ? body.message : "request failed";
580
+ return `read System AI admin changes failed (${status}): ${message3}`;
573
581
  }
574
582
  function timestamp(value) {
575
583
  if (typeof value !== "number" || !Number.isFinite(value)) return String(value ?? "");
@@ -670,8 +678,8 @@ async function responseBody2(res) {
670
678
  }
671
679
  function apiError2(action, status, body) {
672
680
  const error = isRecord2(body) && isRecord2(body.error) ? body.error : void 0;
673
- const message = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
674
- return `${action} failed (${status}): ${message}`;
681
+ const message3 = error && typeof error.message === "string" ? error.message : isRecord2(body) && typeof body.message === "string" ? body.message : "request failed";
682
+ return `${action} failed (${status}): ${message3}`;
675
683
  }
676
684
  function isRecord2(value) {
677
685
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -798,9 +806,9 @@ async function adminAi(options) {
798
806
  validation: validationUpdate
799
807
  })
800
808
  });
801
- const response2 = await responseBody3(res2);
802
- if (!res2.ok) throw new Error(apiError3("update security review routes", res2.status, response2));
803
- out.log(options.json ? JSON.stringify(response2, null, 2) : "updated security review discovery and independent validation routes atomically");
809
+ const response3 = await responseBody3(res2);
810
+ if (!res2.ok) throw new Error(apiError3("update security review routes", res2.status, response3));
811
+ out.log(options.json ? JSON.stringify(response3, null, 2) : "updated security review discovery and independent validation routes atomically");
804
812
  return;
805
813
  }
806
814
  const purpose = requireSystemAiPurpose(options.purpose);
@@ -818,9 +826,9 @@ async function adminAi(options) {
818
826
  headers,
819
827
  body: JSON.stringify(body)
820
828
  });
821
- const response = await responseBody3(res);
822
- if (!res.ok) throw new Error(apiError3(`update ${purpose}`, res.status, response));
823
- const policy = isRecord3(response) && isRecord3(response.policy) ? response.policy : isRecord3(response) ? response : {};
829
+ const response2 = await responseBody3(res);
830
+ if (!res.ok) throw new Error(apiError3(`update ${purpose}`, res.status, response2));
831
+ const policy = isRecord3(response2) && isRecord3(response2.policy) ? response2.policy : isRecord3(response2) ? response2 : {};
824
832
  out.log(options.json ? JSON.stringify({ policy }, null, 2) : `updated ${purpose}: ${String(policy.provider ?? "unchanged")}/${String(policy.model ?? "unchanged")}`);
825
833
  }
826
834
  function requireProvider(value) {
@@ -851,8 +859,8 @@ async function responseBody3(res) {
851
859
  }
852
860
  function apiError3(action, status, body) {
853
861
  const error = isRecord3(body) && isRecord3(body.error) ? body.error : void 0;
854
- const message = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
855
- return `${action} failed (${status}): ${message}`;
862
+ const message3 = error && typeof error.message === "string" ? error.message : isRecord3(body) && typeof body.message === "string" ? body.message : "request failed";
863
+ return `${action} failed (${status}): ${message3}`;
856
864
  }
857
865
  function isRecord3(value) {
858
866
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -1325,6 +1333,93 @@ async function appExport(options) {
1325
1333
  return { file, backup: newest };
1326
1334
  }
1327
1335
 
1336
+ // src/app-owners.ts
1337
+ var sink = (options) => options.stdout ?? console;
1338
+ async function ownersRequest(method, suffix, options, body) {
1339
+ const cfg = await loadProjectConfig(options.configPath);
1340
+ const doFetch = options.fetch ?? fetch;
1341
+ const token = await getDeveloperToken(
1342
+ cfg,
1343
+ { configPath: cfg.configPath, token: options.token, email: options.email, open: false },
1344
+ doFetch,
1345
+ sink(options)
1346
+ );
1347
+ const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/owners${suffix}`, {
1348
+ method,
1349
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
1350
+ body: body === void 0 ? void 0 : JSON.stringify(body)
1351
+ });
1352
+ const data = await res.json().catch(() => ({}));
1353
+ if (!res.ok) {
1354
+ throw new Error(
1355
+ `owners ${method} failed${data.error?.code ? ` (${data.error.code})` : ""}: ` + (data.error?.message ?? `registry returned ${res.status}`)
1356
+ );
1357
+ }
1358
+ return data.owners ?? [];
1359
+ }
1360
+ function report(options, owners, headline) {
1361
+ const out = sink(options);
1362
+ if (options.json === true) {
1363
+ out.log(JSON.stringify(owners, null, 2));
1364
+ return;
1365
+ }
1366
+ if (headline) out.log(headline);
1367
+ out.log(`owners (${owners.length}):`);
1368
+ for (const o of owners) {
1369
+ out.log(` ${o.primary ? "\u2605" : "\xB7"} ${o.email ?? o.ownerId}${o.primary ? " (primary)" : ""}`);
1370
+ }
1371
+ }
1372
+ async function ownersList(options) {
1373
+ report(options, await ownersRequest("GET", "", options));
1374
+ }
1375
+ async function ownersAdd(email, options) {
1376
+ const owners = await ownersRequest("POST", "", options, { email });
1377
+ report(
1378
+ options,
1379
+ owners,
1380
+ `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).`
1381
+ );
1382
+ }
1383
+ async function ownersRemove(target, options) {
1384
+ let ownerId = target;
1385
+ if (target.includes("@")) {
1386
+ const owners2 = await ownersRequest("GET", "", options);
1387
+ const match = owners2.find((o) => o.email?.toLowerCase() === target.toLowerCase());
1388
+ if (!match) throw new Error(`no co-owner with email ${target}`);
1389
+ if (match.primary) throw new Error("can't remove the primary owner");
1390
+ ownerId = match.ownerId;
1391
+ }
1392
+ const owners = await ownersRequest("DELETE", `/${encodeURIComponent(ownerId)}`, options);
1393
+ report(options, owners, `removed ${target}`);
1394
+ }
1395
+ async function appOwnersCommand(parsed, dependencies = {}) {
1396
+ const sub = parsed.positionals[2] ?? "list";
1397
+ const options = {
1398
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
1399
+ token: stringOpt(parsed.options.token),
1400
+ email: stringOpt(parsed.options.email),
1401
+ json: parsed.options.json === true,
1402
+ fetch: dependencies.fetch,
1403
+ stdout: dependencies.stdout
1404
+ };
1405
+ const allowed = ["config", "token", "email", "json"];
1406
+ if (sub === "list") {
1407
+ assertArgs(parsed, allowed, 3);
1408
+ await ownersList(options);
1409
+ return;
1410
+ }
1411
+ if (sub === "add" || sub === "remove") {
1412
+ assertArgs(parsed, allowed, 4);
1413
+ const email = parsed.positionals[3];
1414
+ if (!email) throw new Error(`"app owners ${sub}" needs an email \u2014 try "odla-ai app owners ${sub} teammate@example.com".`);
1415
+ await (sub === "add" ? ownersAdd(email, options) : ownersRemove(email, options));
1416
+ return;
1417
+ }
1418
+ throw new Error(
1419
+ `unknown app owners subcommand "${sub}". Try "odla-ai app owners list", "odla-ai app owners add <email>", or "odla-ai app owners remove <email>".`
1420
+ );
1421
+ }
1422
+
1328
1423
  // src/app-lifecycle.ts
1329
1424
  async function lifecycleCall(action, options) {
1330
1425
  const cfg = await loadProjectConfig(options.configPath);
@@ -1367,6 +1462,10 @@ async function appRestore(options) {
1367
1462
  }
1368
1463
  async function appCommand(parsed, dependencies = {}) {
1369
1464
  const sub = parsed.positionals[1];
1465
+ if (sub === "owners") {
1466
+ await appOwnersCommand(parsed, dependencies);
1467
+ return;
1468
+ }
1370
1469
  if (sub === "export") {
1371
1470
  assertArgs(parsed, ["config", "env", "token", "email", "fresh", "out", "json"], 2);
1372
1471
  await appExport({
@@ -1384,7 +1483,7 @@ async function appCommand(parsed, dependencies = {}) {
1384
1483
  }
1385
1484
  if (sub !== "archive" && sub !== "restore") {
1386
1485
  throw new Error(
1387
- `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.)`
1486
+ `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.)`
1388
1487
  );
1389
1488
  }
1390
1489
  assertArgs(parsed, ["config", "token", "email", "yes", "json"], 2);
@@ -1407,6 +1506,7 @@ var CAPABILITIES = {
1407
1506
  "start an email-bound device request without accepting a password or user session token",
1408
1507
  "register the app and enable configured services per environment",
1409
1508
  "issue, persist, and explicitly rotate configured service credentials",
1509
+ "add or remove app co-owners, each of whom self-provisions their own credentials for the shared db without any secret handoff",
1410
1510
  "write local Worker values and push deployed Worker secrets through Wrangler stdin",
1411
1511
  "store tenant-vault secrets (including the Clerk webhook secret and reserved Clerk secret key) write-only from stdin or an env var",
1412
1512
  "compose declared app-capability schema/rules, create guarded seeds when absent, and configure platform AI, auth, and deployment links",
@@ -1435,13 +1535,13 @@ var CAPABILITIES = {
1435
1535
  "view telemetry and environment state",
1436
1536
  "let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
1437
1537
  "review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
1438
- "perform manual credential recovery when the CLI's local shown-once copy is unavailable",
1538
+ "perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
1439
1539
  "configure system AI purposes and view app/environment/run-attributed usage",
1440
1540
  "connect/revoke GitHub security sources and inspect ref-to-SHA jobs, routes, retention, coverage, and normalized reports"
1441
1541
  ]
1442
1542
  };
1443
- function printCapabilities(json = false, out = console) {
1444
- if (json) {
1543
+ function printCapabilities(json2 = false, out = console) {
1544
+ if (json2) {
1445
1545
  out.log(JSON.stringify(CAPABILITIES, null, 2));
1446
1546
  return;
1447
1547
  }
@@ -1471,8 +1571,8 @@ var CalendarRequestError = class extends Error {
1471
1571
  status;
1472
1572
  /** Machine-readable `error.code` from the response body, when present. */
1473
1573
  code;
1474
- constructor(message, status, code) {
1475
- super(message);
1574
+ constructor(message3, status, code) {
1575
+ super(message3);
1476
1576
  this.name = "CalendarRequestError";
1477
1577
  this.status = status;
1478
1578
  if (code !== void 0) this.code = code;
@@ -1625,9 +1725,9 @@ async function calendarJson(ctx, suffix, init) {
1625
1725
  const url = new URL(`/registry/apps/${encodeURIComponent(appId)}/calendar${suffix}`, platform);
1626
1726
  url.searchParams.set("env", env);
1627
1727
  const token = credential(ctx.token);
1628
- let response;
1728
+ let response2;
1629
1729
  try {
1630
- response = await (ctx.fetch ?? fetch)(url, {
1730
+ response2 = await (ctx.fetch ?? fetch)(url, {
1631
1731
  ...init,
1632
1732
  redirect: "error",
1633
1733
  credentials: "omit",
@@ -1636,12 +1736,12 @@ async function calendarJson(ctx, suffix, init) {
1636
1736
  } catch (error) {
1637
1737
  throw new Error(`calendar request failed: ${error instanceof Error ? error.message : String(error)}`);
1638
1738
  }
1639
- const body = await response.json().catch(() => ({}));
1640
- if (!response.ok) {
1739
+ const body = await response2.json().catch(() => ({}));
1740
+ if (!response2.ok) {
1641
1741
  const code = textField(body.error?.code, 128);
1642
- const message = textField(body.error?.message, 500);
1643
- const detail = `${code ? ` ${code}` : ""}${message ? `: ${message}` : ""}`;
1644
- throw new CalendarRequestError(redactSecrets(`calendar request failed (${response.status})${detail}`), response.status, code);
1742
+ const message3 = textField(body.error?.message, 500);
1743
+ const detail = `${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`;
1744
+ throw new CalendarRequestError(redactSecrets(`calendar request failed (${response2.status})${detail}`), response2.status, code);
1645
1745
  }
1646
1746
  return body;
1647
1747
  }
@@ -1699,8 +1799,8 @@ function credential(value) {
1699
1799
  // src/calendar-poll.ts
1700
1800
  async function waitForCalendarPoll(milliseconds, signal) {
1701
1801
  if (signal?.aborted) throw signal.reason ?? new Error("calendar connection aborted");
1702
- await new Promise((resolve7, reject) => {
1703
- const timer = setTimeout(resolve7, milliseconds);
1802
+ await new Promise((resolve10, reject) => {
1803
+ const timer = setTimeout(resolve10, milliseconds);
1704
1804
  signal?.addEventListener("abort", () => {
1705
1805
  clearTimeout(timer);
1706
1806
  reject(signal.reason ?? new Error("calendar connection aborted"));
@@ -1802,7 +1902,7 @@ async function continueConnectedCalendar(ctx, current, options) {
1802
1902
  if (current.status !== "authorizing") return null;
1803
1903
  const now = options.now ?? Date.now;
1804
1904
  const deadline = now() + pollTimeout(options.pollTimeoutMs);
1805
- const wait = options.wait ?? waitForCalendarPoll;
1905
+ const wait2 = options.wait ?? waitForCalendarPoll;
1806
1906
  let prior = "";
1807
1907
  while (now() < deadline) {
1808
1908
  if (options.signal?.aborted) throw options.signal.reason ?? new Error("calendar connection aborted");
@@ -1818,7 +1918,7 @@ async function continueConnectedCalendar(ctx, current, options) {
1818
1918
  throw new Error(`calendar connection failed${reason ? `: ${reason}` : ""}; inspect status and reconnect explicitly`);
1819
1919
  }
1820
1920
  if (status.status !== "authorizing") return null;
1821
- await wait(Math.min(2e3, Math.max(0, deadline - now())), options.signal);
1921
+ await wait2(Math.min(2e3, Math.max(0, deadline - now())), options.signal);
1822
1922
  }
1823
1923
  throw new Error('Existing Google Calendar authorization timed out; run "odla-ai calendar status" and retry');
1824
1924
  }
@@ -1833,7 +1933,7 @@ async function connectWithContext(ctx, options) {
1833
1933
  const now = options.now ?? Date.now;
1834
1934
  const timeout = pollTimeout(options.pollTimeoutMs);
1835
1935
  const deadline = Math.min(attempt.expiresAt, now() + timeout);
1836
- const wait = options.wait ?? waitForCalendarPoll;
1936
+ const wait2 = options.wait ?? waitForCalendarPoll;
1837
1937
  let prior = "";
1838
1938
  while (now() < deadline) {
1839
1939
  if (options.signal?.aborted) throw options.signal.reason ?? new Error("calendar connection aborted");
@@ -1850,7 +1950,7 @@ async function connectWithContext(ctx, options) {
1850
1950
  const reason = status.error?.message ?? status.error?.code;
1851
1951
  throw new Error(`calendar connection ${status.status}${reason ? `: ${reason}` : ""}`);
1852
1952
  }
1853
- await wait(Math.min(2e3, Math.max(0, deadline - now())), options.signal);
1953
+ await wait2(Math.min(2e3, Math.max(0, deadline - now())), options.signal);
1854
1954
  }
1855
1955
  throw new Error('Google Calendar consent timed out; run "odla-ai calendar status" and retry connect');
1856
1956
  }
@@ -1876,8 +1976,8 @@ function pollTimeout(value = 10 * 6e4) {
1876
1976
  function productionConsent(env, yes, action) {
1877
1977
  if ((env === "prod" || env === "production") && !yes) throw new Error(`refusing to ${action} for "${env}" without --yes`);
1878
1978
  }
1879
- function printStatus(status, json, out) {
1880
- if (json) {
1979
+ function printStatus(status, json2, out) {
1980
+ if (json2) {
1881
1981
  out.log(JSON.stringify(status, null, 2));
1882
1982
  return;
1883
1983
  }
@@ -1889,192 +1989,3169 @@ function printStatus(status, json, out) {
1889
1989
  if (status.error?.code || status.error?.message) out.log(` error: ${status.error.code ?? "error"}${status.error.message ? ` \u2014 ${status.error.message}` : ""}`);
1890
1990
  }
1891
1991
 
1892
- // src/doctor-checks.ts
1893
- var import_node_child_process3 = require("child_process");
1992
+ // src/code-connect.ts
1894
1993
  var import_node_fs7 = require("fs");
1895
- var import_node_path5 = require("path");
1896
-
1897
- // src/wrangler.ts
1898
- var import_node_child_process2 = require("child_process");
1899
- var import_node_fs6 = require("fs");
1994
+ var import_node_os = require("os");
1900
1995
  var import_node_path4 = require("path");
1901
- var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
1902
- const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
1903
- let stdout = "";
1904
- let stderr = "";
1905
- child.stdout.on("data", (chunk) => stdout += chunk.toString());
1906
- child.stderr.on("data", (chunk) => stderr += chunk.toString());
1907
- child.on("error", reject);
1908
- child.on("close", (code) => resolvePromise({ code: code ?? 1, stdout, stderr }));
1909
- child.stdin.end(opts?.input ?? "");
1910
- });
1911
- var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
1912
- function findWranglerConfig(rootDir) {
1913
- for (const name of WRANGLER_CONFIG_FILES) {
1914
- const path = (0, import_node_path4.join)(rootDir, name);
1915
- if ((0, import_node_fs6.existsSync)(path)) return path;
1996
+
1997
+ // ../harness/dist/chunk-UG5XHNFB.js
1998
+ var HARNESS_PROTOCOL_VERSION = 1;
1999
+
2000
+ // ../harness/dist/chunk-QALIIZRJ.js
2001
+ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
2002
+ var HarnessProtocolError = class extends Error {
2003
+ name = "HarnessProtocolError";
2004
+ };
2005
+ function record2(value) {
2006
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2007
+ }
2008
+ function boundedText(value, label, max) {
2009
+ if (typeof value !== "string" || !value || value.length > max || CONTROL.test(value)) {
2010
+ throw new HarnessProtocolError(`${label} must be a non-empty string of at most ${max} characters`);
1916
2011
  }
1917
- return null;
2012
+ return value;
1918
2013
  }
1919
- function readWranglerConfig(path) {
1920
- if (path.endsWith(".toml")) return null;
2014
+ function parseAgentOutput(line) {
2015
+ if (Buffer.byteLength(line, "utf8") > 1e6) throw new HarnessProtocolError("agent message exceeds 1 MB");
2016
+ let value;
1921
2017
  try {
1922
- return JSON.parse(stripJsonComments((0, import_node_fs6.readFileSync)(path, "utf8")));
2018
+ value = JSON.parse(line);
1923
2019
  } catch {
1924
- return null;
2020
+ throw new HarnessProtocolError("agent emitted invalid JSON");
1925
2021
  }
1926
- }
1927
- function stripJsonComments(text) {
1928
- let result = "";
1929
- let inString = false;
1930
- for (let i = 0; i < text.length; i++) {
1931
- const ch = text[i];
1932
- if (inString) {
1933
- result += ch;
1934
- if (ch === "\\") {
1935
- result += text[i + 1] ?? "";
1936
- i++;
1937
- } else if (ch === '"') {
1938
- inString = false;
1939
- }
1940
- continue;
1941
- }
1942
- if (ch === '"') {
1943
- inString = true;
1944
- result += ch;
1945
- continue;
1946
- }
1947
- if (ch === "/" && text[i + 1] === "/") {
1948
- while (i < text.length && text[i] !== "\n") i++;
1949
- result += "\n";
1950
- continue;
2022
+ const message3 = record2(value);
2023
+ if (!message3 || message3.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
2024
+ throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
2025
+ }
2026
+ if (message3.type === "event") {
2027
+ return {
2028
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
2029
+ type: "event",
2030
+ kind: boundedText(message3.kind, "event.kind", 120),
2031
+ ...message3.payload === void 0 ? {} : { payload: message3.payload }
2032
+ };
2033
+ }
2034
+ if (message3.type === "inference.request") {
2035
+ const call = record2(message3.call);
2036
+ if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
2037
+ throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
1951
2038
  }
1952
- if (ch === "/" && text[i + 1] === "*") {
1953
- i += 2;
1954
- while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
1955
- i++;
1956
- continue;
2039
+ return {
2040
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
2041
+ type: "inference.request",
2042
+ requestId: boundedText(message3.requestId, "requestId", 180),
2043
+ call
2044
+ };
2045
+ }
2046
+ if (message3.type === "tool.request") {
2047
+ const input = record2(message3.input);
2048
+ const tool = String(message3.tool);
2049
+ if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
2050
+ throw new HarnessProtocolError("tool.request requires a registered tool and object input");
1957
2051
  }
1958
- result += ch;
2052
+ return {
2053
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
2054
+ type: "tool.request",
2055
+ requestId: boundedText(message3.requestId, "requestId", 180),
2056
+ tool,
2057
+ input
2058
+ };
1959
2059
  }
1960
- return result;
1961
- }
1962
- async function wranglerLoggedIn(run, cwd) {
1963
- try {
1964
- const result = await run("npx", ["wrangler", "whoami"], { cwd });
1965
- return result.code === 0 && !/not authenticated/i.test(`${result.stdout}${result.stderr}`);
1966
- } catch {
1967
- return false;
2060
+ if (message3.type === "attempt.complete") {
2061
+ if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message3.status))) {
2062
+ throw new HarnessProtocolError("attempt.complete.status is invalid");
2063
+ }
2064
+ return {
2065
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
2066
+ type: "attempt.complete",
2067
+ status: message3.status,
2068
+ ...message3.result === void 0 ? {} : { result: message3.result }
2069
+ };
1968
2070
  }
2071
+ throw new HarnessProtocolError("agent message type is unsupported");
1969
2072
  }
1970
- function wranglerPutSecret(run, opts) {
1971
- const args = ["wrangler", "secret", "put", opts.name, ...opts.env ? ["--env", opts.env] : []];
1972
- return run("npx", args, { input: opts.value, cwd: opts.cwd });
2073
+ function encodeAgentInput(message3) {
2074
+ return `${JSON.stringify(message3)}
2075
+ `;
1973
2076
  }
1974
2077
 
1975
- // src/doctor-checks.ts
1976
- function lintRules(rules, entities, publicRead) {
1977
- const warnings = [];
1978
- if (!rules) return warnings;
1979
- for (const [ns, actions] of Object.entries(rules)) {
1980
- for (const [action, expr] of Object.entries(actions)) {
1981
- if (typeof expr !== "string" || expr.trim() !== "true") continue;
1982
- if (action === "view" && publicRead.includes(ns)) continue;
1983
- warnings.push(
1984
- 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`
1985
- );
2078
+ // ../harness/dist/chunk-FNYUIJUN.js
2079
+ var import_child_process = require("child_process");
2080
+ var import_fs = require("fs");
2081
+ var import_promises2 = require("fs/promises");
2082
+ var import_path = require("path");
2083
+ var import_process = require("process");
2084
+ var import_promises3 = require("fs/promises");
2085
+ var import_os = require("os");
2086
+ var import_path2 = require("path");
2087
+ var import_child_process2 = require("child_process");
2088
+ var DIGEST_IMAGE = /^[a-z0-9][a-z0-9._/-]*(?::[a-zA-Z0-9._-]+)?@sha256:[0-9a-f]{64}$/;
2089
+ function assertPinnedImage(image) {
2090
+ if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
2091
+ }
2092
+ async function commandAvailable(engine) {
2093
+ for (const directory of (process.env.PATH ?? "").split(import_path.delimiter).filter(Boolean)) {
2094
+ try {
2095
+ await (0, import_promises2.access)((0, import_path.join)(directory, engine), import_fs.constants.X_OK);
2096
+ return true;
2097
+ } catch {
1986
2098
  }
1987
2099
  }
1988
- for (const entity of entities) {
1989
- if (!(entity in rules)) warnings.push(`schema entity "${entity}" has no rules entry (all client access denied)`);
1990
- }
1991
- return warnings;
2100
+ return false;
1992
2101
  }
1993
- var defaultExec = (cmd, args, cwd) => (0, import_node_child_process3.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
1994
- function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
1995
- let output;
1996
- try {
1997
- const custom = localPaths.map((path) => gitignoreEntry(rootDir, path)).filter((path) => !!path);
1998
- output = exec("git", ["ls-files", "--", ".dev.vars", ".odla", ...custom], rootDir);
1999
- } catch {
2000
- return [];
2102
+ async function selectContainerEngine(requested = "auto", options = {}) {
2103
+ const platform = options.platform ?? process.platform;
2104
+ const arch = options.arch ?? process.arch;
2105
+ const uid = options.uid ?? (typeof import_process.getuid === "function" ? (0, import_process.getuid)() : 1e3);
2106
+ const available = options.available ?? commandAvailable;
2107
+ const validate3 = async (engine) => {
2108
+ if (engine === "container" && (platform !== "darwin" || arch !== "arm64")) {
2109
+ throw new TypeError("Apple container requires Apple Silicon macOS");
2110
+ }
2111
+ if (engine === "podman" && platform === "linux" && uid === 0) {
2112
+ throw new TypeError("the Linux harness requires rootless Podman; do not run the runner as root");
2113
+ }
2114
+ if (!await available(engine)) throw new TypeError(`${engine} is not installed or executable`);
2115
+ return engine;
2116
+ };
2117
+ if (requested !== "auto") return validate3(requested);
2118
+ const candidates = platform === "darwin" ? arch === "arm64" ? ["container", "podman"] : ["podman"] : platform === "linux" ? ["podman"] : [];
2119
+ for (const engine of candidates) {
2120
+ if (await available(engine)) return validate3(engine);
2121
+ }
2122
+ if (platform === "linux") {
2123
+ throw new TypeError("no rootless Podman found; install Podman or explicitly choose --engine docker after reviewing its daemon boundary");
2124
+ }
2125
+ if (platform === "darwin") {
2126
+ throw new TypeError("no Apple container or Podman Machine found; install one or explicitly choose --engine docker");
2127
+ }
2128
+ throw new TypeError("no supported container engine found");
2129
+ }
2130
+ function inspectRootlessPodman() {
2131
+ return new Promise((resolve23, reject) => {
2132
+ (0, import_child_process.execFile)(
2133
+ "podman",
2134
+ ["info", "--format", "{{.Host.Security.Rootless}}"],
2135
+ { encoding: "utf8", maxBuffer: 16 * 1024, timeout: 1e4 },
2136
+ (error, stdout) => {
2137
+ if (error) {
2138
+ reject(new TypeError("could not verify that the active Podman service is rootless"));
2139
+ return;
2140
+ }
2141
+ resolve23(stdout.trim() === "true");
2142
+ }
2143
+ );
2144
+ });
2145
+ }
2146
+ async function verifyContainerEngineBoundary(engine, options = {}) {
2147
+ const platform = options.platform ?? process.platform;
2148
+ const arch = options.arch ?? process.arch;
2149
+ const uid = options.uid ?? (typeof import_process.getuid === "function" ? (0, import_process.getuid)() : 1e3);
2150
+ if (engine === "container" && (platform !== "darwin" || arch !== "arm64")) {
2151
+ throw new TypeError("Apple container requires Apple Silicon macOS");
2152
+ }
2153
+ if (engine !== "podman" || platform !== "linux") return;
2154
+ if (uid === 0) throw new TypeError("the Linux harness requires rootless Podman; do not run the runner as root");
2155
+ const rootless = await (options.podmanRootless ?? inspectRootlessPodman)();
2156
+ if (!rootless) throw new TypeError("the active Podman service is not rootless; refusing to run the harness");
2157
+ }
2158
+ function buildContainerRunArgs(options) {
2159
+ if (!options.allowUnpinnedImage) assertPinnedImage(options.image);
2160
+ if (/[,\r\n]/.test(options.workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
2161
+ const uid = typeof import_process.getuid === "function" ? (0, import_process.getuid)() : 1e3;
2162
+ const gid = typeof import_process.getgid === "function" ? (0, import_process.getgid)() : 1e3;
2163
+ const safeAttempt = options.task.attemptId.toLowerCase().replace(/[^a-z0-9_.-]/g, "-").slice(0, 40);
2164
+ const name = `odla-harness-${safeAttempt}-${crypto.randomUUID().slice(0, 8)}`;
2165
+ const limits = options.limits ?? {};
2166
+ const access2 = options.workspaceAccess ?? "read-write";
2167
+ const appleMount = access2 === "none" ? [] : [`--mount=type=bind,source=${options.workspaceDir},target=/workspace${access2 === "read-only" ? ",readonly" : ""}`];
2168
+ const ociMount = access2 === "none" ? [] : [`--mount=type=bind,src=${options.workspaceDir},dst=/workspace${access2 === "read-only" ? ",readonly" : ""}`];
2169
+ if (options.engine === "container") {
2170
+ return [
2171
+ "run",
2172
+ "--rm",
2173
+ "--interactive",
2174
+ `--name=${name}`,
2175
+ "--network=none",
2176
+ "--read-only",
2177
+ "--cap-drop=ALL",
2178
+ `--memory=${limits.memory ?? "1g"}`,
2179
+ `--cpus=${limits.cpus ?? 1}`,
2180
+ `--user=${uid}:${gid}`,
2181
+ "--tmpfs=/tmp",
2182
+ ...appleMount,
2183
+ "--workdir=/workspace",
2184
+ `--env=ODLA_HARNESS_PROTOCOL=${HARNESS_PROTOCOL_VERSION}`,
2185
+ `--label=ai.odla.harness.attempt=${options.task.attemptId}`,
2186
+ options.image
2187
+ ];
2001
2188
  }
2002
- 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`);
2189
+ return [
2190
+ "run",
2191
+ "--rm",
2192
+ "--interactive",
2193
+ `--name=${name}`,
2194
+ "--pull=never",
2195
+ "--network=none",
2196
+ "--read-only",
2197
+ "--cap-drop=ALL",
2198
+ "--security-opt=no-new-privileges",
2199
+ `--pids-limit=${limits.pids ?? 256}`,
2200
+ `--memory=${limits.memory ?? "1g"}`,
2201
+ `--cpus=${limits.cpus ?? 1}`,
2202
+ `--user=${uid}:${gid}`,
2203
+ `--tmpfs=/tmp:rw,noexec,nosuid,nodev,size=${limits.tmpfsBytes ?? 64 * 1024 * 1024}`,
2204
+ ...ociMount,
2205
+ "--workdir=/workspace",
2206
+ `--env=ODLA_HARNESS_PROTOCOL=${HARNESS_PROTOCOL_VERSION}`,
2207
+ `--label=ai.odla.harness.attempt=${options.task.attemptId}`,
2208
+ options.image
2209
+ ];
2003
2210
  }
2004
- function wranglerWarnings(rootDir) {
2005
- const configPath = findWranglerConfig(rootDir);
2006
- if (!configPath) return [];
2007
- const config = readWranglerConfig(configPath);
2008
- if (!config) return [];
2009
- const warnings = [];
2010
- const blocks = [{ label: "", block: config }];
2011
- const envs = config.env;
2012
- if (envs && typeof envs === "object") {
2013
- for (const [name, block] of Object.entries(envs)) {
2014
- if (block && typeof block === "object") blocks.push({ label: `env.${name}.`, block });
2211
+ function containerName(args) {
2212
+ return args.find((arg) => arg.startsWith("--name=")).slice("--name=".length);
2213
+ }
2214
+ async function runContainerAttempt(options) {
2215
+ if (options.signal?.aborted) return { exitCode: 1, status: "cancelled", stderr: "" };
2216
+ await verifyContainerEngineBoundary(options.engine);
2217
+ const args = buildContainerRunArgs(options);
2218
+ const name = containerName(args);
2219
+ const child = (0, import_child_process.spawn)(options.engine, args, { stdio: ["pipe", "pipe", "pipe"], shell: false });
2220
+ let stderr = "";
2221
+ let outputBytes = 0;
2222
+ let complete = null;
2223
+ let stopped = false;
2224
+ let exited = false;
2225
+ child.stderr.setEncoding("utf8");
2226
+ child.stderr.on("data", (text) => {
2227
+ if (stderr.length < 64 * 1024) stderr += text.slice(0, 64 * 1024 - stderr.length);
2228
+ });
2229
+ const stop = (reason) => {
2230
+ if (stopped || exited) return;
2231
+ stopped = true;
2232
+ if (!child.stdin.destroyed) {
2233
+ const cancel = { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "attempt.cancel", reason };
2234
+ child.stdin.write(encodeAgentInput(cancel));
2015
2235
  }
2016
- }
2017
- for (const { label, block } of blocks) {
2018
- const assets = block.assets;
2019
- if (assets?.directory) {
2020
- const dir = (0, import_node_path5.resolve)(rootDir, assets.directory);
2021
- if (dir === (0, import_node_path5.resolve)(rootDir)) {
2022
- warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
2023
- } else if ((0, import_node_fs7.existsSync)((0, import_node_path5.join)(dir, "node_modules"))) {
2024
- warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
2236
+ const removeArgs = options.engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
2237
+ const killer = (0, import_child_process.spawn)(options.engine, removeArgs, { stdio: "ignore", shell: false });
2238
+ killer.unref();
2239
+ };
2240
+ const abort = () => stop("runner_cancelled");
2241
+ options.signal?.addEventListener("abort", abort, { once: true });
2242
+ const timeout = setTimeout(() => stop("timeout"), options.task.policy.timeoutMs);
2243
+ const start = { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "task.start", task: options.task };
2244
+ if (!stopped && !options.signal?.aborted) child.stdin.write(encodeAgentInput(start));
2245
+ const consume = (async () => {
2246
+ let pending = Buffer.alloc(0);
2247
+ const handleLine = async (raw) => {
2248
+ const bytes = raw.at(-1) === 13 ? raw.subarray(0, -1) : raw;
2249
+ if (bytes.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
2250
+ const line = bytes.toString("utf8");
2251
+ if (!line.trim()) return;
2252
+ const message3 = parseAgentOutput(line);
2253
+ if (message3.type === "attempt.complete") complete = message3;
2254
+ const response2 = await options.onMessage(message3);
2255
+ if (response2 && !child.stdin.destroyed) child.stdin.write(encodeAgentInput(response2));
2256
+ };
2257
+ try {
2258
+ for await (const raw of child.stdout) {
2259
+ const chunk = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
2260
+ outputBytes += chunk.byteLength;
2261
+ if (outputBytes > options.task.policy.maxOutputBytes) {
2262
+ throw new Error(`agent output exceeds ${options.task.policy.maxOutputBytes} bytes`);
2263
+ }
2264
+ pending = Buffer.concat([pending, chunk]);
2265
+ let newline = pending.indexOf(10);
2266
+ while (newline >= 0) {
2267
+ await handleLine(pending.subarray(0, newline));
2268
+ pending = pending.subarray(newline + 1);
2269
+ newline = pending.indexOf(10);
2270
+ }
2271
+ if (pending.byteLength > 1e6) throw new Error("agent message exceeds 1 MB");
2025
2272
  }
2273
+ if (pending.byteLength) await handleLine(pending);
2274
+ } catch (error) {
2275
+ stop("protocol_error");
2276
+ throw error;
2026
2277
  }
2027
- const vars = block.vars;
2028
- if (vars && typeof vars === "object") {
2029
- for (const [name, value] of Object.entries(vars)) {
2030
- if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value === "string" && looksSecret(value)) {
2031
- warnings.push(`wrangler ${label}vars.${name} looks like a secret \u2014 use "odla-ai secrets push" / wrangler secret put, never vars`);
2032
- }
2278
+ })();
2279
+ const exit = new Promise((accept, reject) => {
2280
+ child.once("error", reject);
2281
+ child.once("exit", (code) => {
2282
+ exited = true;
2283
+ accept(code ?? 1);
2284
+ });
2285
+ });
2286
+ try {
2287
+ const [exitCode] = await Promise.all([exit, consume]);
2288
+ if (stderr && options.onStderr) await options.onStderr(stderr);
2289
+ if (options.signal?.aborted) return { exitCode, status: "cancelled", stderr };
2290
+ const terminal = complete;
2291
+ if (!terminal) return { exitCode, status: "failed", result: { error: "agent exited without completion" }, stderr };
2292
+ return { exitCode, status: exitCode === 0 ? terminal.status : "failed", result: terminal.result, stderr };
2293
+ } catch (error) {
2294
+ stop("runner_error");
2295
+ await exit.catch(() => 1);
2296
+ throw error;
2297
+ } finally {
2298
+ clearTimeout(timeout);
2299
+ options.signal?.removeEventListener("abort", abort);
2300
+ }
2301
+ }
2302
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
2303
+ var SECRET_FILE = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
2304
+ async function sourceFiles(sourceDir, maxFiles, maxBytes) {
2305
+ const files = [];
2306
+ let bytes = 0;
2307
+ const walk = async (dir) => {
2308
+ for (const entry of await (0, import_promises3.readdir)(dir, { withFileTypes: true })) {
2309
+ if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
2310
+ if (!entry.isDirectory() && SECRET_FILE.test(entry.name)) continue;
2311
+ const path = (0, import_path2.join)(dir, entry.name);
2312
+ if (entry.isSymbolicLink()) continue;
2313
+ if (entry.isDirectory()) {
2314
+ await walk(path);
2315
+ continue;
2033
2316
  }
2317
+ if (!entry.isFile()) continue;
2318
+ const metadata2 = await (0, import_promises3.stat)(path);
2319
+ bytes += metadata2.size;
2320
+ if (files.length + 1 > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
2321
+ if (bytes > maxBytes) throw new Error(`workspace exceeds ${maxBytes} bytes`);
2322
+ files.push({
2323
+ source: path,
2324
+ relativePath: (0, import_path2.relative)(sourceDir, path),
2325
+ mode: metadata2.mode & 511,
2326
+ bytes: metadata2.size
2327
+ });
2034
2328
  }
2329
+ };
2330
+ await walk(sourceDir);
2331
+ return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
2332
+ }
2333
+ async function copyTree(files, destination) {
2334
+ for (const file of files) {
2335
+ const target = (0, import_path2.join)(destination, file.relativePath);
2336
+ await (0, import_promises3.mkdir)((0, import_path2.resolve)(target, ".."), { recursive: true });
2337
+ await (0, import_promises3.copyFile)(file.source, target);
2338
+ await (0, import_promises3.chmod)(target, file.mode);
2339
+ }
2340
+ }
2341
+ async function captureGitDiff(root, maxBytes) {
2342
+ const child = (0, import_child_process2.spawn)("git", [
2343
+ "diff",
2344
+ "--no-index",
2345
+ "--binary",
2346
+ "--no-ext-diff",
2347
+ "--src-prefix=a/",
2348
+ "--dst-prefix=b/",
2349
+ "--",
2350
+ "baseline",
2351
+ "workspace"
2352
+ ], { cwd: root, stdio: ["ignore", "pipe", "pipe"], shell: false });
2353
+ const stdout = [];
2354
+ const stderr = [];
2355
+ let bytes = 0;
2356
+ child.stdout.on("data", (chunk) => {
2357
+ bytes += chunk.byteLength;
2358
+ if (bytes > maxBytes) child.kill("SIGKILL");
2359
+ else stdout.push(chunk);
2360
+ });
2361
+ child.stderr.on("data", (chunk) => {
2362
+ if (stderr.reduce((sum, value) => sum + value.byteLength, 0) < 16384) stderr.push(chunk);
2363
+ });
2364
+ const code = await new Promise((accept, reject) => {
2365
+ child.once("error", reject);
2366
+ child.once("exit", accept);
2367
+ });
2368
+ if (bytes > maxBytes) throw new Error(`patch exceeds ${maxBytes} bytes`);
2369
+ if (code !== 0 && code !== 1) {
2370
+ throw new Error(`git diff failed: ${Buffer.concat(stderr).toString("utf8").slice(0, 1e3)}`);
2371
+ }
2372
+ return Buffer.concat(stdout).toString("utf8").replaceAll("a/baseline/", "a/").replaceAll("b/workspace/", "b/").replaceAll("--- a/baseline", "--- a").replaceAll("+++ b/workspace", "+++ b");
2373
+ }
2374
+ async function stageWorkspace(source, options = {}) {
2375
+ const sourceDir = await (0, import_promises3.realpath)((0, import_path2.resolve)(source));
2376
+ const sourceStat = await (0, import_promises3.stat)(sourceDir);
2377
+ if (!sourceStat.isDirectory()) throw new TypeError("workspace source must be a directory");
2378
+ const root = await (0, import_promises3.mkdtemp)((0, import_path2.join)(options.tempRoot ?? (0, import_os.tmpdir)(), "odla-harness-"));
2379
+ const baselineDir = (0, import_path2.join)(root, "baseline");
2380
+ const workspaceDir = (0, import_path2.join)(root, "workspace");
2381
+ await Promise.all([(0, import_promises3.mkdir)(baselineDir), (0, import_promises3.mkdir)(workspaceDir)]);
2382
+ try {
2383
+ const files = await sourceFiles(sourceDir, options.maxFiles ?? 2e4, options.maxBytes ?? 512 * 1024 * 1024);
2384
+ await Promise.all([copyTree(files, baselineDir), copyTree(files, workspaceDir)]);
2385
+ return {
2386
+ root,
2387
+ baselineDir,
2388
+ workspaceDir,
2389
+ fileCount: files.length,
2390
+ byteCount: files.reduce((sum, file) => sum + file.bytes, 0),
2391
+ patch: (maxBytes) => captureGitDiff(root, maxBytes),
2392
+ cleanup: () => (0, import_promises3.rm)(root, { recursive: true, force: true })
2393
+ };
2394
+ } catch (error) {
2395
+ await (0, import_promises3.rm)(root, { recursive: true, force: true });
2396
+ throw error;
2035
2397
  }
2036
- const pkg = readPackageJson(rootDir);
2037
- if (pkg && typeof pkg.scripts === "object" && pkg.scripts && "deploy" in pkg.scripts) {
2038
- warnings.push(`package.json has a script named "deploy" \u2014 CI may auto-deploy it; rename to deploy:app`);
2039
- }
2040
- return warnings;
2041
2398
  }
2042
- function o11yProjectWarnings(rootDir) {
2043
- const warnings = [];
2044
- const pkg = readPackageJson(rootDir);
2045
- const dependencies = pkg?.dependencies;
2046
- const devDependencies = pkg?.devDependencies;
2047
- if (!dependencies?.["@odla-ai/o11y"]) {
2048
- warnings.push(
2049
- 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"'
2050
- );
2399
+
2400
+ // ../camel/dist/chunk-7FHPOQVP.js
2401
+ var CamelError = class extends Error {
2402
+ code;
2403
+ safeMessage;
2404
+ constructor(code, safeMessage, options) {
2405
+ super(safeMessage, options);
2406
+ this.name = "CamelError";
2407
+ this.code = code;
2408
+ this.safeMessage = safeMessage;
2051
2409
  }
2052
- const configPath = findWranglerConfig(rootDir);
2053
- const config = configPath ? readWranglerConfig(configPath) : null;
2054
- if (!configPath || !config) {
2055
- warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
2056
- return warnings;
2410
+ };
2411
+
2412
+ // ../camel/dist/chunk-L5DYU2E2.js
2413
+ function canonicalJson(value) {
2414
+ return JSON.stringify(normalize(value));
2415
+ }
2416
+ async function sha256Hex(value) {
2417
+ const bytes = typeof value === "string" ? new TextEncoder().encode(value) : value;
2418
+ const digest2 = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer);
2419
+ return [...new Uint8Array(digest2)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2420
+ }
2421
+ function utf8Length(value) {
2422
+ if (typeof value === "string") return new TextEncoder().encode(value).byteLength;
2423
+ if (value instanceof Uint8Array) return value.byteLength;
2424
+ try {
2425
+ return new TextEncoder().encode(canonicalJson(value)).byteLength;
2426
+ } catch {
2427
+ throw new CamelError("conversion_rejected", "Unsafe input could not be canonically measured.");
2057
2428
  }
2058
- const main = typeof config.main === "string" ? (0, import_node_path5.resolve)(rootDir, config.main) : null;
2059
- if (!main || !(0, import_node_fs7.existsSync)(main)) {
2060
- warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
2061
- } else {
2062
- let source = "";
2063
- try {
2064
- source = (0, import_node_fs7.readFileSync)(main, "utf8");
2065
- } catch {
2066
- }
2067
- if (!/\bwithObservability\b/.test(source)) {
2068
- warnings.push(`wrangler entrypoint ${config.main} does not reference withObservability`);
2069
- }
2429
+ }
2430
+ function normalize(value) {
2431
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
2432
+ if (typeof value === "number") {
2433
+ if (!Number.isFinite(value)) throw new CamelError("state_conflict", "Canonical JSON rejects non-finite numbers.");
2434
+ return value;
2070
2435
  }
2071
- const flags = Array.isArray(config.compatibility_flags) ? config.compatibility_flags : [];
2072
- if (!flags.includes("nodejs_compat")) {
2073
- warnings.push('wrangler compatibility_flags is missing "nodejs_compat" required by @odla-ai/o11y');
2436
+ if (Array.isArray(value)) return value.map(normalize);
2437
+ if (value instanceof Uint8Array) return { $bytes: [...value] };
2438
+ if (typeof value === "object") {
2439
+ const record5 = value;
2440
+ return Object.fromEntries(Object.keys(record5).filter((key) => record5[key] !== void 0).sort().map((key) => [key, normalize(record5[key])]));
2074
2441
  }
2075
- return warnings;
2442
+ throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
2076
2443
  }
2077
- function calendarProjectWarnings(rootDir) {
2444
+ function canRead(readers, readerId) {
2445
+ return readers.kind === "public" || readers.principalIds.includes(readerId);
2446
+ }
2447
+ function dependenciesOf(values, influence = "data") {
2448
+ const result = [];
2449
+ for (const value of values) {
2450
+ result.push(...value.label.dependencies);
2451
+ for (const ref of value.label.provenance) {
2452
+ result.push({ ref, influence, promptSafetyAtUse: value.label.promptSafety });
2453
+ }
2454
+ }
2455
+ const unique3 = /* @__PURE__ */ new Map();
2456
+ for (const dep of result) unique3.set(`${dep.ref.kind}\0${dep.ref.id}\0${dep.influence}\0${dep.promptSafetyAtUse}`, dep);
2457
+ return [...unique3.values()];
2458
+ }
2459
+
2460
+ // ../camel/dist/chunk-S7EVNA2U.js
2461
+ var camelValueBrand = /* @__PURE__ */ Symbol("@odla-ai/camel/value");
2462
+ var authenticCamelValues = /* @__PURE__ */ new WeakSet();
2463
+ function isCamelValue(value) {
2464
+ return typeof value === "object" && value !== null && authenticCamelValues.has(value);
2465
+ }
2466
+ function isSafe(value) {
2467
+ return isCamelValue(value) && value.label.promptSafety === "safe";
2468
+ }
2469
+ function isUnsafe(value) {
2470
+ return isCamelValue(value) && value.label.promptSafety === "unsafe";
2471
+ }
2472
+ function createSafeInternal(value, safeBasis, metadata2) {
2473
+ return createValue(value, {
2474
+ schemaVersion: 1,
2475
+ promptSafety: "safe",
2476
+ safeBasis,
2477
+ ...copyMetadata(metadata2)
2478
+ });
2479
+ }
2480
+ function createUnsafeInternal(value, metadata2) {
2481
+ return createValue(value, {
2482
+ schemaVersion: 1,
2483
+ promptSafety: "unsafe",
2484
+ ...copyMetadata(metadata2)
2485
+ });
2486
+ }
2487
+ function createValue(value, label) {
2488
+ const result = { value, label: Object.freeze(label) };
2489
+ Object.defineProperty(result, camelValueBrand, { value: label.promptSafety, enumerable: false });
2490
+ authenticCamelValues.add(result);
2491
+ return Object.freeze(result);
2492
+ }
2493
+ function copyMetadata(metadata2) {
2494
+ if (metadata2.provenance.length === 0) throw new CamelError("state_conflict", "Label provenance must be non-empty.");
2495
+ const provenance = metadata2.provenance.map(copyRef);
2496
+ const dependencies = (metadata2.dependencies ?? []).map((dependency) => Object.freeze({
2497
+ ref: copyRef(dependency.ref),
2498
+ influence: dependency.influence,
2499
+ promptSafetyAtUse: dependency.promptSafetyAtUse
2500
+ }));
2501
+ return {
2502
+ readers: normalizeReaders(metadata2.readers),
2503
+ provenance: Object.freeze(provenance),
2504
+ dependencies: Object.freeze(dependencies)
2505
+ };
2506
+ }
2507
+ function copyRef(ref) {
2508
+ if (!ref.id || ref.digest !== void 0 && !ref.digest) throw new CamelError("state_conflict", "Provenance references must have non-empty identities.");
2509
+ return Object.freeze({ kind: ref.kind, id: ref.id, ...ref.digest === void 0 ? {} : { digest: ref.digest } });
2510
+ }
2511
+ function normalizeReaders(readers) {
2512
+ if (readers.kind === "public") return Object.freeze({ kind: "public" });
2513
+ const principalIds = [...new Set(readers.principalIds)].sort();
2514
+ if (principalIds.some((id) => !id)) throw new CamelError("reader_mismatch", "Reader principal IDs must be non-empty.");
2515
+ return Object.freeze({ kind: "principals", principalIds: Object.freeze(principalIds) });
2516
+ }
2517
+
2518
+ // ../camel/dist/code.js
2519
+ var DIGEST = /^sha256:[0-9a-f]{64}$/;
2520
+ var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2521
+ var ID = /^[A-Za-z0-9._:-]{1,160}$/;
2522
+ async function digestCodeVerificationReceipt(fields) {
2523
+ validate(fields);
2524
+ const canonical = {
2525
+ schemaVersion: fields.schemaVersion,
2526
+ verificationId: fields.verificationId,
2527
+ trustedBaseCommitSha: fields.trustedBaseCommitSha,
2528
+ trustedBaseDigest: fields.trustedBaseDigest,
2529
+ patchDigest: fields.patchDigest,
2530
+ candidateDigest: fields.candidateDigest,
2531
+ sourceDigest: fields.sourceDigest,
2532
+ policyDigest: fields.policyDigest,
2533
+ recipes: fields.recipes.map((recipe2) => ({
2534
+ recipeId: recipe2.recipeId,
2535
+ recipeDigest: recipe2.recipeDigest,
2536
+ status: recipe2.status,
2537
+ exitCode: recipe2.exitCode,
2538
+ durationMs: recipe2.durationMs,
2539
+ artifacts: recipe2.artifacts.map((artifact) => ({
2540
+ artifactId: artifact.artifactId,
2541
+ status: artifact.status,
2542
+ bytes: artifact.bytes,
2543
+ digest: artifact.digest
2544
+ }))
2545
+ })),
2546
+ changedTestCount: fields.changedTestCount,
2547
+ changedTestSetDigest: fields.changedTestSetDigest,
2548
+ changedTestsRequireReview: fields.changedTestsRequireReview,
2549
+ outcome: fields.outcome
2550
+ };
2551
+ return `sha256:${await sha256Hex(canonicalJson(canonical))}`;
2552
+ }
2553
+ function validate(fields) {
2554
+ 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) {
2555
+ throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
2556
+ }
2557
+ const ids = /* @__PURE__ */ new Set();
2558
+ for (const recipe2 of fields.recipes) {
2559
+ 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) {
2560
+ throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
2561
+ }
2562
+ const artifactIds = /* @__PURE__ */ new Set();
2563
+ if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
2564
+ for (const artifact of recipe2.artifacts) {
2565
+ 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)) {
2566
+ throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
2567
+ }
2568
+ artifactIds.add(artifact.artifactId);
2569
+ }
2570
+ if (recipe2.status === "passed" && (recipe2.exitCode !== 0 || recipe2.artifacts.some((artifact) => artifact.status !== "verified"))) {
2571
+ throw new CamelError("state_conflict", "A passing recipe must verify every registered artifact.");
2572
+ }
2573
+ ids.add(recipe2.recipeId);
2574
+ }
2575
+ const passed = fields.recipes.every((recipe2) => recipe2.status === "passed");
2576
+ if (fields.outcome === "passed" !== passed) {
2577
+ throw new CamelError("state_conflict", "Code verification outcome does not match its recipe receipts.");
2578
+ }
2579
+ }
2580
+ var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2581
+ var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
2582
+ var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
2583
+ var MAX_PATCH_BYTES = 256 * 1024;
2584
+ var MAX_STATE_BYTES = 64 * 1024;
2585
+ async function createCodePortableCheckpoint(input) {
2586
+ const state = normalizeState(input.state);
2587
+ validateBaseAndPatch(input.baseCommitSha, input.patch);
2588
+ const patchDigest = `sha256:${await sha256Hex(input.patch)}`;
2589
+ const stateDigest = `sha256:${await sha256Hex(canonicalJson(state))}`;
2590
+ const fields = { schemaVersion: 1, baseCommitSha: input.baseCommitSha, patchDigest, state, stateDigest };
2591
+ const checkpointDigest = `sha256:${await sha256Hex(canonicalJson(fields))}`;
2592
+ return freeze({ ...fields, patch: input.patch, checkpointDigest });
2593
+ }
2594
+ async function verifyCodePortableCheckpoint(value) {
2595
+ const input = object(value, "checkpoint");
2596
+ exact2(input, ["schemaVersion", "baseCommitSha", "patch", "patchDigest", "state", "stateDigest", "checkpointDigest"]);
2597
+ if (input.schemaVersion !== 1 || typeof input.baseCommitSha !== "string" || typeof input.patch !== "string" || typeof input.patchDigest !== "string" || typeof input.stateDigest !== "string" || typeof input.checkpointDigest !== "string") {
2598
+ throw invalid2("Portable checkpoint fields are malformed.");
2599
+ }
2600
+ const created = await createCodePortableCheckpoint({
2601
+ baseCommitSha: input.baseCommitSha,
2602
+ patch: input.patch,
2603
+ state: normalizeState(input.state)
2604
+ });
2605
+ if (created.patchDigest !== input.patchDigest || created.stateDigest !== input.stateDigest || created.checkpointDigest !== input.checkpointDigest) {
2606
+ throw invalid2("Portable checkpoint digest does not match its content.");
2607
+ }
2608
+ return created;
2609
+ }
2610
+ function normalizeState(value) {
2611
+ const state = object(value, "state");
2612
+ exact2(state, [
2613
+ "planCursor",
2614
+ "conversationRefs",
2615
+ "planningInputDigest",
2616
+ "buildPolicyDigest",
2617
+ "dependencyLayerDigest",
2618
+ "verificationDigest",
2619
+ "reviewDigest",
2620
+ "completedEffects",
2621
+ "unresolvedApprovals",
2622
+ "trustStatus"
2623
+ ]);
2624
+ const planCursor = state.planCursor;
2625
+ const conversations = strings(state.conversationRefs, "conversationRefs", ID2, 256, false);
2626
+ const approvals = strings(state.unresolvedApprovals, "unresolvedApprovals", DIGEST2, 256, true);
2627
+ 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) {
2628
+ throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
2629
+ }
2630
+ const effects = state.completedEffects.map((item) => {
2631
+ const effect = object(item, "completed effect");
2632
+ exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
2633
+ 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)) {
2634
+ throw invalid2("Portable checkpoint effect receipt is malformed.");
2635
+ }
2636
+ return {
2637
+ effectId: effect.effectId,
2638
+ actionDigest: effect.actionDigest,
2639
+ receiptDigest: effect.receiptDigest
2640
+ };
2641
+ });
2642
+ if (new Set(effects.map((item) => item.effectId)).size !== effects.length) {
2643
+ throw invalid2("Portable checkpoint repeats a completed effect.");
2644
+ }
2645
+ const trustStatus = state.trustStatus;
2646
+ 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)) {
2647
+ throw invalid2("Portable checkpoint trust status does not match its evidence digests.");
2648
+ }
2649
+ const normalized = {
2650
+ planCursor,
2651
+ conversationRefs: conversations,
2652
+ planningInputDigest: state.planningInputDigest,
2653
+ buildPolicyDigest: state.buildPolicyDigest,
2654
+ dependencyLayerDigest: state.dependencyLayerDigest,
2655
+ verificationDigest: state.verificationDigest,
2656
+ reviewDigest: state.reviewDigest,
2657
+ completedEffects: effects,
2658
+ unresolvedApprovals: approvals,
2659
+ trustStatus
2660
+ };
2661
+ if (utf8Length(normalized) > MAX_STATE_BYTES) throw invalid2("Portable checkpoint state exceeds 64 KB.");
2662
+ return normalized;
2663
+ }
2664
+ function validateBaseAndPatch(baseCommitSha, patch2) {
2665
+ if (!SHA2.test(baseCommitSha) || utf8Length(patch2) > MAX_PATCH_BYTES || patch2.includes("\0")) {
2666
+ throw invalid2("Portable checkpoint base or patch is malformed or outside its bounds.");
2667
+ }
2668
+ }
2669
+ function strings(value, name, pattern, maximum, sort) {
2670
+ if (!Array.isArray(value) || value.length > maximum || value.some((item) => typeof item !== "string" || !pattern.test(item)) || new Set(value).size !== value.length) {
2671
+ throw invalid2(`Portable checkpoint ${name} is malformed or outside its bounds.`);
2672
+ }
2673
+ const result = [...value];
2674
+ return sort ? result.sort() : result;
2675
+ }
2676
+ function object(value, name) {
2677
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid2(`Portable ${name} must be an object.`);
2678
+ return value;
2679
+ }
2680
+ function exact2(value, keys) {
2681
+ if (Object.keys(value).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value))) {
2682
+ throw invalid2("Portable checkpoint contains missing or unsupported fields.");
2683
+ }
2684
+ }
2685
+ function freeze(value) {
2686
+ return Object.freeze({
2687
+ ...value,
2688
+ state: Object.freeze({
2689
+ ...value.state,
2690
+ conversationRefs: Object.freeze([...value.state.conversationRefs]),
2691
+ completedEffects: Object.freeze(value.state.completedEffects.map((item) => Object.freeze({ ...item }))),
2692
+ unresolvedApprovals: Object.freeze([...value.state.unresolvedApprovals])
2693
+ })
2694
+ });
2695
+ }
2696
+ function invalid2(message3) {
2697
+ return new CamelError("state_conflict", message3);
2698
+ }
2699
+ var SHA4 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
2700
+ var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
2701
+ var DEFAULT_LIMITS = { maximumFiles: 4e3, maximumBytes: 2 * 1024 * 1024 };
2702
+ async function digestCodeRepositorySnapshot(snapshot, limits = DEFAULT_LIMITS) {
2703
+ validateSnapshot(snapshot, limits);
2704
+ const normalized = {
2705
+ repository: snapshot.repository,
2706
+ commitSha: snapshot.commitSha,
2707
+ files: [...snapshot.files].map((file) => ({ path: file.path, content: file.content })).sort((left, right) => left.path.localeCompare(right.path))
2708
+ };
2709
+ return `sha256:${await sha256Hex(canonicalJson(normalized))}`;
2710
+ }
2711
+ function validateSnapshot(snapshot, limits) {
2712
+ if (!REPOSITORY.test(snapshot.repository) || !SHA4.test(snapshot.commitSha)) {
2713
+ throw new CamelError("state_conflict", "Code snapshot repository or commit is invalid.");
2714
+ }
2715
+ if (!Number.isSafeInteger(limits.maximumFiles) || limits.maximumFiles < 1 || !Number.isSafeInteger(limits.maximumBytes) || limits.maximumBytes < 1 || snapshot.files.length > limits.maximumFiles) {
2716
+ throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
2717
+ }
2718
+ const paths = /* @__PURE__ */ new Set();
2719
+ let bytes = 0;
2720
+ for (const file of snapshot.files) {
2721
+ 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") {
2722
+ throw new CamelError("state_conflict", "Code snapshot contains an invalid or duplicate path.");
2723
+ }
2724
+ paths.add(file.path);
2725
+ bytes += utf8Length(file.path) + utf8Length(file.content);
2726
+ }
2727
+ if (bytes > limits.maximumBytes) {
2728
+ throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
2729
+ }
2730
+ }
2731
+
2732
+ // ../harness/dist/chunk-RVUUURK2.js
2733
+ var import_child_process3 = require("child_process");
2734
+ var import_promises4 = require("fs/promises");
2735
+ var import_path3 = require("path");
2736
+ var import_child_process4 = require("child_process");
2737
+ var import_process2 = require("process");
2738
+ var import_crypto = require("crypto");
2739
+ var import_crypto2 = require("crypto");
2740
+ var import_fs2 = require("fs");
2741
+ var import_promises5 = require("fs/promises");
2742
+ var import_path4 = require("path");
2743
+ var import_crypto3 = require("crypto");
2744
+ var import_promises6 = require("fs/promises");
2745
+ var import_path5 = require("path");
2746
+ var import_promises7 = require("fs/promises");
2747
+ var import_os2 = require("os");
2748
+ var import_path6 = require("path");
2749
+ var import_promises8 = require("fs/promises");
2750
+ var import_path7 = require("path");
2751
+
2752
+ // ../camel/dist/chunk-LAXU2AVK.js
2753
+ function conversionPolicyDigest(policy) {
2754
+ return sha256Hex(canonicalJson(policy));
2755
+ }
2756
+ function registeredIdRegistryDigest(values) {
2757
+ return sha256Hex(canonicalJson(values));
2758
+ }
2759
+ async function createConversionRegistry(config) {
2760
+ const policies = /* @__PURE__ */ new Map();
2761
+ for (const policy of config.policies) {
2762
+ validatePolicyShape(policy);
2763
+ if (policies.has(policy.conversionId)) throw new CamelError("state_conflict", "Conversion IDs must be unique.");
2764
+ const { digest: _digest, ...definition } = policy;
2765
+ if (await conversionPolicyDigest(definition) !== policy.digest) throw new CamelError("state_conflict", "Conversion policy digest mismatch.");
2766
+ if (policy.output.kind === "registered_id") {
2767
+ const registry = config.registeredIds?.[policy.output.registryId];
2768
+ const validValues = registry && Object.entries(registry.values).every(([candidate, id]) => candidate.length > 0 && typeof id === "string" && id.length > 0);
2769
+ if (!registry || !validValues || registry.digest !== policy.output.registryDigest || await registeredIdRegistryDigest(registry.values) !== registry.digest) {
2770
+ throw new CamelError("state_conflict", "Registered-ID registry digest mismatch.");
2771
+ }
2772
+ }
2773
+ policies.set(policy.conversionId, Object.freeze(policy));
2774
+ }
2775
+ const outputCounts = /* @__PURE__ */ new Map();
2776
+ const get = (id, kind) => {
2777
+ const policy = policies.get(id);
2778
+ if (!policy || policy.output.kind !== kind) throw new CamelError("conversion_rejected", "Conversion policy is missing or has the wrong output kind.");
2779
+ return policy;
2780
+ };
2781
+ const checked = (source, id, kind) => {
2782
+ const policy = get(id, kind);
2783
+ if (utf8Length(source.value) > policy.maximumSourceBytes) throw new CamelError("limit_exceeded", "Unsafe conversion input exceeds its byte bound.");
2784
+ return policy;
2785
+ };
2786
+ const emit = (source, policy, value) => convert(source, policy, value, outputCounts);
2787
+ const operations = Object.freeze({
2788
+ boolean: async (value, id) => {
2789
+ const policy = checked(value, id, "boolean");
2790
+ return emit(value, policy, requireBoolean(value.value));
2791
+ },
2792
+ integer: async (value, id) => {
2793
+ const policy = checked(value, id, "integer");
2794
+ return emit(value, policy, boundedInteger(value.value, policy.output));
2795
+ },
2796
+ finiteNumber: async (value, id) => {
2797
+ const policy = checked(value, id, "finite_number");
2798
+ return emit(value, policy, boundedNumber(value.value, policy.output));
2799
+ },
2800
+ enum: async (value, id) => {
2801
+ const policy = checked(value, id, "enum");
2802
+ return emit(value, policy, enumMember(value.value, policy.output));
2803
+ },
2804
+ date: async (value, id) => {
2805
+ const policy = checked(value, id, "date");
2806
+ return emit(value, policy, canonicalDate(value.value, policy.output));
2807
+ },
2808
+ registeredId: async (value, id) => {
2809
+ const policy = checked(value, id, "registered_id");
2810
+ const registry = config.registeredIds?.[policy.output.registryId];
2811
+ const output = typeof value.value === "string" ? registry?.values[value.value] : void 0;
2812
+ if (!output) throw new CamelError("conversion_rejected", "Registered-ID conversion rejected the candidate.");
2813
+ return emit(value, policy, output);
2814
+ },
2815
+ digest: async (value, id) => {
2816
+ const policy = checked(value, id, "digest");
2817
+ if (!(value.value instanceof Uint8Array)) throw new CamelError("conversion_rejected", "Digest conversion requires bytes.");
2818
+ return emit(value, policy, await sha256Hex(value.value));
2819
+ },
2820
+ measure: async (value, metric, id) => {
2821
+ const policy = checked(value, id, "integer");
2822
+ const measured = measure(value.value, metric);
2823
+ return emit(value, policy, boundedInteger(measured, policy.output));
2824
+ },
2825
+ test: async (value, predicateId, id) => {
2826
+ const policy = checked(value, id, "boolean");
2827
+ const predicate = config.predicates?.[predicateId];
2828
+ if (!predicate) throw new CamelError("conversion_rejected", "Predicate is not registered.");
2829
+ return emit(value, policy, evaluatePredicate(value.value, predicate, config.registeredIds));
2830
+ }
2831
+ });
2832
+ return Object.freeze({ operations, policy: (id) => policies.get(id) ?? missingPolicy() });
2833
+ }
2834
+ async function convert(source, policy, value, counts) {
2835
+ const sourceKey = sourceIdentity(source);
2836
+ const countKey = `${policy.conversionId}\0${sourceKey}`;
2837
+ const count = counts.get(countKey) ?? 0;
2838
+ if (count >= policy.maximumOutputsPerArtifact) throw new CamelError("limit_exceeded", "Conversion output count exceeds its per-source bound.");
2839
+ counts.set(countKey, count + 1);
2840
+ return createSafeInternal(value, "atomic_conversion", {
2841
+ readers: source.label.readers,
2842
+ provenance: [...source.label.provenance, { kind: "converter", id: policy.conversionId, digest: policy.digest }],
2843
+ dependencies: dependenciesOf([source])
2844
+ });
2845
+ }
2846
+ function validatePolicyShape(policy) {
2847
+ if (!policy.conversionId || !Number.isSafeInteger(policy.version) || policy.version < 1 || !policy.digest) throw new CamelError("state_conflict", "Conversion policy identity is invalid.");
2848
+ 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.");
2849
+ const output = policy.output;
2850
+ 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.");
2851
+ 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.");
2852
+ 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.");
2853
+ if (output.kind === "registered_id" && (!output.registryId || !output.registryDigest)) throw new CamelError("state_conflict", "Registered-ID conversion identity is invalid.");
2854
+ if (output.kind === "date" && output.format !== "date" && output.format !== "rfc3339") throw new CamelError("state_conflict", "Date conversion format is invalid.");
2855
+ if (output.kind === "digest" && output.algorithm !== "sha256") throw new CamelError("state_conflict", "Digest conversion algorithm is invalid.");
2856
+ }
2857
+ function requireBoolean(value) {
2858
+ if (typeof value !== "boolean") throw new CamelError("conversion_rejected", "Boolean conversion requires a structured boolean.");
2859
+ return value;
2860
+ }
2861
+ function boundedInteger(value, spec) {
2862
+ 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.");
2863
+ return value;
2864
+ }
2865
+ function boundedNumber(value, spec) {
2866
+ 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.");
2867
+ const text = String(value);
2868
+ 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.");
2869
+ return value;
2870
+ }
2871
+ function enumMember(value, spec) {
2872
+ if (spec.kind !== "enum" || typeof value !== "string") throw new CamelError("conversion_rejected", "Enum conversion requires a structured string member.");
2873
+ const member = spec.caseSensitive ? spec.values.find((item) => item === value) : spec.values.find((item) => item.toLocaleLowerCase("en-US") === value.toLocaleLowerCase("en-US"));
2874
+ if (member === void 0) throw new CamelError("conversion_rejected", "Enum conversion rejected a non-member.");
2875
+ return member;
2876
+ }
2877
+ function canonicalDate(value, spec) {
2878
+ if (spec.kind !== "date" || typeof value !== "string") throw new CamelError("conversion_rejected", "Date conversion requires a canonical structured string.");
2879
+ 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})$/;
2880
+ const instant = Date.parse(spec.format === "date" ? `${value}T00:00:00Z` : value);
2881
+ 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.");
2882
+ if (spec.earliest && value < spec.earliest || spec.latest && value > spec.latest) throw new CamelError("conversion_rejected", "Date conversion rejected an out-of-range value.");
2883
+ return value;
2884
+ }
2885
+ function measure(value, metric) {
2886
+ if (metric === "byte_length" && (typeof value === "string" || value instanceof Uint8Array)) return utf8Length(value);
2887
+ if (metric === "codepoint_length" && typeof value === "string") return [...value].length;
2888
+ if (metric === "item_count" && Array.isArray(value)) return value.length;
2889
+ throw new CamelError("conversion_rejected", "Measurement input does not match the registered metric.");
2890
+ }
2891
+ function evaluatePredicate(value, predicate, registries) {
2892
+ if (predicate.kind === "has_fields") return typeof value === "object" && value !== null && !Array.isArray(value) && predicate.fields.every((field) => Object.hasOwn(value, field));
2893
+ if (predicate.kind === "registered_id") return typeof value === "string" && registries?.[predicate.registryId]?.values[value] !== void 0;
2894
+ const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
2895
+ return actual === predicate.valueType;
2896
+ }
2897
+ function sourceIdentity(source) {
2898
+ const artifact = [...source.label.provenance, ...source.label.dependencies.map((dependency) => dependency.ref)].find((ref2) => ref2.kind === "artifact");
2899
+ const fallback = source.label.provenance[0];
2900
+ if (!artifact && !fallback) throw new CamelError("conversion_rejected", "Conversion source has no immutable provenance.");
2901
+ const ref = artifact ?? fallback;
2902
+ return `${ref.kind}:${ref.id}:${ref.digest ?? ""}`;
2903
+ }
2904
+ function missingPolicy() {
2905
+ throw new CamelError("conversion_rejected", "Conversion policy is not registered.");
2906
+ }
2907
+
2908
+ // ../camel/dist/chunk-P2WQIAG6.js
2909
+ function createCamelIngress(constants2 = []) {
2910
+ const byId = /* @__PURE__ */ new Map();
2911
+ for (const item of constants2) {
2912
+ if (!item.id || byId.has(item.id)) throw new CamelError("state_conflict", "Control constant IDs must be unique and non-empty.");
2913
+ assertNoUnsafeConstant(item.value);
2914
+ byId.set(item.id, item);
2915
+ }
2916
+ const ingress = {
2917
+ userInstruction: (value, input) => createSafeInternal(value, "user_instruction", metadata("user_instruction", input.id, input.readers)),
2918
+ systemPolicy: (value, input) => createSafeInternal(value, "system_policy", metadata("system_policy", input.id, input.readers)),
2919
+ control: (id) => {
2920
+ const item = byId.get(id);
2921
+ if (!item) throw new CamelError("permission_denied", "Unknown control constant.");
2922
+ return createSafeInternal(item.value, "harness_constant", metadata("harness", id, item.readers));
2923
+ },
2924
+ external: (value, label) => createUnsafeInternal(value, label),
2925
+ quarantinedOutput: (value, input) => {
2926
+ if (!input.runId) throw new CamelError("state_conflict", "Quarantined run IDs must be non-empty.");
2927
+ return createUnsafeInternal(value, {
2928
+ readers: input.readers,
2929
+ dependencies: input.dependencies,
2930
+ provenance: [{ kind: "quarantined_llm", id: input.runId, digest: input.digest }]
2931
+ });
2932
+ }
2933
+ };
2934
+ return Object.freeze(ingress);
2935
+ }
2936
+ function assertNoUnsafeConstant(value, seen = /* @__PURE__ */ new WeakSet()) {
2937
+ if (typeof value !== "object" || value === null || seen.has(value)) return;
2938
+ seen.add(value);
2939
+ if (isCamelValue(value)) {
2940
+ if (isUnsafe(value)) throw new CamelError("unsafe_privileged_flow", "Control constants cannot contain Prompt-Unsafe values.");
2941
+ assertNoUnsafeConstant(value.value, seen);
2942
+ return;
2943
+ }
2944
+ for (const child of Object.values(value)) assertNoUnsafeConstant(child, seen);
2945
+ }
2946
+ function metadata(kind, id, readers) {
2947
+ if (!id) throw new CamelError("state_conflict", "Provenance IDs must be non-empty.");
2948
+ return { readers, provenance: [{ kind, id }] };
2949
+ }
2950
+
2951
+ // ../camel/dist/policy.js
2952
+ function createEffectPolicy(config = {}) {
2953
+ const approvalEffects = new Set(config.approvalEffects ?? ["irreversible_mutation", "external_send", "financial", "secret_read", "code_execution"]);
2954
+ const registries = copyRegistries(config.destinationRegistries ?? {});
2955
+ return Object.freeze({ evaluate: (input) => evaluate(input, registries, approvalEffects) });
2956
+ }
2957
+ function destinationRegistryDigest(values) {
2958
+ return sha256Hex(canonicalJson([...values].sort()));
2959
+ }
2960
+ async function evaluate(input, registries, approvals) {
2961
+ const actionDigest = await sha256Hex(canonicalJson(input));
2962
+ const decisionId = `decision_${actionDigest.slice(0, 24)}`;
2963
+ const deny = (reasonCode) => ({ outcome: "deny", decisionId, reasonCode });
2964
+ if (!input.planId || !input.tool.name || !input.tool.policyId || !Number.isSafeInteger(input.tool.version)) return deny("invalid_descriptor");
2965
+ const expectedPaths = Object.keys(input.tool.argumentRoles).sort();
2966
+ const actualPaths = Object.keys(input.args).sort();
2967
+ if (expectedPaths.length !== actualPaths.length || expectedPaths.some((path, index) => path !== actualPaths[index])) return deny("argument_shape_mismatch");
2968
+ const confined = /* @__PURE__ */ new Set();
2969
+ for (const [path, arg] of Object.entries(input.args)) {
2970
+ if (arg.role !== input.tool.argumentRoles[path]) return deny("argument_role_mismatch");
2971
+ const invalid3 = await validateArgument(path, arg, input.tool, registries);
2972
+ if (invalid3) return deny(invalid3);
2973
+ if (arg.role === "selector" && !isSafe(arg.value)) confined.add(arg.value);
2974
+ }
2975
+ if (input.controlDependencies.some((value) => !isSafe(value) && !confined.has(value))) return deny("unsafe_control_dependency");
2976
+ if (confined.size > 0) {
2977
+ const fixed = input.tool.unsafeSelectorPolicy?.fixedProviderIds ?? [];
2978
+ const destinations = Object.values(input.args).filter((arg) => arg.role === "destination");
2979
+ if (destinations.length === 0 || destinations.some((arg) => !fixed.includes(arg.value.value))) return deny("unsafe_selector_provider_mismatch");
2980
+ }
2981
+ const readerValues = input.intendedReaderIds ?? [];
2982
+ if (readerValues.some((reader) => !isSafe(reader) || typeof reader.value !== "string" || !reader.value || !isControlOwned(reader))) return deny("invalid_reader");
2983
+ const readers = readerValues.map((reader) => reader.value);
2984
+ for (const arg of Object.values(input.args)) {
2985
+ if (readers.some((reader) => !canRead(arg.value.label.readers, reader))) return deny("reader_mismatch");
2986
+ }
2987
+ const hasUnsafePayload = Object.values(input.args).some((arg) => arg.role === "payload" && !isSafe(arg.value));
2988
+ if (hasUnsafePayload && input.tool.effect === "external_send" && readers.length === 0) return deny("reader_required");
2989
+ if (approvals.has(input.tool.effect)) return { outcome: "require_approval", decisionId, reasonCode: "effect_requires_approval", actionDigest };
2990
+ return { outcome: "allow", decisionId };
2991
+ }
2992
+ async function validateArgument(path, arg, tool, registries) {
2993
+ if (arg.role === "instruction" && !isSafe(arg.value)) return "unsafe_instruction_argument";
2994
+ if (arg.role === "authority") {
2995
+ if (!isSafe(arg.value)) return "unsafe_authority_argument";
2996
+ if (!isControlOwned(arg.value)) return "authority_not_control_owned";
2997
+ }
2998
+ if (arg.role === "destination") {
2999
+ if (!isSafe(arg.value)) return "unsafe_destination_argument";
3000
+ if (!isControlOwned(arg.value)) return "destination_not_control_owned";
3001
+ const registry = registries[arg.registryId];
3002
+ 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;
3003
+ if (!registry || !validValues || registry.digest !== arg.registryDigest || await destinationRegistryDigest(registry.values) !== registry.digest || !registry.values.includes(arg.value.value)) return "destination_not_registered";
3004
+ }
3005
+ if (arg.role === "selector" && !isSafe(arg.value)) return validateUnsafeSelector(path, arg.value, tool);
3006
+ return void 0;
3007
+ }
3008
+ function isControlOwned(value) {
3009
+ return value.label.safeBasis === "system_policy" || value.label.safeBasis === "harness_constant";
3010
+ }
3011
+ function copyRegistries(registries) {
3012
+ return Object.freeze(Object.fromEntries(Object.entries(registries).map(([id, registry]) => [id, Object.freeze({ digest: registry.digest, values: Object.freeze([...registry.values]) })])));
3013
+ }
3014
+ function validateUnsafeSelector(path, value, tool) {
3015
+ const policy = tool.unsafeSelectorPolicy;
3016
+ if (!policy || policy.effect !== tool.effect || !policy.selectorPaths.includes(path)) return "unsafe_selector_not_confined";
3017
+ if (!policy.fixedDestination || !policy.unsafeOutput || policy.fixedProviderIds.length === 0) return "unsafe_selector_not_confined";
3018
+ if (![policy.maximumCalls, policy.maximumResultsPerCall, policy.maximumOutputBytes, policy.maximumSelectorBytes].every((bound) => Number.isSafeInteger(bound) && bound > 0)) return "unsafe_selector_not_confined";
3019
+ if (utf8Length(value.value) > policy.maximumSelectorBytes) return "unsafe_selector_too_large";
3020
+ if (typeof value.value === "string" && looksLikeDestination(value.value)) return "unsafe_selector_is_destination";
3021
+ return void 0;
3022
+ }
3023
+ function looksLikeDestination(value) {
3024
+ const text = value.trim();
3025
+ return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text);
3026
+ }
3027
+
3028
+ // ../harness/dist/chunk-RVUUURK2.js
3029
+ var import_crypto4 = require("crypto");
3030
+ var CODE_RUNTIME_PROTOCOL_VERSION = 1;
3031
+ async function runCodeRuntimeHeartbeatLoop(options) {
3032
+ const heartbeatMs = options.heartbeatMs ?? 15e3;
3033
+ if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3 || heartbeatMs > 3e5) {
3034
+ throw new TypeError("heartbeatMs must be an integer from 1000 to 300000");
3035
+ }
3036
+ let retryMs = 1e3;
3037
+ do {
3038
+ if (options.signal?.aborted) return;
3039
+ try {
3040
+ const snapshot = await options.control.heartbeat(options.runtimeVersion, options.capabilities);
3041
+ await options.onSnapshot?.(snapshot);
3042
+ retryMs = 1e3;
3043
+ if (options.once) return;
3044
+ await wait(heartbeatMs, options.signal);
3045
+ } catch (error) {
3046
+ if (options.signal?.aborted) return;
3047
+ if (options.once || !retryableControlFailure(error)) throw error;
3048
+ await options.onRetry?.(error, retryMs);
3049
+ await wait(retryMs, options.signal);
3050
+ retryMs = Math.min(retryMs * 2, 3e4);
3051
+ }
3052
+ } while (!options.signal?.aborted);
3053
+ }
3054
+ var CodeRuntimeReconciler = class {
3055
+ constructor(control, engine) {
3056
+ this.control = control;
3057
+ this.engine = engine;
3058
+ }
3059
+ control;
3060
+ engine;
3061
+ results = /* @__PURE__ */ new Map();
3062
+ async reconcile(snapshot) {
3063
+ for (const command of snapshot.commands) {
3064
+ let completed = this.results.get(command.commandId);
3065
+ if (!completed) {
3066
+ let result;
3067
+ try {
3068
+ result = await this.engine.execute(command);
3069
+ } catch (error) {
3070
+ result = { status: "failed", message: (error instanceof Error ? error.message : String(error)).slice(0, 2e3) };
3071
+ }
3072
+ completed = { result, notified: false };
3073
+ this.results.set(command.commandId, completed);
3074
+ if (this.results.size > 1024) this.results.delete(this.results.keys().next().value);
3075
+ }
3076
+ await this.control.acknowledge(command.commandId, completed.result);
3077
+ if (!completed.notified) {
3078
+ await this.engine.acknowledged?.(command, completed.result);
3079
+ completed.notified = true;
3080
+ }
3081
+ }
3082
+ }
3083
+ };
3084
+ function retryableControlFailure(value) {
3085
+ if (!value || typeof value !== "object") return false;
3086
+ const failure = value;
3087
+ if (failure.code === "invalid_response" || typeof failure.status !== "number") return false;
3088
+ return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
3089
+ }
3090
+ function wait(ms, signal) {
3091
+ return new Promise((resolve52) => {
3092
+ if (signal?.aborted) return resolve52();
3093
+ const timer = setTimeout(resolve52, ms);
3094
+ signal?.addEventListener("abort", () => {
3095
+ clearTimeout(timer);
3096
+ resolve52();
3097
+ }, { once: true });
3098
+ });
3099
+ }
3100
+ var CodeRuntimeControlError = class extends Error {
3101
+ constructor(message3, status, code = "control_error") {
3102
+ super(message3);
3103
+ this.status = status;
3104
+ this.code = code;
3105
+ }
3106
+ status;
3107
+ code;
3108
+ name = "CodeRuntimeControlError";
3109
+ };
3110
+ function createCodeRuntimeControlClient(options) {
3111
+ const endpoint = validatedEndpoint(options.endpoint);
3112
+ if (!/^odla_code_host_[0-9a-f]{64}$/.test(options.token)) throw new TypeError("invalid Code host credential");
3113
+ const requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
3114
+ if (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1e3 || requestTimeoutMs > 12e4) {
3115
+ throw new TypeError("requestTimeoutMs must be an integer from 1000 to 120000");
3116
+ }
3117
+ const request = options.fetch ?? fetch;
3118
+ const call = async (path, body) => {
3119
+ const timeout = AbortSignal.timeout(requestTimeoutMs);
3120
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
3121
+ let response2;
3122
+ try {
3123
+ response2 = await request(`${endpoint}${path}`, {
3124
+ method: "POST",
3125
+ headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
3126
+ body: JSON.stringify(body),
3127
+ redirect: "error",
3128
+ signal
3129
+ });
3130
+ } catch (cause) {
3131
+ if (options.signal?.aborted) throw cause;
3132
+ throw new CodeRuntimeControlError("Code runtime control plane is unavailable", 503, "transport_unavailable");
3133
+ }
3134
+ const value = await response2.json().catch(() => null);
3135
+ if (!response2.ok) {
3136
+ const problem = record3(record3(value)?.error);
3137
+ throw new CodeRuntimeControlError(
3138
+ typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
3139
+ response2.status,
3140
+ typeof problem?.code === "string" ? problem.code : void 0
3141
+ );
3142
+ }
3143
+ return value;
3144
+ };
3145
+ return {
3146
+ heartbeat: async (version, capabilities) => {
3147
+ validateHeartbeat(version, capabilities);
3148
+ return parseSnapshot(await call("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
3149
+ },
3150
+ acknowledge: async (commandId, result) => {
3151
+ if (!/^ccmd_[0-9a-f]{32}$/.test(commandId)) throw new TypeError("invalid Code runtime command id");
3152
+ await call(`/registry/code/runtime/commands/${commandId}/ack`, result);
3153
+ },
3154
+ source: async (sessionId) => parseSource(
3155
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
3156
+ ),
3157
+ infer: async (sessionId, inference) => {
3158
+ const value = record3(await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`, inference));
3159
+ if (!value || value.requestId !== inference.requestId || !record3(value.response) || !record3(value.receipt)) {
3160
+ throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
3161
+ }
3162
+ return value;
3163
+ },
3164
+ review: async (sessionId, review) => parseReview(
3165
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review)
3166
+ ),
3167
+ submitCandidate: async (sessionId, checkpointId, verification) => {
3168
+ if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
3169
+ return parseCandidate(await call(
3170
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
3171
+ { checkpointId, verification }
3172
+ ));
3173
+ },
3174
+ appendSessionEvent: async (sessionId, eventId, event) => {
3175
+ const serialized = JSON.stringify(event);
3176
+ if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
3177
+ throw new TypeError("invalid Code session event");
3178
+ }
3179
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
3180
+ },
3181
+ reportSessionFailure: async (sessionId, message3) => {
3182
+ if (!message3.trim() || message3.length > 2e3) throw new TypeError("invalid Code session failure");
3183
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message3 });
3184
+ }
3185
+ };
3186
+ }
3187
+ function validatedEndpoint(value) {
3188
+ const endpoint = value.replace(/\/+$/, "");
3189
+ let url;
3190
+ try {
3191
+ url = new URL(endpoint);
3192
+ } catch {
3193
+ throw new TypeError("endpoint must be an HTTPS URL");
3194
+ }
3195
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
3196
+ if (url.username || url.password || url.protocol !== "https:" && !(loopback && url.protocol === "http:")) {
3197
+ throw new TypeError("endpoint must use HTTPS (HTTP is allowed only for loopback testing)");
3198
+ }
3199
+ return endpoint;
3200
+ }
3201
+ function validSessionId(value) {
3202
+ if (!/^csess_[0-9a-f]{32}$/.test(value)) throw new TypeError("invalid Code session id");
3203
+ return value;
3204
+ }
3205
+ function validateHeartbeat(version, capabilities) {
3206
+ if (!version.trim() || version.length > 80) throw new TypeError("runtimeVersion is required and at most 80 characters");
3207
+ if (capabilities.protocolVersion !== CODE_RUNTIME_PROTOCOL_VERSION) throw new TypeError("unsupported Code runtime protocol version");
3208
+ if (capabilities.platform !== "macos" && capabilities.platform !== "linux") throw new TypeError("invalid runtime platform");
3209
+ if (!capabilities.arch || !capabilities.engines.length || !capabilities.engines.every((engine) => ["container", "podman", "docker"].includes(engine))) {
3210
+ throw new TypeError("runtime arch and supported engine are required");
3211
+ }
3212
+ if (!Number.isSafeInteger(capabilities.cpuCount) || capabilities.cpuCount < 1 || !Number.isSafeInteger(capabilities.memoryBytes) || capabilities.memoryBytes < 1) {
3213
+ throw new TypeError("runtime resources must be positive integers");
3214
+ }
3215
+ }
3216
+ function parseSnapshot(value) {
3217
+ const root = record3(value);
3218
+ const host = record3(root?.host);
3219
+ 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");
3220
+ const bindingIds = /* @__PURE__ */ new Set();
3221
+ const bindings = root.bindings.map((item) => {
3222
+ const binding = record3(item);
3223
+ 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)) {
3224
+ throw invalid("binding");
3225
+ }
3226
+ bindingIds.add(binding.bindingId);
3227
+ return binding;
3228
+ });
3229
+ const commandIds = /* @__PURE__ */ new Set();
3230
+ const commandSequences = /* @__PURE__ */ new Set();
3231
+ const commands = root.commands.map((item) => {
3232
+ const command = record3(item);
3233
+ const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
3234
+ const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
3235
+ 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");
3236
+ commandIds.add(command.commandId);
3237
+ commandSequences.add(sequenceKey);
3238
+ return command;
3239
+ });
3240
+ return { host, bindings, commands };
3241
+ }
3242
+ async function parseSource(value) {
3243
+ const snapshot = record3(record3(value)?.snapshot);
3244
+ if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
3245
+ const files = snapshot.files.map((value2) => {
3246
+ const file = record3(value2);
3247
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
3248
+ return { path: file.path, content: file.content };
3249
+ });
3250
+ const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
3251
+ const digest2 = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
3252
+ if (digest2 !== snapshot.treeDigest) throw invalid("source digest");
3253
+ return { ...source, treeDigest: digest2 };
3254
+ }
3255
+ function parseReview(value) {
3256
+ const review = record3(record3(value)?.review);
3257
+ 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");
3258
+ return review;
3259
+ }
3260
+ function parseCandidate(value) {
3261
+ const candidate = record3(record3(value)?.candidate);
3262
+ if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
3263
+ throw invalid("candidate");
3264
+ }
3265
+ return { candidateId: candidate.candidateId, status: candidate.status };
3266
+ }
3267
+ var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
3268
+ var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
3269
+ var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3270
+ var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3271
+ var PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
3272
+ 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;
3273
+ function validateCodePatch(patch2, maxBytes) {
3274
+ if (!patch2 || Buffer.byteLength(patch2) > maxBytes || patch2.includes("\0") || patch2.includes("\r")) {
3275
+ throw new TypeError("patch is empty, malformed, or exceeds its byte limit");
3276
+ }
3277
+ if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
3278
+ throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
3279
+ }
3280
+ const paths = [];
3281
+ const lines = patch2.split("\n");
3282
+ for (let index = 0; index < lines.length; index += 1) {
3283
+ const line = lines[index];
3284
+ if (!line.startsWith("diff --git ")) continue;
3285
+ const match = /^diff --git a\/(\S+) b\/(\S+)$/.exec(line);
3286
+ const path = match?.[1];
3287
+ if (!path || !match?.[2] || path !== match[2]) throw new TypeError("patch must use one unquoted relative path per diff");
3288
+ validateRelativePath(path);
3289
+ const header = lines.slice(index + 1).findIndex((candidate) => candidate.startsWith("diff --git "));
3290
+ const section = lines.slice(index + 1, header < 0 ? lines.length : index + 1 + header);
3291
+ const oldPath = section.find((candidate) => candidate.startsWith("--- "))?.slice(4);
3292
+ const newPath = section.find((candidate) => candidate.startsWith("+++ "))?.slice(4);
3293
+ if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
3294
+ throw new TypeError("patch file headers do not match the declared path");
3295
+ }
3296
+ paths.push(path);
3297
+ }
3298
+ if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
3299
+ return paths;
3300
+ }
3301
+ function validHeaderPath(value, path, prefix) {
3302
+ return value === "/dev/null" || value === `${prefix}/${path}`;
3303
+ }
3304
+ function validateRelativePath(path) {
3305
+ const parts = path.split("/");
3306
+ if (!PATH.test(path) || parts.some((part) => part === "." || part === ".." || RESERVED.has(part)) || parts.some((part) => SECRET.test(part))) {
3307
+ throw new TypeError("path is outside the allowed staged source tree");
3308
+ }
3309
+ }
3310
+ function resolveCodePath(workspaceDir, path) {
3311
+ validateRelativePath(path);
3312
+ const root = (0, import_path3.resolve)(workspaceDir);
3313
+ const target = (0, import_path3.resolve)(root, path);
3314
+ if (target !== root && !target.startsWith(`${root}${import_path3.sep}`)) throw new TypeError("path escapes the staged workspace");
3315
+ return target;
3316
+ }
3317
+ async function applyCodePatch(workspaceDir, patch2, paths) {
3318
+ await gitApply(workspaceDir, patch2, true);
3319
+ await gitApply(workspaceDir, patch2, false);
3320
+ for (const path of paths) {
3321
+ try {
3322
+ const info = await (0, import_promises4.lstat)(resolveCodePath(workspaceDir, path));
3323
+ if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
3324
+ throw new TypeError("patch created a non-regular workspace entry");
3325
+ }
3326
+ } catch (reason) {
3327
+ if (reason.code !== "ENOENT") throw reason;
3328
+ }
3329
+ }
3330
+ }
3331
+ function gitApply(cwd, patch2, check) {
3332
+ return new Promise((accept, reject) => {
3333
+ const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
3334
+ const child = (0, import_child_process3.spawn)("git", args, {
3335
+ cwd,
3336
+ shell: false,
3337
+ stdio: ["pipe", "ignore", "pipe"],
3338
+ env: { PATH: process.env.PATH ?? "", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null" }
3339
+ });
3340
+ let stderr = "";
3341
+ child.stderr.setEncoding("utf8");
3342
+ child.stderr.on("data", (text) => {
3343
+ if (stderr.length < 4e3) stderr += text.slice(0, 4e3);
3344
+ });
3345
+ child.once("error", reject);
3346
+ child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
3347
+ child.stdin.end(patch2);
3348
+ });
3349
+ }
3350
+ async function createCodeWorkspaceCheckpoint(input) {
3351
+ const maximum = input.maximumPatchBytes ?? 256 * 1024;
3352
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > 256 * 1024) {
3353
+ throw new TypeError("checkpoint patch bound must be from 1 to 262144 bytes");
3354
+ }
3355
+ const patch2 = await input.workspace.patch(maximum);
3356
+ if (patch2) validateCodePatch(patch2, maximum);
3357
+ return createCodePortableCheckpoint({ baseCommitSha: input.baseCommitSha, patch: patch2, state: input.state });
3358
+ }
3359
+ async function restoreCodeWorkspaceCheckpoint(input) {
3360
+ const checkpoint = await verifyCodePortableCheckpoint(input.checkpoint);
3361
+ if (checkpoint.baseCommitSha !== input.trustedBaseCommitSha) {
3362
+ throw new TypeError("checkpoint trusted base does not match the fetched commit");
3363
+ }
3364
+ const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
3365
+ try {
3366
+ if (checkpoint.patch) {
3367
+ const paths = validateCodePatch(checkpoint.patch, 256 * 1024);
3368
+ await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
3369
+ }
3370
+ return { workspace, checkpoint };
3371
+ } catch (error) {
3372
+ await workspace.cleanup();
3373
+ throw error;
3374
+ }
3375
+ }
3376
+ var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
3377
+ var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
3378
+ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${(0, import_crypto.randomUUID)().slice(0, 12)}`) {
3379
+ assertCodeBuildRecipe(recipe2);
3380
+ if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
3381
+ const uid = typeof import_process2.getuid === "function" ? (0, import_process2.getuid)() : 1e3;
3382
+ const gid = typeof import_process2.getgid === "function" ? (0, import_process2.getgid)() : 1e3;
3383
+ const limits = {
3384
+ cpus: recipe2.cpus ?? 1,
3385
+ memory: recipe2.memory ?? "1g",
3386
+ pids: recipe2.pids ?? 256,
3387
+ tmpfs: recipe2.tmpfsBytes ?? 64 * 1024 * 1024
3388
+ };
3389
+ if (engine === "container") {
3390
+ return [
3391
+ "run",
3392
+ "--rm",
3393
+ `--name=${name}`,
3394
+ "--network=none",
3395
+ "--read-only",
3396
+ "--cap-drop=ALL",
3397
+ `--memory=${limits.memory}`,
3398
+ `--cpus=${limits.cpus}`,
3399
+ `--user=${uid}:${gid}`,
3400
+ "--tmpfs=/tmp",
3401
+ `--mount=type=bind,source=${workspaceDir},target=/workspace`,
3402
+ "--workdir=/workspace",
3403
+ "--env=CI=1",
3404
+ recipe2.image,
3405
+ ...recipe2.command
3406
+ ];
3407
+ }
3408
+ return [
3409
+ "run",
3410
+ "--rm",
3411
+ `--name=${name}`,
3412
+ "--pull=never",
3413
+ "--network=none",
3414
+ "--read-only",
3415
+ "--cap-drop=ALL",
3416
+ "--security-opt=no-new-privileges",
3417
+ `--pids-limit=${limits.pids}`,
3418
+ `--memory=${limits.memory}`,
3419
+ `--cpus=${limits.cpus}`,
3420
+ `--user=${uid}:${gid}`,
3421
+ `--tmpfs=/tmp:rw,noexec,nosuid,nodev,size=${limits.tmpfs}`,
3422
+ `--mount=type=bind,src=${workspaceDir},dst=/workspace`,
3423
+ "--workdir=/workspace",
3424
+ "--env=CI=1",
3425
+ recipe2.image,
3426
+ ...recipe2.command
3427
+ ];
3428
+ }
3429
+ function createContainerRecipeExecutor(engine) {
3430
+ return {
3431
+ async run(input) {
3432
+ await verifyContainerEngineBoundary(engine);
3433
+ const name = `odla-recipe-${(0, import_crypto.randomUUID)().slice(0, 12)}`;
3434
+ const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name);
3435
+ return execute(engine, args, name, input.recipe, input.signal);
3436
+ }
3437
+ };
3438
+ }
3439
+ function assertCodeBuildRecipe(recipe2) {
3440
+ const memoryBytes = parseMemory(recipe2.memory ?? "1g");
3441
+ const tmpfsBytes = recipe2.tmpfsBytes ?? 64 * 1024 * 1024;
3442
+ const artifacts = recipe2.expectedArtifacts ?? [];
3443
+ assertPinnedImage(recipe2.image);
3444
+ 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)) {
3445
+ throw new TypeError("build recipe is malformed or exceeds its control bounds");
3446
+ }
3447
+ }
3448
+ function parseMemory(value) {
3449
+ const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(value.toLowerCase());
3450
+ if (!match?.[1] || !match[2]) return 0;
3451
+ const scale = match[2] === "k" ? 1024 : match[2] === "m" ? 1024 ** 2 : 1024 ** 3;
3452
+ return Number(match[1]) * scale;
3453
+ }
3454
+ function execute(engine, args, name, recipe2, signal) {
3455
+ return new Promise((accept, reject) => {
3456
+ const started = Date.now();
3457
+ const child = (0, import_child_process4.spawn)(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
3458
+ const stdout = [];
3459
+ const stderr = [];
3460
+ let bytes = 0;
3461
+ let outputLimitExceeded = false;
3462
+ let timedOut = false;
3463
+ let stopping = false;
3464
+ const stop = (reason) => {
3465
+ if (stopping) return;
3466
+ stopping = true;
3467
+ timedOut = reason === "timeout";
3468
+ outputLimitExceeded = reason === "output";
3469
+ const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
3470
+ const killer = (0, import_child_process4.spawn)(engine, remove, { shell: false, stdio: "ignore" });
3471
+ killer.unref();
3472
+ child.kill("SIGTERM");
3473
+ };
3474
+ const collect = (target) => (chunk) => {
3475
+ bytes += chunk.byteLength;
3476
+ if (bytes > recipe2.maxOutputBytes) stop("output");
3477
+ else target.push(chunk);
3478
+ };
3479
+ child.stdout.on("data", collect(stdout));
3480
+ child.stderr.on("data", collect(stderr));
3481
+ const abort = () => stop("abort");
3482
+ signal?.addEventListener("abort", abort, { once: true });
3483
+ if (signal?.aborted) abort();
3484
+ const timer = setTimeout(() => stop("timeout"), recipe2.timeoutMs);
3485
+ child.once("error", (error) => {
3486
+ clearTimeout(timer);
3487
+ signal?.removeEventListener("abort", abort);
3488
+ reject(error);
3489
+ });
3490
+ child.once("exit", (code) => {
3491
+ clearTimeout(timer);
3492
+ signal?.removeEventListener("abort", abort);
3493
+ accept({
3494
+ exitCode: code ?? 1,
3495
+ stdout: Buffer.concat(stdout).toString("utf8"),
3496
+ stderr: Buffer.concat(stderr).toString("utf8"),
3497
+ durationMs: Date.now() - started,
3498
+ outputLimitExceeded,
3499
+ timedOut
3500
+ });
3501
+ });
3502
+ });
3503
+ }
3504
+ async function digestStagedWorkspace(root, limits) {
3505
+ const files = [];
3506
+ const walk = async (directory) => {
3507
+ const entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
3508
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3509
+ if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
3510
+ const target = (0, import_path5.resolve)(directory, entry.name);
3511
+ if (entry.isDirectory()) await walk(target);
3512
+ else if (entry.isFile()) {
3513
+ files.push({ path: (0, import_path5.relative)(root, target).split("\\").join("/"), target });
3514
+ if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
3515
+ }
3516
+ }
3517
+ };
3518
+ await walk((0, import_path5.resolve)(root));
3519
+ const hash = (0, import_crypto3.createHash)("sha256");
3520
+ let bytes = 0;
3521
+ for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
3522
+ const content = await (0, import_promises6.readFile)(file.target);
3523
+ bytes += Buffer.byteLength(file.path) + content.byteLength;
3524
+ if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
3525
+ hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
3526
+ hash.update(content);
3527
+ }
3528
+ return `sha256:${hash.digest("hex")}`;
3529
+ }
3530
+ var SHA3 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
3531
+ var DIGEST3 = /^sha256:[0-9a-f]{64}$/;
3532
+ var ID3 = /^[A-Za-z0-9._:-]{1,160}$/;
3533
+ var RULE = /^[A-Za-z0-9_@+.,/-]{1,160}$/;
3534
+ var DEFAULT_PREFIXES = ["test/", "tests/", "__tests__/"];
3535
+ var DEFAULT_SUFFIXES = [".test.js", ".test.ts", ".test.tsx", ".spec.js", ".spec.ts", ".spec.tsx"];
3536
+ async function verifyCodeCandidate(input) {
3537
+ const policy = validate2(input);
3538
+ const limits = { maxFiles: policy.maximumFiles, maxBytes: policy.maximumBytes };
3539
+ const staged = await stageWorkspace(input.trustedBaseDir, limits);
3540
+ try {
3541
+ const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
3542
+ if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
3543
+ const paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
3544
+ await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
3545
+ const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
3546
+ const policyDigest = digestPolicy(policy);
3547
+ const patchDigest = digestBytes(input.candidatePatch);
3548
+ const candidateDigest = digestJson({
3549
+ trustedBaseCommitSha: input.trustedBaseCommitSha,
3550
+ trustedBaseDigest: input.trustedBaseDigest,
3551
+ patchDigest
3552
+ });
3553
+ const changedTests = changedTestPaths(paths, policy);
3554
+ if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
3555
+ const recipes = [];
3556
+ const logs = [];
3557
+ for (const recipe2 of policy.recipes) {
3558
+ const clean = await stageWorkspace(staged.workspaceDir, limits);
3559
+ try {
3560
+ if (await digestStagedWorkspace(clean.workspaceDir, limits) !== sourceDigest) {
3561
+ throw new TypeError("clean verifier source changed before execution");
3562
+ }
3563
+ const result = checkedResult(await input.recipeExecutor.run({
3564
+ workspaceDir: clean.workspaceDir,
3565
+ recipe: recipe2,
3566
+ signal: input.signal
3567
+ }), recipe2.maxOutputBytes);
3568
+ const artifacts = await inspectArtifacts(clean.workspaceDir, recipe2);
3569
+ recipes.push(recipeReceipt(recipe2, result, artifacts));
3570
+ logs.push({ recipeId: recipe2.id, ...boundedLogs(result, recipe2.maxOutputBytes) });
3571
+ } finally {
3572
+ await clean.cleanup();
3573
+ }
3574
+ }
3575
+ const fields = {
3576
+ schemaVersion: 1,
3577
+ verificationId: input.verificationId ?? `verify-${(0, import_crypto2.randomUUID)()}`,
3578
+ trustedBaseCommitSha: input.trustedBaseCommitSha,
3579
+ trustedBaseDigest: input.trustedBaseDigest,
3580
+ patchDigest,
3581
+ candidateDigest,
3582
+ sourceDigest,
3583
+ policyDigest,
3584
+ recipes,
3585
+ changedTestCount: changedTests.length,
3586
+ changedTestSetDigest: digestJson(changedTests),
3587
+ changedTestsRequireReview: changedTests.length > 0,
3588
+ outcome: recipes.every((recipe2) => recipe2.status === "passed") ? "passed" : "failed"
3589
+ };
3590
+ return {
3591
+ receipt: { ...fields, receiptDigest: await digestCodeVerificationReceipt(fields) },
3592
+ changedTests: Object.freeze(changedTests),
3593
+ logs: Object.freeze(logs)
3594
+ };
3595
+ } finally {
3596
+ await staged.cleanup();
3597
+ }
3598
+ }
3599
+ function validate2(input) {
3600
+ const policy = input.policy;
3601
+ 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) {
3602
+ throw new TypeError("clean verification input is malformed");
3603
+ }
3604
+ for (const recipe2 of policy.recipes) assertCodeBuildRecipe(recipe2);
3605
+ const result = {
3606
+ policyId: policy.policyId,
3607
+ recipes: policy.recipes,
3608
+ testPathPrefixes: policy.testPathPrefixes ?? DEFAULT_PREFIXES,
3609
+ testPathSuffixes: policy.testPathSuffixes ?? DEFAULT_SUFFIXES,
3610
+ maximumChangedTests: policy.maximumChangedTests ?? 1e3,
3611
+ maximumPatchBytes: policy.maximumPatchBytes ?? 256 * 1024,
3612
+ maximumFiles: policy.maximumFiles ?? 2e4,
3613
+ maximumBytes: policy.maximumBytes ?? 512 * 1024 * 1024
3614
+ };
3615
+ 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)) {
3616
+ throw new TypeError("clean verification policy exceeds its bounds");
3617
+ }
3618
+ return result;
3619
+ }
3620
+ function changedTestPaths(paths, policy) {
3621
+ return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
3622
+ }
3623
+ function recipeReceipt(recipe2, result, artifacts) {
3624
+ const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
3625
+ return {
3626
+ recipeId: recipe2.id,
3627
+ recipeDigest: digestRecipe(recipe2),
3628
+ status,
3629
+ exitCode: result.exitCode,
3630
+ durationMs: result.durationMs,
3631
+ artifacts
3632
+ };
3633
+ }
3634
+ async function inspectArtifacts(workspaceDir, recipe2) {
3635
+ const receipts = [];
3636
+ for (const artifact of recipe2.expectedArtifacts ?? []) {
3637
+ try {
3638
+ const path = (0, import_path4.join)(workspaceDir, artifact.path);
3639
+ const info = await (0, import_promises5.lstat)(path);
3640
+ if (!info.isFile() || info.isSymbolicLink()) {
3641
+ receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
3642
+ } else if (info.size > artifact.maximumBytes) {
3643
+ receipts.push({ artifactId: artifact.id, status: "too_large", bytes: info.size, digest: null });
3644
+ } else {
3645
+ receipts.push({ artifactId: artifact.id, status: "verified", bytes: info.size, digest: await hashFile(path) });
3646
+ }
3647
+ } catch (reason) {
3648
+ if (reason.code !== "ENOENT") throw reason;
3649
+ receipts.push({ artifactId: artifact.id, status: "missing", bytes: null, digest: null });
3650
+ }
3651
+ }
3652
+ return receipts;
3653
+ }
3654
+ function hashFile(path) {
3655
+ return new Promise((accept, reject) => {
3656
+ const hash = (0, import_crypto2.createHash)("sha256");
3657
+ const stream = (0, import_fs2.createReadStream)(path);
3658
+ stream.on("data", (chunk) => {
3659
+ hash.update(chunk);
3660
+ });
3661
+ stream.once("error", reject);
3662
+ stream.once("end", () => accept(`sha256:${hash.digest("hex")}`));
3663
+ });
3664
+ }
3665
+ function checkedResult(result, maximumOutputBytes) {
3666
+ 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") {
3667
+ throw new TypeError("recipe executor returned an invalid result");
3668
+ }
3669
+ const bytes = Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr);
3670
+ return bytes > maximumOutputBytes ? { ...result, outputLimitExceeded: true } : result;
3671
+ }
3672
+ function boundedLogs(result, maximum) {
3673
+ const stdout = Buffer.from(result.stdout);
3674
+ const stderr = Buffer.from(result.stderr);
3675
+ const first = stdout.subarray(0, maximum);
3676
+ return {
3677
+ stdout: first.toString("utf8"),
3678
+ stderr: stderr.subarray(0, Math.max(0, maximum - first.byteLength)).toString("utf8")
3679
+ };
3680
+ }
3681
+ function digestPolicy(policy) {
3682
+ return digestJson({
3683
+ policyId: policy.policyId,
3684
+ recipes: policy.recipes.map((recipe2) => normalizedRecipe(recipe2)),
3685
+ testPathPrefixes: [...policy.testPathPrefixes].sort(),
3686
+ testPathSuffixes: [...policy.testPathSuffixes].sort(),
3687
+ maximumChangedTests: policy.maximumChangedTests,
3688
+ maximumPatchBytes: policy.maximumPatchBytes,
3689
+ maximumFiles: policy.maximumFiles,
3690
+ maximumBytes: policy.maximumBytes
3691
+ });
3692
+ }
3693
+ function digestRecipe(recipe2) {
3694
+ return digestJson(normalizedRecipe(recipe2));
3695
+ }
3696
+ function normalizedRecipe(recipe2) {
3697
+ return {
3698
+ id: recipe2.id,
3699
+ image: recipe2.image,
3700
+ command: [...recipe2.command],
3701
+ timeoutMs: recipe2.timeoutMs,
3702
+ maxOutputBytes: recipe2.maxOutputBytes,
3703
+ cpus: recipe2.cpus ?? 1,
3704
+ memory: recipe2.memory ?? "1g",
3705
+ pids: recipe2.pids ?? 256,
3706
+ tmpfsBytes: recipe2.tmpfsBytes ?? 64 * 1024 * 1024,
3707
+ expectedArtifacts: [...recipe2.expectedArtifacts ?? []].sort((left, right) => left.id.localeCompare(right.id)).map((artifact) => ({ id: artifact.id, path: artifact.path, maximumBytes: artifact.maximumBytes }))
3708
+ };
3709
+ }
3710
+ function digestJson(value) {
3711
+ return digestBytes(JSON.stringify(value));
3712
+ }
3713
+ function digestBytes(value) {
3714
+ return `sha256:${(0, import_crypto2.createHash)("sha256").update(value).digest("hex")}`;
3715
+ }
3716
+ function integer(value, minimum, maximum) {
3717
+ return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
3718
+ }
3719
+ async function prepareRuntimeCheckpoint(input) {
3720
+ const patch2 = await input.workspace.patch(256 * 1024);
3721
+ let verification = null;
3722
+ let review = null;
3723
+ let note = patch2 ? "Candidate remains untrusted" : "Checkpoint has no source changes";
3724
+ if (patch2 && input.role === "coding") {
3725
+ try {
3726
+ const evidence = await verifyCodeCandidate({
3727
+ verificationId: `verify-${input.sessionId.slice("csess_".length)}`,
3728
+ trustedBaseDir: input.workspace.baselineDir,
3729
+ trustedBaseCommitSha: input.baseCommitSha,
3730
+ trustedBaseDigest: input.trustedBaseDigest,
3731
+ candidatePatch: patch2,
3732
+ policy: {
3733
+ policyId: "code.runtime",
3734
+ recipes: input.recipes,
3735
+ maximumFiles: 1e4,
3736
+ maximumBytes: 16 * 1024 * 1024
3737
+ },
3738
+ recipeExecutor: input.recipeExecutor
3739
+ });
3740
+ if (evidence.receipt.outcome === "passed") {
3741
+ verification = evidence.receipt;
3742
+ review = await input.review(patch2, verification);
3743
+ note = review.verdict === "approved" ? "Clean verification and independent review passed" : "Clean verification passed; independent review rejected the candidate";
3744
+ } else {
3745
+ note = `Clean verification failed: ${evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed").map((recipe2) => `${recipe2.recipeId}=${recipe2.status}`).join(", ")}`;
3746
+ }
3747
+ } catch (cause) {
3748
+ note = `Candidate verification or review failed closed: ${message(cause)}`;
3749
+ }
3750
+ }
3751
+ const reviewed = verification && review?.verdict === "approved";
3752
+ const checkpoint = await createCodeWorkspaceCheckpoint({
3753
+ workspace: input.workspace,
3754
+ baseCommitSha: input.baseCommitSha,
3755
+ state: {
3756
+ planCursor: null,
3757
+ conversationRefs: input.conversationRefs,
3758
+ planningInputDigest: input.planningInputDigest,
3759
+ buildPolicyDigest: verification?.policyDigest ?? input.fallbackPolicyDigest,
3760
+ dependencyLayerDigest: null,
3761
+ verificationDigest: verification?.receiptDigest ?? null,
3762
+ reviewDigest: reviewed && review ? review.reviewDigest : null,
3763
+ completedEffects: [],
3764
+ unresolvedApprovals: [],
3765
+ trustStatus: reviewed ? "reviewed" : verification ? "verified" : "candidate_untrusted"
3766
+ }
3767
+ });
3768
+ return { checkpoint, verification, review, note };
3769
+ }
3770
+ var message = (value) => (value instanceof Error ? value.message : String(value)).slice(0, 500);
3771
+ var CodeRuntimeCheckpointManager = class {
3772
+ constructor(options) {
3773
+ this.options = options;
3774
+ }
3775
+ options;
3776
+ #pending = /* @__PURE__ */ new Map();
3777
+ async prepare(command, active) {
3778
+ active.abort.abort("checkpoint_stop");
3779
+ await active.done;
3780
+ const prepared = await prepareRuntimeCheckpoint({
3781
+ sessionId: command.sessionId,
3782
+ role: active.role,
3783
+ workspace: active.workspace,
3784
+ baseCommitSha: active.baseCommitSha,
3785
+ trustedBaseDigest: active.trustedBaseDigest,
3786
+ planningInputDigest: active.planningInputDigest,
3787
+ conversationRefs: active.conversationRefs,
3788
+ fallbackPolicyDigest: this.options.fallbackPolicyDigest,
3789
+ recipes: this.options.recipes,
3790
+ recipeExecutor: this.options.recipeExecutor,
3791
+ review: (patch2, verification) => this.options.control.review(command.sessionId, { patch: patch2, verification })
3792
+ });
3793
+ if (prepared.review?.verdict === "approved" && prepared.verification) {
3794
+ this.#pending.set(command.commandId, { verification: prepared.verification, refs: active.conversationRefs });
3795
+ }
3796
+ await this.options.event(command, { type: "message", actor: "system", body: prepared.note }, active.conversationRefs).catch(() => void 0);
3797
+ await active.workspace.cleanup();
3798
+ await this.options.event(command, { type: "status", status: "checkpointed" }, active.conversationRefs).catch(() => void 0);
3799
+ return { status: "checkpointed", checkpoint: prepared.checkpoint, message: "Pi stopped at a portable checkpoint" };
3800
+ }
3801
+ async acknowledged(command, result) {
3802
+ if (command.kind !== "checkpoint_stop" || result.status !== "checkpointed") return false;
3803
+ const pending = this.#pending.get(command.commandId);
3804
+ if (!pending) return true;
3805
+ const checkpointId = `cpoint_${command.commandId.slice("ccmd_".length)}`;
3806
+ const candidate = await this.options.control.submitCandidate(command.sessionId, checkpointId, pending.verification);
3807
+ await this.options.event(command, {
3808
+ type: "message",
3809
+ actor: "system",
3810
+ body: `Candidate ${candidate.candidateId} is ready for exact owner publication approval`
3811
+ }, pending.refs);
3812
+ this.#pending.delete(command.commandId);
3813
+ return true;
3814
+ }
3815
+ };
3816
+ var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
3817
+ var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
3818
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os2.tmpdir)()) {
3819
+ if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
3820
+ const root = await (0, import_promises7.mkdtemp)((0, import_path6.join)(tempRoot, "odla-code-source-"));
3821
+ const sourceDir = (0, import_path6.join)(root, "source");
3822
+ await (0, import_promises7.mkdir)(sourceDir);
3823
+ const seen = /* @__PURE__ */ new Set();
3824
+ let bytes = 0;
3825
+ try {
3826
+ for (const file of snapshot.files) {
3827
+ validatePath(file.path);
3828
+ if (seen.has(file.path)) throw new TypeError("Code source repeats a path");
3829
+ seen.add(file.path);
3830
+ bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
3831
+ if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
3832
+ const target = (0, import_path6.resolve)(sourceDir, file.path);
3833
+ if (!target.startsWith(`${(0, import_path6.resolve)(sourceDir)}${import_path6.sep}`)) throw new TypeError("Code source path escapes its root");
3834
+ await (0, import_promises7.mkdir)((0, import_path6.dirname)(target), { recursive: true });
3835
+ await (0, import_promises7.writeFile)(target, file.content, { flag: "wx", mode: 420 });
3836
+ }
3837
+ return { sourceDir, cleanup: () => (0, import_promises7.rm)(root, { recursive: true, force: true }) };
3838
+ } catch (cause) {
3839
+ await (0, import_promises7.rm)(root, { recursive: true, force: true });
3840
+ throw cause;
3841
+ }
3842
+ }
3843
+ function validatePath(path) {
3844
+ const parts = path.split("/");
3845
+ if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
3846
+ throw new TypeError("Code source contains an unsafe path");
3847
+ }
3848
+ }
3849
+ var DESTINATIONS = "code-workspaces.v1";
3850
+ var READ = descriptor("sandbox.read", "scoped_data_read", {
3851
+ workspace: "destination",
3852
+ authority: "authority",
3853
+ path: "selector",
3854
+ startLine: "selector",
3855
+ endLine: "selector"
3856
+ });
3857
+ var PATCH = descriptor("sandbox.apply_patch", "reversible_mutation", {
3858
+ workspace: "destination",
3859
+ authority: "authority",
3860
+ patch: "payload"
3861
+ });
3862
+ var RECIPE = descriptor("sandbox.run_recipe", "code_execution", {
3863
+ workspace: "destination",
3864
+ authority: "authority",
3865
+ recipeId: "selector",
3866
+ sourceDigest: "payload"
3867
+ });
3868
+ function createCodePolicyGate(options) {
3869
+ return {
3870
+ read: async (input) => {
3871
+ const base = await environment(input, options, "sandbox.read");
3872
+ const conversions = await conversionRegistry([
3873
+ await registeredPolicy("code.path.v1", "code.paths.v1", input.paths),
3874
+ await conversionPolicy("code.line.v1", { kind: "integer", minimum: 1, maximum: 1e6 })
3875
+ ], { "code.paths.v1": input.paths });
3876
+ const path = await conversions.operations.registeredId(unsafe(base, input.path, "path"), "code.path.v1");
3877
+ const start = await conversions.operations.integer(unsafe(base, input.startLine, "start"), "code.line.v1");
3878
+ const end = await conversions.operations.integer(unsafe(base, input.endLine, "end"), "code.line.v1");
3879
+ if (end.value < start.value) return false;
3880
+ return authorize(input, options, base, READ, {
3881
+ ...base.fixedArgs,
3882
+ path: { role: "selector", value: path },
3883
+ startLine: { role: "selector", value: start },
3884
+ endLine: { role: "selector", value: end }
3885
+ }, [path, start, end]);
3886
+ },
3887
+ patch: async (input) => {
3888
+ const base = await environment(input, options, "sandbox.apply_patch");
3889
+ const patch2 = unsafe(base, input.patch, "patch");
3890
+ return authorize(input, options, base, PATCH, {
3891
+ ...base.fixedArgs,
3892
+ patch: { role: "payload", value: patch2 }
3893
+ }, []);
3894
+ },
3895
+ recipe: async (input) => {
3896
+ const base = await environment(input, options, "sandbox.run_recipe");
3897
+ const conversions = await conversionRegistry([
3898
+ await registeredPolicy("code.recipe.v1", "code.recipes.v1", input.recipeIds)
3899
+ ], { "code.recipes.v1": input.recipeIds });
3900
+ const recipe2 = await conversions.operations.registeredId(unsafe(base, input.recipeId, "recipe"), "code.recipe.v1");
3901
+ const source = unsafe(base, input.sourceDigest, "source");
3902
+ return authorize(input, options, base, RECIPE, {
3903
+ ...base.fixedArgs,
3904
+ recipeId: { role: "selector", value: recipe2 },
3905
+ sourceDigest: { role: "payload", value: source }
3906
+ }, [recipe2]);
3907
+ }
3908
+ };
3909
+ }
3910
+ function descriptor(name, effect, argumentRoles) {
3911
+ return { name, version: 1, effect, inputSchema: { type: "object" }, argumentRoles, policyId: `odla.code.${name}.v1` };
3912
+ }
3913
+ async function conversionPolicy(id, output) {
3914
+ const definition = {
3915
+ conversionId: id,
3916
+ version: 1,
3917
+ output,
3918
+ maximumSourceBytes: 1e6,
3919
+ maximumOutputsPerArtifact: 4,
3920
+ presentation: "json_scalar"
3921
+ };
3922
+ return { ...definition, digest: await conversionPolicyDigest(definition) };
3923
+ }
3924
+ async function registeredPolicy(id, registryId, values) {
3925
+ const mapping = Object.fromEntries(values.map((value) => [value, value]));
3926
+ return conversionPolicy(id, {
3927
+ kind: "registered_id",
3928
+ registryId,
3929
+ registryDigest: await registeredIdRegistryDigest(mapping)
3930
+ });
3931
+ }
3932
+ async function conversionRegistry(policies, values) {
3933
+ const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id, entries]) => {
3934
+ const mapping = Object.fromEntries(entries.map((value) => [value, value]));
3935
+ return [id, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
3936
+ })));
3937
+ return createConversionRegistry({ policies, registeredIds });
3938
+ }
3939
+ async function environment(input, options, tool) {
3940
+ const ingress = createCamelIngress([
3941
+ { id: "workspace", value: input.workspaceId, readers: input.readers },
3942
+ { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
3943
+ { id: "reader", value: options.readerId, readers: input.readers }
3944
+ ]);
3945
+ const digest2 = await destinationRegistryDigest([input.workspaceId]);
3946
+ const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
3947
+ if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
3948
+ const policy = createEffectPolicy({
3949
+ destinationRegistries: { [DESTINATIONS]: { digest: digest2, values: [input.workspaceId] } },
3950
+ approvalEffects: approvals
3951
+ });
3952
+ const fixedArgs = {
3953
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest2 },
3954
+ authority: { role: "authority", value: ingress.control("authority") }
3955
+ };
3956
+ return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
3957
+ }
3958
+ function unsafe(base, value, field) {
3959
+ return base.ingress.quarantinedOutput(value, {
3960
+ readers: base.reader.label.readers,
3961
+ runId: `${base.runId}:${field}`
3962
+ });
3963
+ }
3964
+ async function authorize(input, options, base, tool, args, controlDependencies) {
3965
+ const policy = await base.policy.evaluate({
3966
+ planId: input.lease.task.taskId,
3967
+ tool,
3968
+ args,
3969
+ controlDependencies,
3970
+ intendedReaderIds: [base.reader]
3971
+ });
3972
+ let approvalConsumed = false;
3973
+ if (policy.outcome === "require_approval" && options.consumeApproval) {
3974
+ approvalConsumed = await options.consumeApproval(decision(input, policy, false, tool.name, policy.actionDigest));
3975
+ }
3976
+ await options.onDecision?.(decision(input, policy, approvalConsumed, tool.name));
3977
+ return policy.outcome === "allow" || approvalConsumed;
3978
+ }
3979
+ function decision(input, policy, approvalConsumed, tool, actionDigest) {
3980
+ return {
3981
+ lease: input.lease,
3982
+ request: input.request,
3983
+ tool,
3984
+ policy,
3985
+ approvalConsumed,
3986
+ actionDigest: actionDigest ?? (policy.outcome === "require_approval" ? policy.actionDigest : "")
3987
+ };
3988
+ }
3989
+ function createCodeToolBroker(options) {
3990
+ validateOptions(options);
3991
+ const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
3992
+ const policy = createCodePolicyGate(options);
3993
+ let tail = Promise.resolve();
3994
+ return {
3995
+ execute(context, request) {
3996
+ const result = tail.then(() => route(context, request, options, recipes, policy));
3997
+ tail = result.then(() => void 0, () => void 0);
3998
+ return result;
3999
+ }
4000
+ };
4001
+ }
4002
+ async function route(context, request, options, recipes, policy) {
4003
+ try {
4004
+ if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
4005
+ if (request.tool === "sandbox.read") return await read(context, request, options, policy);
4006
+ if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy);
4007
+ return await recipe(context, request, options, recipes, policy);
4008
+ } catch (reason) {
4009
+ return response(request, false, reason instanceof TypeError ? reason.message : "tool failed closed");
4010
+ }
4011
+ }
4012
+ async function read(context, request, options, policy) {
4013
+ exactKeys(request.input, ["path", "startLine", "endLine"]);
4014
+ const path = stringField(request.input, "path");
4015
+ const startLine = optionalInteger(request.input.startLine) ?? 1;
4016
+ const endLine = optionalInteger(request.input.endLine) ?? startLine + (options.maxReadLines ?? 2e3) - 1;
4017
+ if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
4018
+ throw new TypeError("requested line range exceeds its bound");
4019
+ }
4020
+ const paths = await registeredFiles(context.workspaceDir, 2e4);
4021
+ const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
4022
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4023
+ const target = resolveCodePath(context.workspaceDir, path);
4024
+ const info = await (0, import_promises8.stat)(target);
4025
+ if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
4026
+ throw new TypeError("file is not a bounded regular source file");
4027
+ }
4028
+ const source = await (0, import_promises8.readFile)(target);
4029
+ if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
4030
+ const lines = source.toString("utf8").split("\n");
4031
+ const content = lines.slice(startLine - 1, endLine).join("\n");
4032
+ if (Buffer.byteLength(content) > (options.maxReadBytes ?? 128 * 1024)) {
4033
+ throw new TypeError("read result exceeds its byte bound");
4034
+ }
4035
+ return response(request, true, content, { path, startLine, endLine: Math.min(endLine, lines.length) });
4036
+ }
4037
+ async function patch(context, request, options, policy) {
4038
+ exactKeys(request.input, ["patch"]);
4039
+ const value = stringField(request.input, "patch");
4040
+ const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
4041
+ const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
4042
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4043
+ await applyCodePatch(context.workspaceDir, value, paths);
4044
+ return response(request, true, `Applied patch to ${paths.length} file(s).`, { paths });
4045
+ }
4046
+ async function recipe(context, request, options, recipes, policy) {
4047
+ exactKeys(request.input, ["recipeId"]);
4048
+ const recipeId = stringField(request.input, "recipeId");
4049
+ const digestLimits = {
4050
+ maxFiles: options.maxRecipeWorkspaceFiles ?? 2e4,
4051
+ maxBytes: options.maxRecipeWorkspaceBytes ?? 512 * 1024 * 1024
4052
+ };
4053
+ const sourceDigest = await digestStagedWorkspace(context.workspaceDir, digestLimits);
4054
+ const allowed = await policy.recipe(policyContext(context, request, options, {
4055
+ recipeIds: [...recipes.keys()].sort(),
4056
+ recipeId,
4057
+ sourceDigest
4058
+ }));
4059
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
4060
+ const selected = recipes.get(recipeId);
4061
+ if (!selected) return response(request, false, "build recipe is not registered");
4062
+ const staged = await stageWorkspace(context.workspaceDir, {
4063
+ maxFiles: digestLimits.maxFiles,
4064
+ maxBytes: digestLimits.maxBytes
4065
+ });
4066
+ try {
4067
+ if (await digestStagedWorkspace(staged.workspaceDir, digestLimits) !== sourceDigest) {
4068
+ throw new TypeError("workspace changed after recipe authorization");
4069
+ }
4070
+ const result = await options.recipeExecutor.run({
4071
+ workspaceDir: staged.workspaceDir,
4072
+ recipe: selected,
4073
+ signal: context.signal
4074
+ });
4075
+ const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
4076
+ const ok = result.exitCode === 0 && !result.outputLimitExceeded && !result.timedOut;
4077
+ const status = result.timedOut ? "timed out" : result.outputLimitExceeded ? "exceeded output limit" : ok ? "passed" : `failed with exit ${result.exitCode}`;
4078
+ return response(request, ok, `Recipe ${recipeId} ${status}.${output ? `
4079
+ ${output}` : ""}`, {
4080
+ recipeId,
4081
+ exitCode: result.exitCode,
4082
+ durationMs: result.durationMs,
4083
+ outputLimitExceeded: result.outputLimitExceeded,
4084
+ timedOut: result.timedOut
4085
+ });
4086
+ } finally {
4087
+ await staged.cleanup();
4088
+ }
4089
+ }
4090
+ function policyContext(context, request, options, extra) {
4091
+ return {
4092
+ lease: context.lease,
4093
+ request,
4094
+ workspaceId: `workspace:${context.lease.task.attemptId}`,
4095
+ readers: { kind: "principals", principalIds: [options.readerId] },
4096
+ ...extra
4097
+ };
4098
+ }
4099
+ async function registeredFiles(root, limit) {
4100
+ const paths = [];
4101
+ const walk = async (directory) => {
4102
+ for (const entry of await (0, import_promises8.readdir)(directory, { withFileTypes: true })) {
4103
+ if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
4104
+ const target = (0, import_path7.resolve)(directory, entry.name);
4105
+ if (entry.isDirectory()) await walk(target);
4106
+ else if (entry.isFile()) {
4107
+ const path = (0, import_path7.relative)(root, target).split("\\").join("/");
4108
+ try {
4109
+ validateRelativePath(path);
4110
+ } catch {
4111
+ continue;
4112
+ }
4113
+ paths.push(path);
4114
+ if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
4115
+ }
4116
+ }
4117
+ };
4118
+ await walk((0, import_path7.resolve)(root));
4119
+ return paths.sort();
4120
+ }
4121
+ function validateOptions(options) {
4122
+ if (!options.readerId || !options.recipes.length || new Set(options.recipes.map((item) => item.id)).size !== options.recipes.length) {
4123
+ throw new TypeError("Code tool broker requires a reader and unique registered recipes");
4124
+ }
4125
+ for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
4126
+ }
4127
+ function exactKeys(input, allowed) {
4128
+ if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
4129
+ }
4130
+ function stringField(input, name) {
4131
+ const value = input[name];
4132
+ if (typeof value !== "string" || !value) throw new TypeError(`${name} must be a non-empty string`);
4133
+ return value;
4134
+ }
4135
+ function optionalInteger(value) {
4136
+ if (value === void 0) return void 0;
4137
+ if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("line bounds must be positive integers");
4138
+ return value;
4139
+ }
4140
+ function response(request, ok, content, details) {
4141
+ return { requestId: request.requestId, ok, content, ...details ? { details } : {} };
4142
+ }
4143
+ function codeCommandMetadata(payload, resume) {
4144
+ const trusted = record22(payload.trustedBase);
4145
+ const role = payload.role;
4146
+ const title = payload.title;
4147
+ const prompt = payload.prompt;
4148
+ if (role !== "coding" && role !== "review" || typeof title !== "string" || typeof prompt !== "string") {
4149
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} metadata`);
4150
+ }
4151
+ const planning = trusted?.planningInputDigest;
4152
+ const attestation = trusted?.attestationDigest;
4153
+ return {
4154
+ role,
4155
+ title,
4156
+ prompt,
4157
+ planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
4158
+ attestationDigest: typeof attestation === "string" ? attestation : "resume"
4159
+ };
4160
+ }
4161
+ function codeCheckpointPayload(payload) {
4162
+ const value = payload.checkpoint;
4163
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
4164
+ return value;
4165
+ }
4166
+ function fakeCodeLease(command, metadata2) {
4167
+ return {
4168
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
4169
+ leaseId: `code:${command.commandId}`,
4170
+ generation: command.bindingGeneration,
4171
+ expiresAt: Date.now() + 24 * 60 * 6e4,
4172
+ task: {
4173
+ taskId: command.sessionId,
4174
+ attemptId: command.instanceId,
4175
+ title: metadata2.title,
4176
+ prompt: metadata2.prompt,
4177
+ workspace: command.appId,
4178
+ aiRoute: metadata2.role,
4179
+ policy: {
4180
+ network: "none",
4181
+ timeoutMs: 30 * 6e4,
4182
+ maxOutputBytes: 4 * 1024 * 1024,
4183
+ maxPatchBytes: 256 * 1024
4184
+ }
4185
+ }
4186
+ };
4187
+ }
4188
+ var record22 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4189
+ var CodePiRuntimeEngine = class {
4190
+ constructor(options) {
4191
+ this.options = options;
4192
+ this.#run = options.runAttempt ?? runContainerAttempt;
4193
+ this.#buildPolicyDigest = digest(JSON.stringify(options.recipes));
4194
+ this.#checkpoints = new CodeRuntimeCheckpointManager({
4195
+ control: options.control,
4196
+ recipes: options.recipes,
4197
+ recipeExecutor: options.recipeExecutor ?? createContainerRecipeExecutor(options.engine),
4198
+ fallbackPolicyDigest: this.#buildPolicyDigest,
4199
+ event: (command, event, refs) => this.#event(command, event, refs)
4200
+ });
4201
+ }
4202
+ options;
4203
+ #active = /* @__PURE__ */ new Map();
4204
+ #run;
4205
+ #buildPolicyDigest;
4206
+ #checkpoints;
4207
+ execute(command) {
4208
+ if (command.kind === "checkpoint_stop") return this.#checkpoint(command);
4209
+ if (command.kind === "prompt") return this.#prompt(command);
4210
+ return this.#start(command, command.kind === "resume");
4211
+ }
4212
+ async acknowledged(command, result) {
4213
+ if (await this.#checkpoints.acknowledged(command, result)) return;
4214
+ const active = this.#active.get(command.sessionId);
4215
+ if (!active || result.status !== "running") return;
4216
+ active.acknowledged = true;
4217
+ if (active.failure) await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
4218
+ }
4219
+ async close() {
4220
+ const sessions = [...this.#active.values()];
4221
+ for (const session of sessions) session.abort.abort("runtime_shutdown");
4222
+ await Promise.allSettled(sessions.map((session) => session.done));
4223
+ await Promise.allSettled(sessions.map((session) => session.workspace.cleanup()));
4224
+ this.#active.clear();
4225
+ }
4226
+ async #start(command, resume) {
4227
+ if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
4228
+ const source = await this.options.control.source(command.sessionId);
4229
+ const materialized = await materializeCodeRuntimeSource(source);
4230
+ let workspace;
4231
+ try {
4232
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
4233
+ trustedBaseDir: materialized.sourceDir,
4234
+ trustedBaseCommitSha: source.commitSha,
4235
+ checkpoint: codeCheckpointPayload(command.payload)
4236
+ })).workspace : await stageWorkspace(materialized.sourceDir);
4237
+ } finally {
4238
+ await materialized.cleanup();
4239
+ }
4240
+ const metadata2 = codeCommandMetadata(command.payload, resume);
4241
+ const abort = new AbortController();
4242
+ const conversationRefs = [];
4243
+ const active = {
4244
+ workspace,
4245
+ abort,
4246
+ conversationRefs,
4247
+ acknowledged: false,
4248
+ role: metadata2.role,
4249
+ title: metadata2.title,
4250
+ baseCommitSha: source.commitSha,
4251
+ trustedBaseDigest: await digestStagedWorkspace(workspace.baselineDir, {
4252
+ maxFiles: 1e4,
4253
+ maxBytes: 16 * 1024 * 1024
4254
+ }),
4255
+ planningInputDigest: metadata2.planningInputDigest ?? digest(
4256
+ JSON.stringify({ attestation: metadata2.attestationDigest, prompt: metadata2.prompt, tree: source.treeDigest })
4257
+ ),
4258
+ done: Promise.resolve(null)
4259
+ };
4260
+ this.#active.set(command.sessionId, active);
4261
+ active.done = this.#runAttempt(command, metadata2, active).catch(async (cause) => {
4262
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` }, conversationRefs).catch(() => void 0);
4263
+ await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
4264
+ await this.#failure(command, active, message2(cause));
4265
+ return null;
4266
+ });
4267
+ return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
4268
+ }
4269
+ async #prompt(command) {
4270
+ const active = this.#active.get(command.sessionId);
4271
+ const prompt = command.payload.prompt;
4272
+ if (!active || typeof prompt !== "string" || !prompt.trim() || prompt.length > 2e4) {
4273
+ throw new TypeError("prompt requires an active Code session and bounded text");
4274
+ }
4275
+ await active.done;
4276
+ active.abort = new AbortController();
4277
+ active.acknowledged = false;
4278
+ active.failure = void 0;
4279
+ active.done = this.#runAttempt(command, {
4280
+ role: active.role,
4281
+ title: active.title,
4282
+ prompt,
4283
+ planningInputDigest: active.planningInputDigest,
4284
+ attestationDigest: "follow-up"
4285
+ }, active).catch(async (cause) => {
4286
+ await this.#event(
4287
+ command,
4288
+ { type: "message", actor: "system", body: `Pi failed: ${message2(cause)}` },
4289
+ active.conversationRefs
4290
+ ).catch(() => void 0);
4291
+ await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
4292
+ await this.#failure(command, active, message2(cause));
4293
+ return null;
4294
+ });
4295
+ return { status: "running", message: "Pi accepted the owner prompt" };
4296
+ }
4297
+ async #runAttempt(command, metadata2, active) {
4298
+ const lease = fakeCodeLease(command, metadata2);
4299
+ const broker = this.#toolBroker(lease, metadata2.role);
4300
+ const startedAt = Date.now();
4301
+ let completionSeen = false;
4302
+ const result = await this.#run({
4303
+ engine: this.options.engine,
4304
+ image: this.options.image,
4305
+ workspaceDir: active.workspace.workspaceDir,
4306
+ workspaceAccess: "none",
4307
+ task: lease.task,
4308
+ limits: this.options.limits,
4309
+ signal: active.abort.signal,
4310
+ onStderr: (text) => this.#event(command, {
4311
+ type: "message",
4312
+ actor: "system",
4313
+ body: text.slice(0, 4e3)
4314
+ }, active.conversationRefs),
4315
+ onMessage: async (output) => {
4316
+ if (output.type === "inference.request") {
4317
+ const inferenceStarted = Date.now();
4318
+ const response2 = await this.options.control.infer(command.sessionId, {
4319
+ requestId: output.requestId,
4320
+ call: output.call
4321
+ });
4322
+ await this.#event(command, {
4323
+ type: "usage",
4324
+ provider: response2.receipt.provider,
4325
+ model: response2.receipt.model,
4326
+ inputTokens: response2.receipt.inputTokens,
4327
+ outputTokens: response2.receipt.outputTokens,
4328
+ durationMs: Date.now() - inferenceStarted
4329
+ }, active.conversationRefs).catch(() => void 0);
4330
+ return {
4331
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
4332
+ type: "inference.response",
4333
+ requestId: output.requestId,
4334
+ response: response2.response
4335
+ };
4336
+ }
4337
+ if (output.type === "tool.request") {
4338
+ const toolStarted = Date.now();
4339
+ await this.#event(
4340
+ command,
4341
+ { type: "tool", phase: "started", tool: output.tool },
4342
+ active.conversationRefs
4343
+ ).catch(() => void 0);
4344
+ const response2 = await broker.execute({
4345
+ lease,
4346
+ workspaceDir: active.workspace.workspaceDir,
4347
+ signal: active.abort.signal
4348
+ }, output);
4349
+ await this.#event(command, {
4350
+ type: "tool",
4351
+ phase: "completed",
4352
+ tool: output.tool,
4353
+ ok: response2.ok,
4354
+ durationMs: Date.now() - toolStarted
4355
+ }, active.conversationRefs).catch(() => void 0);
4356
+ return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
4357
+ }
4358
+ if (output.type === "event") {
4359
+ const payload = object2(output.payload);
4360
+ if (output.kind === "pi.started") {
4361
+ await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
4362
+ } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
4363
+ await this.#event(command, {
4364
+ type: "thinking",
4365
+ available: true,
4366
+ durationMs: Math.min(Number(payload.durationMs), 864e5)
4367
+ }, active.conversationRefs);
4368
+ } else {
4369
+ await this.#event(command, {
4370
+ type: "message",
4371
+ actor: "system",
4372
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${json(output.payload)}`}`
4373
+ }, active.conversationRefs);
4374
+ }
4375
+ } else if (output.type === "attempt.complete") {
4376
+ completionSeen = true;
4377
+ const body = resultText(output.result) ?? `Pi ${output.status}.`;
4378
+ await this.#event(command, {
4379
+ type: "message",
4380
+ actor: output.status === "completed" ? "agent" : "system",
4381
+ body
4382
+ }, active.conversationRefs);
4383
+ await this.#event(command, {
4384
+ type: "status",
4385
+ status: output.status === "completed" ? "idle" : "failed",
4386
+ durationMs: Date.now() - startedAt
4387
+ }, active.conversationRefs);
4388
+ }
4389
+ }
4390
+ });
4391
+ if (result.status === "failed" && result.stderr) {
4392
+ await this.#event(command, { type: "message", actor: "system", body: result.stderr.slice(0, 4e3) }, active.conversationRefs);
4393
+ }
4394
+ if (!completionSeen) await this.#event(command, {
4395
+ type: "status",
4396
+ status: result.status === "completed" ? "idle" : "failed",
4397
+ durationMs: Date.now() - startedAt
4398
+ }, active.conversationRefs).catch(() => void 0);
4399
+ if (result.status === "failed") {
4400
+ await this.#failure(command, active, result.stderr || "Pi container failed");
4401
+ }
4402
+ return result;
4403
+ }
4404
+ #toolBroker(lease, role) {
4405
+ const broker = createCodeToolBroker({
4406
+ recipes: this.options.recipes,
4407
+ recipeExecutor: createContainerRecipeExecutor(this.options.engine),
4408
+ recipeAuthorization: this.options.recipeAuthorization ?? "registered_recipe",
4409
+ readerId: `code-session:${lease.task.taskId}`
4410
+ });
4411
+ 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" }) };
4412
+ }
4413
+ async #checkpoint(command) {
4414
+ const active = this.#active.get(command.sessionId);
4415
+ if (!active) throw new TypeError("Code session workspace is not active on this runtime");
4416
+ const result = await this.#checkpoints.prepare(command, active);
4417
+ this.#active.delete(command.sessionId);
4418
+ return result;
4419
+ }
4420
+ async #failure(command, active, value) {
4421
+ active.failure = value.slice(0, 2e3);
4422
+ if (active.acknowledged) {
4423
+ await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
4424
+ }
4425
+ }
4426
+ async #event(command, event, refs) {
4427
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
4428
+ refs.push(eventId);
4429
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
4430
+ await this.options.control.appendSessionEvent(command.sessionId, eventId, bounded);
4431
+ }
4432
+ };
4433
+ var object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
4434
+ var digest = (value) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value).digest("hex")}`;
4435
+ var message2 = (value) => value instanceof Error ? value.message : String(value);
4436
+ var json = (value) => {
4437
+ try {
4438
+ return JSON.stringify(value).slice(0, 1e4);
4439
+ } catch {
4440
+ return "[event]";
4441
+ }
4442
+ };
4443
+ function resultText(value) {
4444
+ const record32 = object2(value);
4445
+ return record32 && typeof record32.text === "string" ? record32.text.slice(0, 2e4) : null;
4446
+ }
4447
+
4448
+ // src/help.ts
4449
+ var import_node_fs6 = require("fs");
4450
+ function cliVersion() {
4451
+ const pkg = JSON.parse((0, import_node_fs6.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
4452
+ return pkg.version ?? "unknown";
4453
+ }
4454
+ function printHelp() {
4455
+ console.log(`odla-ai
4456
+
4457
+ Usage:
4458
+ odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
4459
+ odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
4460
+ odla-ai doctor [--config odla.config.mjs]
4461
+ odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
4462
+ odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
4463
+ odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
4464
+ odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
4465
+ odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
4466
+ odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
4467
+ odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
4468
+ odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
4469
+ odla-ai app owners add <email> [--email <odla-account>] [--json]
4470
+ odla-ai app owners remove <email> [--email <odla-account>] [--json]
4471
+ odla-ai capabilities [--json]
4472
+ odla-ai code connect [--env dev|prod] [--email <odla-account>] [--engine auto|container|podman|docker] [--slots <1-64>] [--once]
4473
+ odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
4474
+ odla-ai admin ai models [--provider <id>] [--json]
4475
+ odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
4476
+ odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
4477
+ --validation-provider <id> --validation-model <id> [--enabled|--no-enabled]
4478
+ odla-ai admin ai credentials [--json]
4479
+ odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
4480
+ odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
4481
+ odla-ai admin ai audit [--limit <1-200>] [--json]
4482
+ odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
4483
+ odla-ai security github disconnect --source <id> [--env dev] [--yes]
4484
+ odla-ai security plan [--env dev] [--json]
4485
+ odla-ai security sources [--env dev] [--json]
4486
+ odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
4487
+ odla-ai security status <job-id> [--follow] [--json]
4488
+ odla-ai security report <job-id> [--json]
4489
+ odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
4490
+ odla-ai security run [target] --self --ack-redacted-source
4491
+ odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
4492
+ odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
4493
+ odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
4494
+ odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
4495
+ odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
4496
+ odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
4497
+ odla-ai version
4498
+
4499
+ Commands:
4500
+ setup Install offline odla runbooks for common coding-agent harnesses.
4501
+ init Create a generic odla.config.mjs plus starter schema/rules files.
4502
+ doctor Validate and summarize the project config without network calls.
4503
+ calendar Inspect, connect, or disconnect the live Google booking connection.
4504
+ app Archive (suspend, data retained), restore, export, or manage the
4505
+ co-owners of the app. Archiving takes every environment's data
4506
+ plane down until restored; permanent deletion has NO CLI \u2014 it
4507
+ requires a signed-in owner in Studio, and no machine or agent
4508
+ credential can purge. "app export" downloads a portable
4509
+ gzipped-JSONL snapshot of one environment's database (--fresh
4510
+ takes a new snapshot first) \u2014 your data is always yours to take.
4511
+ "app owners add <email>" grants a signed-up odla member the SAME
4512
+ full access as you; they then run their own "provision" to mint
4513
+ their own credentials for the shared db (prod included) \u2014 no
4514
+ secret is ever copied between people.
4515
+ capabilities Show what the CLI automates vs agent edits and human checkpoints.
4516
+ code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
4517
+ admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
4518
+ security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
4519
+ provision Register services, compose integrations, persist credentials, optionally push secrets.
4520
+ smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
4521
+ skill Same installer; --agent accepts all, claude, codex, cursor,
4522
+ copilot, gemini, or agents (repeatable or comma-separated).
4523
+ secrets Push configured db/o11y secrets into the Worker via wrangler
4524
+ stdin; set stores a tenant-vault secret and set-clerk-key the
4525
+ reserved Clerk secret key, write-only from stdin or an env var.
4526
+ version Print the CLI version.
4527
+
4528
+ Safety:
4529
+ New projects target dev only. Add prod explicitly to odla.config.mjs and pass
4530
+ --yes to provision it; use --dry-run first to inspect the resolved plan.
4531
+ Provision caches the approved developer token and service credentials under
4532
+ .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
4533
+ preflights Wrangler before any shown-once issuance or destructive rotation.
4534
+ Provision opens the approval page in your browser automatically whenever the
4535
+ machine can show one, including agent-driven runs; only CI, SSH, and
4536
+ display-less hosts skip it. Use --open to force or --no-open to suppress.
4537
+ Browser launch is best-effort: the printed approval URL is authoritative and
4538
+ agents must relay it to the human verbatim. A started handshake is persisted
4539
+ under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
4540
+ the same code. Outside an interactive terminal the wait is capped (90s by
4541
+ default, --wait <seconds> to change); a still-pending handshake then exits
4542
+ with code 75: relay the URL, wait for approval, and re-run to collect.
4543
+ A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
4544
+ The email is a non-secret identity hint: never provide a password or session
4545
+ token. The matching account must already exist, be signed in, explicitly
4546
+ review the exact code, and finish any current request before claiming another.
4547
+ Run Code from a GitHub checkout already connected to an app in Studio; an
4548
+ odla.config.mjs may select the app explicitly but is not required. Code host
4549
+ approval and credential hashes live in odla-ai/db. The host
4550
+ credential is never written under .odla/; it exists only in the foreground
4551
+ "code connect" process and is rotated by the next approved connection.
4552
+ Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
4553
+ OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
4554
+ GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
4555
+ for a PAT or provider key. GitHub read access is separate from the explicit
4556
+ --ack-redacted-source consent and the exact --plan-digest printed by security
4557
+ plan are required before bounded snippets reach System AI.
4558
+ Run security plan first to inspect the admin-selected providers, models,
4559
+ per-route bounds, credential readiness, retention, no-execution boundary,
4560
+ and digest that binds consent to that exact plan.
4561
+ `);
4562
+ }
4563
+
4564
+ // src/security-hosted-github.ts
4565
+ var import_node_child_process2 = require("child_process");
4566
+ var import_node_util = require("util");
4567
+
4568
+ // src/security-hosted-request.ts
4569
+ async function requestHostedSecurityJson(options, path, init, action) {
4570
+ const platform = platformAudience(options.platform);
4571
+ const token = hostedSecurityCredential(options.token);
4572
+ const requestInit = {
4573
+ ...init,
4574
+ redirect: "error",
4575
+ credentials: "omit",
4576
+ cache: "no-store",
4577
+ signal: options.signal,
4578
+ headers: {
4579
+ authorization: `Bearer ${token}`,
4580
+ "content-type": "application/json",
4581
+ ...init.headers
4582
+ }
4583
+ };
4584
+ const response2 = await (options.fetch ?? fetch)(new URL(path, platform), requestInit);
4585
+ const body = await response2.json().catch(() => ({}));
4586
+ if (!response2.ok) {
4587
+ const code = optionalHostedText(body.error?.code, "error code", 128);
4588
+ const message3 = optionalHostedText(body.error?.message, "error message", 300);
4589
+ throw new Error(`${action} failed (${response2.status})${code ? ` ${code}` : ""}${message3 ? `: ${message3}` : ""}`);
4590
+ }
4591
+ return body;
4592
+ }
4593
+ function hostedIdentifier(value, name) {
4594
+ const result = optionalHostedText(value, name, 180);
4595
+ if (!result || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(result)) {
4596
+ throw new Error(`${name} is invalid`);
4597
+ }
4598
+ return result;
4599
+ }
4600
+ function positivePolicyVersion(value, name) {
4601
+ if (!Number.isSafeInteger(value) || value < 1) {
4602
+ throw new Error(`${name} is invalid`);
4603
+ }
4604
+ return value;
4605
+ }
4606
+ function optionalHostedText(value, name, maxLength) {
4607
+ if (value === void 0 || value === null || value === "") return void 0;
4608
+ if (typeof value !== "string" || value.length > maxLength || /[\u0000-\u001f\u007f]/.test(value)) {
4609
+ throw new Error(`${name} is invalid`);
4610
+ }
4611
+ return value;
4612
+ }
4613
+ function githubRepositoryName(value) {
4614
+ if (typeof value !== "string" || value.length > 201) {
4615
+ throw new Error("repository must be owner/name");
4616
+ }
4617
+ const parts = value.split("/");
4618
+ const owner = parts[0] ?? "";
4619
+ const name = (parts[1] ?? "").replace(/\.git$/i, "");
4620
+ 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 === "..") {
4621
+ throw new Error("repository must be owner/name");
4622
+ }
4623
+ return `${owner}/${name}`;
4624
+ }
4625
+ function trustedGitHubInstallUrl(value) {
4626
+ let url;
4627
+ try {
4628
+ url = new URL(value);
4629
+ } catch {
4630
+ throw new Error("odla.ai returned an invalid GitHub installation URL");
4631
+ }
4632
+ if (url.protocol !== "https:" || url.hostname !== "github.com" || url.username || url.password) {
4633
+ throw new Error("odla.ai returned an untrusted GitHub installation URL");
4634
+ }
4635
+ return url.toString();
4636
+ }
4637
+ function hostedPollInterval(value = 2e3) {
4638
+ if (!Number.isSafeInteger(value) || value < 100 || value > 3e4) {
4639
+ throw new Error("poll interval must be 100-30000ms");
4640
+ }
4641
+ return value;
4642
+ }
4643
+ function hostedPollTimeout(value = 10 * 6e4) {
4644
+ if (!Number.isSafeInteger(value) || value < 1e3 || value > 60 * 6e4) {
4645
+ throw new Error("poll timeout must be 1000-3600000ms");
4646
+ }
4647
+ return value;
4648
+ }
4649
+ async function waitForHostedPoll(milliseconds, signal) {
4650
+ if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
4651
+ await new Promise((resolve10, reject) => {
4652
+ const timer = setTimeout(resolve10, milliseconds);
4653
+ signal?.addEventListener("abort", () => {
4654
+ clearTimeout(timer);
4655
+ reject(signal.reason ?? new DOMException("aborted", "AbortError"));
4656
+ }, { once: true });
4657
+ });
4658
+ }
4659
+ function isValidHostedSecurityPlan(value, env) {
4660
+ 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;
4661
+ 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;
4662
+ return validRoute(value.routes?.discovery, "security.discovery") && validRoute(value.routes?.validation, "security.validation");
4663
+ }
4664
+ function hostedSecurityCredential(value) {
4665
+ if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
4666
+ throw new Error("Hosted security requires an injected odla developer token");
4667
+ }
4668
+ return value;
4669
+ }
4670
+
4671
+ // src/security-hosted-github.ts
4672
+ async function connectGitHubSecuritySource(options) {
4673
+ const out = options.stdout ?? console;
4674
+ const repository = githubRepositoryName(options.repository);
4675
+ const attempt = await requestHostedSecurityJson(
4676
+ options,
4677
+ "/registry/github/connect",
4678
+ {
4679
+ method: "POST",
4680
+ body: JSON.stringify({
4681
+ appId: hostedIdentifier(options.appId, "appId"),
4682
+ env: hostedIdentifier(options.env, "env"),
4683
+ repository
4684
+ })
4685
+ },
4686
+ "start GitHub connection"
4687
+ );
4688
+ const serverExpiry = Date.parse(attempt?.expiresAt ?? "");
4689
+ if (!attempt?.attemptId || !attempt.installUrl || !Number.isFinite(serverExpiry)) {
4690
+ throw new Error("odla.ai returned an invalid GitHub connection attempt");
4691
+ }
4692
+ const installUrl = trustedGitHubInstallUrl(attempt.installUrl);
4693
+ out.log(`GitHub approval: ${installUrl}`);
4694
+ if (options.open !== false) {
4695
+ await (options.openInstallUrl ?? openUrl)(installUrl);
4696
+ out.log("github: opened installation approval in your browser");
4697
+ }
4698
+ const interval = hostedPollInterval(options.pollIntervalMs);
4699
+ const now = options.now ?? Date.now;
4700
+ const deadline = Math.min(serverExpiry, now() + hostedPollTimeout(options.pollTimeoutMs));
4701
+ const wait2 = options.wait ?? waitForHostedPoll;
4702
+ while (true) {
4703
+ const state = await requestHostedSecurityJson(
4704
+ options,
4705
+ `/registry/github/connect/${encodeURIComponent(attempt.attemptId)}`,
4706
+ {},
4707
+ "check GitHub connection"
4708
+ );
4709
+ if (state.status !== "pending") {
4710
+ if (state.status === "connected") return state;
4711
+ throw new Error(`GitHub connection ${state.status}${state.failureCode ? `: ${state.failureCode}` : ""}`);
4712
+ }
4713
+ if (now() >= deadline) throw new Error("GitHub connection approval timed out");
4714
+ await wait2(Math.min(interval, Math.max(0, deadline - now())), options.signal);
4715
+ }
4716
+ }
4717
+ async function listGitHubSecuritySources(options) {
4718
+ const appId = hostedIdentifier(options.appId, "appId");
4719
+ const env = hostedIdentifier(options.env, "env");
4720
+ const body = await requestHostedSecurityJson(
4721
+ options,
4722
+ `/registry/apps/${encodeURIComponent(appId)}/github/sources?env=${encodeURIComponent(env)}`,
4723
+ {},
4724
+ "list GitHub security sources"
4725
+ );
4726
+ return Array.isArray(body.sources) ? body.sources : [];
4727
+ }
4728
+ async function disconnectGitHubSecuritySource(options) {
4729
+ const appId = hostedIdentifier(options.appId, "appId");
4730
+ const sourceId = hostedIdentifier(options.sourceId, "sourceId");
4731
+ await requestHostedSecurityJson(
4732
+ options,
4733
+ `/registry/apps/${encodeURIComponent(appId)}/github/sources/${encodeURIComponent(sourceId)}`,
4734
+ { method: "DELETE" },
4735
+ "disconnect GitHub security source"
4736
+ );
4737
+ }
4738
+ function repositoryFromGitRemote(remoteInput) {
4739
+ const remote = remoteInput.trim();
4740
+ const scp = /^git@github\.com:([^/\s]+)\/([^/\s]+?)\/?$/.exec(remote);
4741
+ if (scp) return githubRepositoryName(`${scp[1]}/${scp[2].replace(/\.git$/, "")}`);
4742
+ let url;
4743
+ try {
4744
+ url = new URL(remote);
4745
+ } catch {
4746
+ throw new Error("origin must be a github.com HTTPS or SSH repository URL");
4747
+ }
4748
+ if (url.hostname !== "github.com" || url.protocol !== "https:" && url.protocol !== "ssh:") {
4749
+ throw new Error("origin must be a github.com HTTPS or SSH repository URL");
4750
+ }
4751
+ if (url.password || url.protocol === "https:" && url.username || url.search || url.hash) {
4752
+ throw new Error("credential-bearing GitHub origin URLs are not accepted");
4753
+ }
4754
+ const parts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
4755
+ if (parts.length !== 2) {
4756
+ throw new Error("origin must identify one GitHub owner/name repository");
4757
+ }
4758
+ return githubRepositoryName(`${parts[0]}/${parts[1].replace(/\.git$/, "")}`);
4759
+ }
4760
+ async function inferGitHubRepository(cwd = process.cwd(), readOrigin = defaultReadOrigin) {
4761
+ let remote;
4762
+ try {
4763
+ remote = await readOrigin(cwd);
4764
+ } catch {
4765
+ throw new Error("Could not read git origin; pass --repo owner/name");
4766
+ }
4767
+ return repositoryFromGitRemote(remote);
4768
+ }
4769
+ async function defaultReadOrigin(cwd) {
4770
+ const result = await (0, import_node_util.promisify)(import_node_child_process2.execFile)(
4771
+ "git",
4772
+ ["remote", "get-url", "origin"],
4773
+ { cwd, encoding: "utf8" }
4774
+ );
4775
+ return result.stdout;
4776
+ }
4777
+
4778
+ // src/code-connect.ts
4779
+ var CODE_PI_IMAGE = "ghcr.io/cory/odla-pi-agent@sha256:713db58428bc05cb486167c5747de8fd826669c9b283ff78e0139ac194c3d058";
4780
+ var CODE_BUILD_RECIPES = Object.freeze([{
4781
+ id: "node-test",
4782
+ image: "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd",
4783
+ command: ["node", "--test"],
4784
+ timeoutMs: 12e4,
4785
+ maxOutputBytes: 1024 * 1024,
4786
+ cpus: 1,
4787
+ memory: "512m",
4788
+ pids: 128
4789
+ }]);
4790
+ async function codeConnect(options) {
4791
+ const cwd = options.cwd ?? process.cwd();
4792
+ const configPath = (0, import_node_path4.resolve)(cwd, options.configPath);
4793
+ const cfg = (0, import_node_fs7.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
4794
+ const repository = cfg ? null : await inferGitHubRepository(cwd, options.readGitOrigin);
4795
+ const platform = cfg?.platformUrl ?? process.env.ODLA_PLATFORM_URL ?? "https://odla.ai";
4796
+ const appEnv = options.env ?? (cfg ? cfg.envs.includes("dev") ? "dev" : cfg.envs[0] : "dev");
4797
+ if (appEnv !== "dev" && appEnv !== "prod" || cfg && !cfg.envs.includes(appEnv)) {
4798
+ 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`);
4799
+ }
4800
+ if (process.platform !== "darwin" && process.platform !== "linux") {
4801
+ throw new Error("odla Code hosts require macOS or Linux");
4802
+ }
4803
+ const slots = options.slots ?? 1;
4804
+ if (!Number.isSafeInteger(slots) || slots < 1 || slots > 64) {
4805
+ throw new Error("--slots must be an integer from 1 to 64");
4806
+ }
4807
+ const heartbeatMs = options.heartbeatMs ?? 15e3;
4808
+ if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3 || heartbeatMs > 3e5) {
4809
+ throw new Error("--heartbeat-ms must be an integer from 1000 to 300000");
4810
+ }
4811
+ const out = options.stdout ?? console;
4812
+ const doFetch = options.fetch ?? fetch;
4813
+ const engine = await (options.selectEngine ?? selectContainerEngine)(options.engine ?? "auto");
4814
+ const hostPlatform = process.platform === "darwin" ? "macos" : "linux";
4815
+ const hostName = (options.name ?? (0, import_node_os.hostname)()).trim();
4816
+ if (!hostName || hostName.length > 120) throw new Error("--name must contain 1 to 120 characters");
4817
+ const approval = await (options.getToken ?? getScopedPlatformToken)({
4818
+ platform,
4819
+ scope: "platform:code:host:connect",
4820
+ email: options.email,
4821
+ open: options.open,
4822
+ fetch: doFetch,
4823
+ stdout: out,
4824
+ openApprovalUrl: options.openApprovalUrl,
4825
+ cache: false,
4826
+ label: `odla CLI Code host for ${cfg?.app.id ?? repository}/${appEnv}`
4827
+ });
4828
+ const target = cfg ? { appId: cfg.app.id } : { repository };
4829
+ const response2 = await doFetch(`${platform}/registry/code/hosts/connect`, {
4830
+ method: "POST",
4831
+ headers: { authorization: `Bearer ${approval}`, "content-type": "application/json" },
4832
+ body: JSON.stringify({ ...target, env: appEnv, name: hostName, platform: hostPlatform, slots }),
4833
+ redirect: "error",
4834
+ signal: options.signal
4835
+ });
4836
+ const raw = await response2.json().catch(() => null);
4837
+ if (!response2.ok) throw new Error(apiFailure("connect Code host", response2.status, raw));
4838
+ const connection = parseConnection(raw, cfg?.app.id, appEnv);
4839
+ const capabilities = {
4840
+ protocolVersion: CODE_RUNTIME_PROTOCOL_VERSION,
4841
+ platform: hostPlatform,
4842
+ arch: process.arch,
4843
+ engines: [engine],
4844
+ cpuCount: (0, import_node_os.cpus)().length,
4845
+ memoryBytes: (0, import_node_os.totalmem)()
4846
+ };
4847
+ out.log(`${connection.resumed ? "reconnected" : "enrolled"}: ${connection.host.name} (${connection.host.hostId})`);
4848
+ out.log(`binding: ${connection.binding.appId}/${connection.binding.env} \xB7 ${connection.offer.slots} slot(s) \xB7 ${engine}`);
4849
+ out.log("credentials: stored in odla-ai/db; host plaintext is memory-only");
4850
+ await (options.runRuntime ?? runCodeRuntime)({
4851
+ endpoint: platform,
4852
+ token: connection.token,
4853
+ engine,
4854
+ capabilities,
4855
+ heartbeatMs,
4856
+ once: options.once === true,
4857
+ signal: options.signal,
4858
+ stdout: out,
4859
+ fetch: doFetch
4860
+ });
4861
+ }
4862
+ async function runCodeRuntime(input) {
4863
+ const controller = new AbortController();
4864
+ const abort = () => controller.abort(input.signal?.reason ?? "runtime_shutdown");
4865
+ input.signal?.addEventListener("abort", abort, { once: true });
4866
+ const signal = input.signal ? AbortSignal.any([input.signal, controller.signal]) : controller.signal;
4867
+ const handlers = ["SIGINT", "SIGTERM"].map((name) => {
4868
+ const handler = () => controller.abort(name);
4869
+ process.once(name, handler);
4870
+ return [name, handler];
4871
+ });
4872
+ const control = createCodeRuntimeControlClient({
4873
+ endpoint: input.endpoint,
4874
+ token: input.token,
4875
+ signal,
4876
+ fetch: input.fetch
4877
+ });
4878
+ const commandEngine = new CodePiRuntimeEngine({
4879
+ control,
4880
+ engine: input.engine,
4881
+ image: CODE_PI_IMAGE,
4882
+ recipes: CODE_BUILD_RECIPES,
4883
+ recipeAuthorization: "registered_recipe"
4884
+ });
4885
+ const reconciler = new CodeRuntimeReconciler(control, commandEngine);
4886
+ try {
4887
+ await runCodeRuntimeHeartbeatLoop({
4888
+ control,
4889
+ runtimeVersion: `odla-ai-cli/${cliVersion()}`,
4890
+ capabilities: input.capabilities,
4891
+ heartbeatMs: input.heartbeatMs,
4892
+ once: input.once,
4893
+ signal,
4894
+ onSnapshot: async (snapshot) => {
4895
+ input.stdout.log(
4896
+ `online: ${snapshot.host.hostId} \xB7 ${snapshot.bindings.length} binding(s) \xB7 ${snapshot.commands.length} command(s)`
4897
+ );
4898
+ if (input.once && snapshot.commands.length) {
4899
+ throw new Error("--once is diagnostic-only and refuses pending Code commands; run without --once");
4900
+ }
4901
+ await reconciler.reconcile(snapshot);
4902
+ },
4903
+ onRetry: (error, delayMs) => input.stdout.error(
4904
+ `Code control plane unavailable; retrying in ${delayMs}ms \xB7 ${error instanceof Error ? error.message : String(error)}`
4905
+ )
4906
+ });
4907
+ } finally {
4908
+ await commandEngine.close();
4909
+ input.signal?.removeEventListener("abort", abort);
4910
+ for (const [name, handler] of handlers) process.removeListener(name, handler);
4911
+ }
4912
+ }
4913
+ function parseConnection(value, appId, appEnv) {
4914
+ const root = record4(value);
4915
+ const host = record4(root?.host);
4916
+ const offer = record4(root?.offer);
4917
+ const binding = record4(root?.binding);
4918
+ 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)) {
4919
+ throw new Error("connect Code host returned an invalid response");
4920
+ }
4921
+ return root;
4922
+ }
4923
+ function apiFailure(action, status, value) {
4924
+ const message3 = record4(record4(value)?.error)?.message;
4925
+ return `${action} failed (${status})${typeof message3 === "string" ? `: ${message3}` : ""}`;
4926
+ }
4927
+ function record4(value) {
4928
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4929
+ }
4930
+
4931
+ // src/code-command.ts
4932
+ async function codeCommand(parsed, dependencies) {
4933
+ const sub = parsed.positionals[1];
4934
+ if (sub !== "connect") {
4935
+ throw new Error(`unknown code subcommand "${sub ?? ""}". Try "odla-ai code connect --env dev".`);
4936
+ }
4937
+ assertArgs(parsed, [
4938
+ "config",
4939
+ "env",
4940
+ "email",
4941
+ "open",
4942
+ "name",
4943
+ "slots",
4944
+ "engine",
4945
+ "heartbeat-ms",
4946
+ "once"
4947
+ ], 2);
4948
+ const engine = stringOpt(parsed.options.engine) ?? "auto";
4949
+ if (!["auto", "container", "podman", "docker"].includes(engine)) {
4950
+ throw new Error("--engine must be auto, container, podman, or docker");
4951
+ }
4952
+ await (dependencies.codeConnect ?? codeConnect)({
4953
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4954
+ env: stringOpt(parsed.options.env),
4955
+ email: stringOpt(parsed.options.email),
4956
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
4957
+ name: stringOpt(parsed.options.name),
4958
+ slots: numberOpt(parsed.options.slots, "--slots"),
4959
+ engine,
4960
+ heartbeatMs: numberOpt(parsed.options["heartbeat-ms"], "--heartbeat-ms"),
4961
+ once: parsed.options.once === true,
4962
+ fetch: dependencies.fetch,
4963
+ stdout: dependencies.stdout,
4964
+ openApprovalUrl: dependencies.openUrl,
4965
+ readGitOrigin: dependencies.readGitOrigin
4966
+ });
4967
+ }
4968
+
4969
+ // src/doctor-checks.ts
4970
+ var import_node_child_process4 = require("child_process");
4971
+ var import_node_fs9 = require("fs");
4972
+ var import_node_path6 = require("path");
4973
+
4974
+ // src/wrangler.ts
4975
+ var import_node_child_process3 = require("child_process");
4976
+ var import_node_fs8 = require("fs");
4977
+ var import_node_path5 = require("path");
4978
+ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4979
+ const child = (0, import_node_child_process3.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4980
+ let stdout = "";
4981
+ let stderr = "";
4982
+ child.stdout.on("data", (chunk) => stdout += chunk.toString());
4983
+ child.stderr.on("data", (chunk) => stderr += chunk.toString());
4984
+ child.on("error", reject);
4985
+ child.on("close", (code) => resolvePromise({ code: code ?? 1, stdout, stderr }));
4986
+ child.stdin.end(opts?.input ?? "");
4987
+ });
4988
+ var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
4989
+ function findWranglerConfig(rootDir) {
4990
+ for (const name of WRANGLER_CONFIG_FILES) {
4991
+ const path = (0, import_node_path5.join)(rootDir, name);
4992
+ if ((0, import_node_fs8.existsSync)(path)) return path;
4993
+ }
4994
+ return null;
4995
+ }
4996
+ function readWranglerConfig(path) {
4997
+ if (path.endsWith(".toml")) return null;
4998
+ try {
4999
+ return JSON.parse(stripJsonComments((0, import_node_fs8.readFileSync)(path, "utf8")));
5000
+ } catch {
5001
+ return null;
5002
+ }
5003
+ }
5004
+ function stripJsonComments(text) {
5005
+ let result = "";
5006
+ let inString = false;
5007
+ for (let i = 0; i < text.length; i++) {
5008
+ const ch = text[i];
5009
+ if (inString) {
5010
+ result += ch;
5011
+ if (ch === "\\") {
5012
+ result += text[i + 1] ?? "";
5013
+ i++;
5014
+ } else if (ch === '"') {
5015
+ inString = false;
5016
+ }
5017
+ continue;
5018
+ }
5019
+ if (ch === '"') {
5020
+ inString = true;
5021
+ result += ch;
5022
+ continue;
5023
+ }
5024
+ if (ch === "/" && text[i + 1] === "/") {
5025
+ while (i < text.length && text[i] !== "\n") i++;
5026
+ result += "\n";
5027
+ continue;
5028
+ }
5029
+ if (ch === "/" && text[i + 1] === "*") {
5030
+ i += 2;
5031
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
5032
+ i++;
5033
+ continue;
5034
+ }
5035
+ result += ch;
5036
+ }
5037
+ return result;
5038
+ }
5039
+ async function wranglerLoggedIn(run, cwd) {
5040
+ try {
5041
+ const result = await run("npx", ["wrangler", "whoami"], { cwd });
5042
+ return result.code === 0 && !/not authenticated/i.test(`${result.stdout}${result.stderr}`);
5043
+ } catch {
5044
+ return false;
5045
+ }
5046
+ }
5047
+ function wranglerPutSecret(run, opts) {
5048
+ const args = ["wrangler", "secret", "put", opts.name, ...opts.env ? ["--env", opts.env] : []];
5049
+ return run("npx", args, { input: opts.value, cwd: opts.cwd });
5050
+ }
5051
+
5052
+ // src/doctor-checks.ts
5053
+ function lintRules(rules, entities, publicRead) {
5054
+ const warnings = [];
5055
+ if (!rules) return warnings;
5056
+ for (const [ns, actions] of Object.entries(rules)) {
5057
+ for (const [action, expr] of Object.entries(actions)) {
5058
+ if (typeof expr !== "string" || expr.trim() !== "true") continue;
5059
+ if (action === "view" && publicRead.includes(ns)) continue;
5060
+ warnings.push(
5061
+ 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`
5062
+ );
5063
+ }
5064
+ }
5065
+ for (const entity of entities) {
5066
+ if (!(entity in rules)) warnings.push(`schema entity "${entity}" has no rules entry (all client access denied)`);
5067
+ }
5068
+ return warnings;
5069
+ }
5070
+ var defaultExec = (cmd, args, cwd) => (0, import_node_child_process4.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5071
+ function trackedSecretFiles(rootDir, exec = defaultExec, localPaths = []) {
5072
+ let output;
5073
+ try {
5074
+ const custom = localPaths.map((path) => gitignoreEntry(rootDir, path)).filter((path) => !!path);
5075
+ output = exec("git", ["ls-files", "--", ".dev.vars", ".odla", ...custom], rootDir);
5076
+ } catch {
5077
+ return [];
5078
+ }
5079
+ 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`);
5080
+ }
5081
+ function wranglerWarnings(rootDir) {
5082
+ const configPath = findWranglerConfig(rootDir);
5083
+ if (!configPath) return [];
5084
+ const config = readWranglerConfig(configPath);
5085
+ if (!config) return [];
5086
+ const warnings = [];
5087
+ const blocks = [{ label: "", block: config }];
5088
+ const envs = config.env;
5089
+ if (envs && typeof envs === "object") {
5090
+ for (const [name, block] of Object.entries(envs)) {
5091
+ if (block && typeof block === "object") blocks.push({ label: `env.${name}.`, block });
5092
+ }
5093
+ }
5094
+ for (const { label, block } of blocks) {
5095
+ const assets = block.assets;
5096
+ if (assets?.directory) {
5097
+ const dir = (0, import_node_path6.resolve)(rootDir, assets.directory);
5098
+ if (dir === (0, import_node_path6.resolve)(rootDir)) {
5099
+ warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
5100
+ } else if ((0, import_node_fs9.existsSync)((0, import_node_path6.join)(dir, "node_modules"))) {
5101
+ warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
5102
+ }
5103
+ }
5104
+ const vars = block.vars;
5105
+ if (vars && typeof vars === "object") {
5106
+ for (const [name, value] of Object.entries(vars)) {
5107
+ if (name === "ODLA_API_KEY" || name === "ODLA_O11Y_TOKEN" || typeof value === "string" && looksSecret(value)) {
5108
+ warnings.push(`wrangler ${label}vars.${name} looks like a secret \u2014 use "odla-ai secrets push" / wrangler secret put, never vars`);
5109
+ }
5110
+ }
5111
+ }
5112
+ }
5113
+ const pkg = readPackageJson(rootDir);
5114
+ if (pkg && typeof pkg.scripts === "object" && pkg.scripts && "deploy" in pkg.scripts) {
5115
+ warnings.push(`package.json has a script named "deploy" \u2014 CI may auto-deploy it; rename to deploy:app`);
5116
+ }
5117
+ return warnings;
5118
+ }
5119
+ function o11yProjectWarnings(rootDir) {
5120
+ const warnings = [];
5121
+ const pkg = readPackageJson(rootDir);
5122
+ const dependencies = pkg?.dependencies;
5123
+ const devDependencies = pkg?.devDependencies;
5124
+ if (!dependencies?.["@odla-ai/o11y"]) {
5125
+ warnings.push(
5126
+ 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"'
5127
+ );
5128
+ }
5129
+ const configPath = findWranglerConfig(rootDir);
5130
+ const config = configPath ? readWranglerConfig(configPath) : null;
5131
+ if (!configPath || !config) {
5132
+ warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
5133
+ return warnings;
5134
+ }
5135
+ const main = typeof config.main === "string" ? (0, import_node_path6.resolve)(rootDir, config.main) : null;
5136
+ if (!main || !(0, import_node_fs9.existsSync)(main)) {
5137
+ warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
5138
+ } else {
5139
+ let source = "";
5140
+ try {
5141
+ source = (0, import_node_fs9.readFileSync)(main, "utf8");
5142
+ } catch {
5143
+ }
5144
+ if (!/\bwithObservability\b/.test(source)) {
5145
+ warnings.push(`wrangler entrypoint ${config.main} does not reference withObservability`);
5146
+ }
5147
+ }
5148
+ const flags = Array.isArray(config.compatibility_flags) ? config.compatibility_flags : [];
5149
+ if (!flags.includes("nodejs_compat")) {
5150
+ warnings.push('wrangler compatibility_flags is missing "nodejs_compat" required by @odla-ai/o11y');
5151
+ }
5152
+ return warnings;
5153
+ }
5154
+ function calendarProjectWarnings(rootDir) {
2078
5155
  const pkg = readPackageJson(rootDir);
2079
5156
  const dependencies = pkg?.dependencies;
2080
5157
  const devDependencies = pkg?.devDependencies;
@@ -2085,14 +5162,14 @@ function calendarProjectWarnings(rootDir) {
2085
5162
  }
2086
5163
  function readPackageJson(rootDir) {
2087
5164
  try {
2088
- return JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path5.join)(rootDir, "package.json"), "utf8"));
5165
+ return JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path6.join)(rootDir, "package.json"), "utf8"));
2089
5166
  } catch {
2090
5167
  return null;
2091
5168
  }
2092
5169
  }
2093
5170
 
2094
5171
  // src/integrations.ts
2095
- var import_node_util = require("util");
5172
+ var import_node_util2 = require("util");
2096
5173
  async function resolveDatabaseConfig(cfg, options = {}) {
2097
5174
  const integrations = cfg.integrations ?? [];
2098
5175
  if (!cfg.services.includes("db")) return { schema: void 0, rules: void 0, integrations };
@@ -2183,7 +5260,7 @@ function normalizeSchema(value) {
2183
5260
  }
2184
5261
  function mergeMap(target, fragment, label) {
2185
5262
  for (const [name, value] of Object.entries(fragment)) {
2186
- if (Object.hasOwn(target, name) && !(0, import_node_util.isDeepStrictEqual)(target[name], value)) {
5263
+ if (Object.hasOwn(target, name) && !(0, import_node_util2.isDeepStrictEqual)(target[name], value)) {
2187
5264
  throw new Error(`${label} "${name}" conflicts with an existing definition`);
2188
5265
  }
2189
5266
  target[name] = value;
@@ -2222,150 +5299,48 @@ async function doctor(options) {
2222
5299
  }
2223
5300
  const warnings = [];
2224
5301
  if (schema && entities.length === 0) warnings.push("schema has no entities");
2225
- if (rules) {
2226
- for (const ns of Object.keys(rules)) {
2227
- if (entities.length > 0 && !entities.includes(ns)) warnings.push(`rules include "${ns}" but schema has no matching entity`);
2228
- }
2229
- }
2230
- warnings.push(...integrationWarnings(database.integrations, schema, rules));
2231
- if (cfg.services.includes("ai") && !cfg.ai?.provider) warnings.push("ai service is enabled but ai.provider is not set");
2232
- if (cfg.auth?.clerk) {
2233
- for (const [env, value] of Object.entries(cfg.auth.clerk)) {
2234
- if (typeof value === "string" && value.startsWith("$") && !process.env[value.slice(1)]) {
2235
- warnings.push(`auth.clerk.${env} references unset env var ${value}`);
2236
- }
2237
- }
2238
- }
2239
- if (cfg.services.includes("ai") && cfg.ai?.keyEnv && !process.env[cfg.ai.keyEnv]) {
2240
- warnings.push(`${cfg.ai.keyEnv} is not set; provision will skip provider key storage`);
2241
- }
2242
- if (cfg.services.includes("o11y")) {
2243
- const credentials = readCredentials(cfg.local.credentialsFile);
2244
- for (const env of cfg.envs) {
2245
- if (!credentials?.envs[env]?.o11yToken) {
2246
- warnings.push(`o11y is enabled but env "${env}" has no ingest token \u2014 run "odla-ai provision" to mint one`);
2247
- }
2248
- }
2249
- warnings.push(...o11yProjectWarnings(cfg.rootDir));
2250
- }
2251
- if (cfg.services.includes("calendar")) warnings.push(...calendarProjectWarnings(cfg.rootDir));
2252
- else if (cfg.calendar) warnings.push('calendar config is present but "calendar" is not in services');
2253
- warnings.push(...lintRules(rules, entities, cfg.db?.publicRead ?? []));
2254
- warnings.push(...trackedSecretFiles(cfg.rootDir, void 0, [
2255
- cfg.local.tokenFile,
2256
- cfg.local.credentialsFile,
2257
- cfg.local.devVarsFile
2258
- ]));
2259
- warnings.push(...wranglerWarnings(cfg.rootDir));
2260
- if (warnings.length) {
2261
- out.log("");
2262
- out.log("warnings:");
2263
- for (const warning of warnings) out.log(` - ${warning}`);
2264
- } else {
2265
- out.log("ok");
2266
- }
2267
- }
2268
-
2269
- // src/help.ts
2270
- var import_node_fs8 = require("fs");
2271
- function cliVersion() {
2272
- const pkg = JSON.parse((0, import_node_fs8.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
2273
- return pkg.version ?? "unknown";
2274
- }
2275
- function printHelp() {
2276
- console.log(`odla-ai
2277
-
2278
- Usage:
2279
- odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
2280
- odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
2281
- odla-ai doctor [--config odla.config.mjs]
2282
- odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
2283
- odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
2284
- odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
2285
- odla-ai calendar disconnect [--env dev] [--email <odla-account>] --yes
2286
- odla-ai app archive [--config odla.config.mjs] [--email <odla-account>] [--json] --yes
2287
- odla-ai app restore [--config odla.config.mjs] [--email <odla-account>] [--json]
2288
- odla-ai app export [--env dev] [--fresh] [--out <file>] [--email <odla-account>] [--json]
2289
- odla-ai capabilities [--json]
2290
- odla-ai admin ai show [--platform https://odla.ai] [--email <odla-account>] [--json]
2291
- odla-ai admin ai models [--provider <id>] [--json]
2292
- odla-ai admin ai set <purpose> [--provider <id>] [--model <id>] [--enabled|--no-enabled]
2293
- odla-ai admin ai set security --discovery-provider <id> --discovery-model <id>
2294
- --validation-provider <id> --validation-model <id> [--enabled|--no-enabled]
2295
- odla-ai admin ai credentials [--json]
2296
- odla-ai admin ai credential set <provider> (--from-env <NAME>|--stdin)
2297
- odla-ai admin ai usage [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
2298
- odla-ai admin ai audit [--limit <1-200>] [--json]
2299
- odla-ai security github connect [--repo owner/name] [--env dev] [--email <odla-account>] [--no-open]
2300
- odla-ai security github disconnect --source <id> [--env dev] [--yes]
2301
- odla-ai security plan [--env dev] [--json]
2302
- odla-ai security sources [--env dev] [--json]
2303
- odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
2304
- odla-ai security status <job-id> [--follow] [--json]
2305
- odla-ai security report <job-id> [--json]
2306
- odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
2307
- odla-ai security run [target] --self --ack-redacted-source
2308
- odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
2309
- odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
2310
- odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
2311
- odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
2312
- odla-ai secrets set <name> --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
2313
- odla-ai secrets set-clerk-key --env <env> (--from-env <NAME>|--stdin) [--email <odla-account>] [--config odla.config.mjs] [--yes]
2314
- odla-ai version
2315
-
2316
- Commands:
2317
- setup Install offline odla runbooks for common coding-agent harnesses.
2318
- init Create a generic odla.config.mjs plus starter schema/rules files.
2319
- doctor Validate and summarize the project config without network calls.
2320
- calendar Inspect, connect, or disconnect the live Google booking connection.
2321
- app Archive (suspend, data retained), restore, or export the app.
2322
- Archiving takes every environment's data plane down until restored;
2323
- permanent deletion has NO CLI \u2014 it requires a signed-in owner in
2324
- Studio, and no machine or agent credential can purge. "app export"
2325
- downloads a portable gzipped-JSONL snapshot of one environment's
2326
- database (--fresh takes a new snapshot first) \u2014 your data is
2327
- always yours to take.
2328
- capabilities Show what the CLI automates vs agent edits and human checkpoints.
2329
- admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
2330
- security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
2331
- provision Register services, compose integrations, persist credentials, optionally push secrets.
2332
- smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
2333
- skill Same installer; --agent accepts all, claude, codex, cursor,
2334
- copilot, gemini, or agents (repeatable or comma-separated).
2335
- secrets Push configured db/o11y secrets into the Worker via wrangler
2336
- stdin; set stores a tenant-vault secret and set-clerk-key the
2337
- reserved Clerk secret key, write-only from stdin or an env var.
2338
- version Print the CLI version.
2339
-
2340
- Safety:
2341
- New projects target dev only. Add prod explicitly to odla.config.mjs and pass
2342
- --yes to provision it; use --dry-run first to inspect the resolved plan.
2343
- Provision caches the approved developer token and service credentials under
2344
- .odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
2345
- preflights Wrangler before any shown-once issuance or destructive rotation.
2346
- Provision opens the approval page in your browser automatically whenever the
2347
- machine can show one, including agent-driven runs; only CI, SSH, and
2348
- display-less hosts skip it. Use --open to force or --no-open to suppress.
2349
- Browser launch is best-effort: the printed approval URL is authoritative and
2350
- agents must relay it to the human verbatim. A started handshake is persisted
2351
- under .odla/, so a command killed mid-wait loses nothing \u2014 rerunning resumes
2352
- the same code. Outside an interactive terminal the wait is capped (90s by
2353
- default, --wait <seconds> to change); a still-pending handshake then exits
2354
- with code 75: relay the URL, wait for approval, and re-run to collect.
2355
- A fresh device handshake requires --email <odla-account> or ODLA_USER_EMAIL.
2356
- The email is a non-secret identity hint: never provide a password or session
2357
- token. The matching account must already exist, be signed in, explicitly
2358
- review the exact code, and finish any current request before claiming another.
2359
- Calendar setup has a second human checkpoint: odla issues a state-bound Google consent URL;
2360
- OAuth codes and refresh tokens never enter the CLI, repo, chat, or app.
2361
- GitHub security uses source-read-only access plus optional metadata-only Checks write: the CLI never asks
2362
- for a PAT or provider key. GitHub read access is separate from the explicit
2363
- --ack-redacted-source consent and the exact --plan-digest printed by security
2364
- plan are required before bounded snippets reach System AI.
2365
- Run security plan first to inspect the admin-selected providers, models,
2366
- per-route bounds, credential readiness, retention, no-execution boundary,
2367
- and digest that binds consent to that exact plan.
2368
- `);
5302
+ if (rules) {
5303
+ for (const ns of Object.keys(rules)) {
5304
+ if (entities.length > 0 && !entities.includes(ns)) warnings.push(`rules include "${ns}" but schema has no matching entity`);
5305
+ }
5306
+ }
5307
+ warnings.push(...integrationWarnings(database.integrations, schema, rules));
5308
+ if (cfg.services.includes("ai") && !cfg.ai?.provider) warnings.push("ai service is enabled but ai.provider is not set");
5309
+ if (cfg.auth?.clerk) {
5310
+ for (const [env, value] of Object.entries(cfg.auth.clerk)) {
5311
+ if (typeof value === "string" && value.startsWith("$") && !process.env[value.slice(1)]) {
5312
+ warnings.push(`auth.clerk.${env} references unset env var ${value}`);
5313
+ }
5314
+ }
5315
+ }
5316
+ if (cfg.services.includes("ai") && cfg.ai?.keyEnv && !process.env[cfg.ai.keyEnv]) {
5317
+ warnings.push(`${cfg.ai.keyEnv} is not set; provision will skip provider key storage`);
5318
+ }
5319
+ if (cfg.services.includes("o11y")) {
5320
+ const credentials = readCredentials(cfg.local.credentialsFile);
5321
+ for (const env of cfg.envs) {
5322
+ if (!credentials?.envs[env]?.o11yToken) {
5323
+ warnings.push(`o11y is enabled but env "${env}" has no ingest token \u2014 run "odla-ai provision" to mint one`);
5324
+ }
5325
+ }
5326
+ warnings.push(...o11yProjectWarnings(cfg.rootDir));
5327
+ }
5328
+ if (cfg.services.includes("calendar")) warnings.push(...calendarProjectWarnings(cfg.rootDir));
5329
+ else if (cfg.calendar) warnings.push('calendar config is present but "calendar" is not in services');
5330
+ warnings.push(...lintRules(rules, entities, cfg.db?.publicRead ?? []));
5331
+ warnings.push(...trackedSecretFiles(cfg.rootDir, void 0, [
5332
+ cfg.local.tokenFile,
5333
+ cfg.local.credentialsFile,
5334
+ cfg.local.devVarsFile
5335
+ ]));
5336
+ warnings.push(...wranglerWarnings(cfg.rootDir));
5337
+ if (warnings.length) {
5338
+ out.log("");
5339
+ out.log("warnings:");
5340
+ for (const warning of warnings) out.log(` - ${warning}`);
5341
+ } else {
5342
+ out.log("ok");
5343
+ }
2369
5344
  }
2370
5345
 
2371
5346
  // src/harness-options.ts
@@ -2388,13 +5363,13 @@ function harnessOption(value, flag) {
2388
5363
  }
2389
5364
 
2390
5365
  // src/init.ts
2391
- var import_node_fs9 = require("fs");
2392
- var import_node_path6 = require("path");
5366
+ var import_node_fs10 = require("fs");
5367
+ var import_node_path7 = require("path");
2393
5368
  function initProject(options) {
2394
5369
  const out = options.stdout ?? console;
2395
- const rootDir = (0, import_node_path6.resolve)(options.rootDir ?? process.cwd());
2396
- const configPath = (0, import_node_path6.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
2397
- if ((0, import_node_fs9.existsSync)(configPath) && !options.force) {
5370
+ const rootDir = (0, import_node_path7.resolve)(options.rootDir ?? process.cwd());
5371
+ const configPath = (0, import_node_path7.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
5372
+ if ((0, import_node_fs10.existsSync)(configPath) && !options.force) {
2398
5373
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
2399
5374
  }
2400
5375
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -2404,20 +5379,20 @@ function initProject(options) {
2404
5379
  const services = options.services?.length ? options.services : ["db", "ai"];
2405
5380
  if (services.includes("calendar") && !services.includes("db")) throw new Error("--services calendar requires db");
2406
5381
  const aiProvider = options.aiProvider ?? "anthropic";
2407
- (0, import_node_fs9.mkdirSync)((0, import_node_path6.dirname)(configPath), { recursive: true });
2408
- (0, import_node_fs9.mkdirSync)((0, import_node_path6.resolve)(rootDir, "src/odla"), { recursive: true });
2409
- (0, import_node_fs9.mkdirSync)((0, import_node_path6.resolve)(rootDir, ".odla"), { recursive: true });
2410
- (0, import_node_fs9.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
2411
- writeIfMissing((0, import_node_path6.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
2412
- writeIfMissing((0, import_node_path6.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
5382
+ (0, import_node_fs10.mkdirSync)((0, import_node_path7.dirname)(configPath), { recursive: true });
5383
+ (0, import_node_fs10.mkdirSync)((0, import_node_path7.resolve)(rootDir, "src/odla"), { recursive: true });
5384
+ (0, import_node_fs10.mkdirSync)((0, import_node_path7.resolve)(rootDir, ".odla"), { recursive: true });
5385
+ (0, import_node_fs10.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
5386
+ writeIfMissing((0, import_node_path7.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
5387
+ writeIfMissing((0, import_node_path7.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
2413
5388
  ensureGitignore(rootDir);
2414
5389
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
2415
5390
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
2416
5391
  out.log("updated .gitignore for local odla credentials");
2417
5392
  }
2418
5393
  function writeIfMissing(path, text) {
2419
- if ((0, import_node_fs9.existsSync)(path)) return;
2420
- (0, import_node_fs9.writeFileSync)(path, text);
5394
+ if ((0, import_node_fs10.existsSync)(path)) return;
5395
+ (0, import_node_fs10.writeFileSync)(path, text);
2421
5396
  }
2422
5397
  function configTemplate(input) {
2423
5398
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -2888,10 +5863,10 @@ async function provision(options) {
2888
5863
  stdout: out
2889
5864
  });
2890
5865
  } catch (error) {
2891
- const message = error instanceof Error ? error.message : String(error);
5866
+ const message3 = error instanceof Error ? error.message : String(error);
2892
5867
  const consent = env === "prod" || env === "production" ? " --yes" : "";
2893
5868
  throw new Error(
2894
- `${message}
5869
+ `${message3}
2895
5870
  ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}${consent}" without issuing or rotating again`,
2896
5871
  { cause: error }
2897
5872
  );
@@ -2954,8 +5929,8 @@ async function readRegistryApp(cfg, token, doFetch) {
2954
5929
  });
2955
5930
  if (res.status === 404) return null;
2956
5931
  if (!res.ok) return null;
2957
- const json = await res.json();
2958
- return json.app ?? null;
5932
+ const json2 = await res.json();
5933
+ return json2.app ?? null;
2959
5934
  }
2960
5935
  async function postJson2(doFetch, url, bearer, body) {
2961
5936
  const res = await doFetch(url, {
@@ -3042,7 +6017,7 @@ async function resolveVaultWrite(options) {
3042
6017
  }
3043
6018
 
3044
6019
  // src/security-command-context.ts
3045
- var import_promises2 = require("readline/promises");
6020
+ var import_promises9 = require("readline/promises");
3046
6021
  async function hostedSecurityContext(parsed, dependencies) {
3047
6022
  const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
3048
6023
  const cfg = await loadProjectConfig(configPath);
@@ -3065,12 +6040,12 @@ async function hostedSecurityContext(parsed, dependencies) {
3065
6040
  );
3066
6041
  return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
3067
6042
  }
3068
- async function interactiveConfirmation(message, dependencies) {
3069
- if (dependencies.confirm) return dependencies.confirm(message);
6043
+ async function interactiveConfirmation(message3, dependencies) {
6044
+ if (dependencies.confirm) return dependencies.confirm(message3);
3070
6045
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
3071
- const prompt = (0, import_promises2.createInterface)({ input: process.stdin, output: process.stdout });
6046
+ const prompt = (0, import_promises9.createInterface)({ input: process.stdin, output: process.stdout });
3072
6047
  try {
3073
- const answer = await prompt.question(`${message} [y/N] `);
6048
+ const answer = await prompt.question(`${message3} [y/N] `);
3074
6049
  return /^y(?:es)?$/i.test(answer.trim());
3075
6050
  } finally {
3076
6051
  prompt.close();
@@ -3112,9 +6087,9 @@ function printHostedSecurityIntent(out, intent) {
3112
6087
  }
3113
6088
  function assertHostedSecurityPlanReady(plan) {
3114
6089
  const reasons = [];
3115
- for (const [label, route] of Object.entries(plan.routes)) {
3116
- if (!route.enabled) reasons.push(`${label} is disabled`);
3117
- if (!route.credentialReady) reasons.push(`${label} provider credential is unavailable`);
6090
+ for (const [label, route2] of Object.entries(plan.routes)) {
6091
+ if (!route2.enabled) reasons.push(`${label} is disabled`);
6092
+ if (!route2.credentialReady) reasons.push(`${label} provider credential is unavailable`);
3118
6093
  }
3119
6094
  if (!plan.independent) reasons.push("discovery and validation are not independently routed");
3120
6095
  if (plan.ready && reasons.length === 0) return;
@@ -3137,54 +6112,51 @@ function printHostedJob(out, job, platform, appId) {
3137
6112
  out.log(` findings: confirmed=${job.counts.confirmed} needs_reproduction=${job.counts.needsReproduction} candidates=${job.counts.candidates}`);
3138
6113
  }
3139
6114
  if (job.errorCode) out.log(` failure: ${job.errorCode}`);
3140
- const url = new URL("/studio", platform);
3141
- url.searchParams.set("app", appId);
3142
- url.searchParams.set("env", job.env);
3143
- url.searchParams.set("tab", "security");
6115
+ const url = new URL(`/studio/apps/${encodeURIComponent(appId)}/${encodeURIComponent(job.env)}/security`, platform);
3144
6116
  url.searchParams.set("job", job.jobId);
3145
6117
  out.log(` Studio: ${url.toString()}`);
3146
6118
  }
3147
- function printHostedReport(out, report) {
3148
- out.log(`security report ${report.jobId}: ${report.repository}@${report.revision}`);
3149
- 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}`);
3150
- out.log(` findings: confirmed=${report.metrics.confirmed} needs_reproduction=${report.metrics.needsReproduction} candidates=${report.metrics.candidates} rejected=${report.metrics.rejected}`);
3151
- out.log(` discovery: ${report.provenance.discovery?.provider ?? "unknown"}/${report.provenance.discovery?.model ?? "unknown"}`);
3152
- out.log(` validation: ${report.provenance.validation?.provider ?? "unknown"}/${report.provenance.validation?.model ?? "unknown"} independent=${String(report.provenance.independentValidation)}`);
3153
- for (const finding of report.findings) {
6119
+ function printHostedReport(out, report2) {
6120
+ out.log(`security report ${report2.jobId}: ${report2.repository}@${report2.revision}`);
6121
+ 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}`);
6122
+ out.log(` findings: confirmed=${report2.metrics.confirmed} needs_reproduction=${report2.metrics.needsReproduction} candidates=${report2.metrics.candidates} rejected=${report2.metrics.rejected}`);
6123
+ out.log(` discovery: ${report2.provenance.discovery?.provider ?? "unknown"}/${report2.provenance.discovery?.model ?? "unknown"}`);
6124
+ out.log(` validation: ${report2.provenance.validation?.provider ?? "unknown"}/${report2.provenance.validation?.model ?? "unknown"} independent=${String(report2.provenance.independentValidation)}`);
6125
+ for (const finding of report2.findings) {
3154
6126
  const location = finding.locations[0];
3155
6127
  out.log(` [${finding.severity}] ${finding.title}${location ? ` (${location.path}:${location.line})` : ""} \xB7 ${finding.disposition}`);
3156
6128
  }
3157
- for (const limitation of report.limitations) out.log(` limitation: ${limitation}`);
6129
+ for (const limitation of report2.limitations) out.log(` limitation: ${limitation}`);
3158
6130
  }
3159
- function enforceHostedReportGate(report, parsed, out, emitSuccess) {
6131
+ function enforceHostedReportGate(report2, parsed, out, emitSuccess) {
3160
6132
  const failOn = hostedSeverity(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
3161
6133
  const candidateValue = parsed.options["fail-on-candidates"];
3162
6134
  const failOnCandidates = candidateValue === false ? void 0 : hostedSeverity(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
3163
6135
  const atOrAbove = (severity, threshold) => HOSTED_SEVERITIES.indexOf(severity) >= HOSTED_SEVERITIES.indexOf(threshold);
3164
- const confirmed = report.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
3165
- const leads = failOnCandidates ? report.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
3166
- const incomplete = report.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
6136
+ const confirmed = report2.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
6137
+ const leads = failOnCandidates ? report2.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
6138
+ const incomplete = report2.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
3167
6139
  if (confirmed.length || leads.length || incomplete) {
3168
- throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report.coverageStatus}` : ""}`);
6140
+ throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report2.coverageStatus}` : ""}`);
3169
6141
  }
3170
6142
  if (emitSuccess) {
3171
- out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report.coverageStatus}. This is not proof that the application is secure.`);
6143
+ out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report2.coverageStatus}. This is not proof that the application is secure.`);
3172
6144
  }
3173
6145
  }
3174
- function printHostedSecurityPlanRoute(out, label, route) {
3175
- const readiness = route.enabled && route.credentialReady ? "ready" : [
3176
- route.enabled ? void 0 : "disabled",
3177
- route.credentialReady ? void 0 : "credential unavailable"
6146
+ function printHostedSecurityPlanRoute(out, label, route2) {
6147
+ const readiness = route2.enabled && route2.credentialReady ? "ready" : [
6148
+ route2.enabled ? void 0 : "disabled",
6149
+ route2.credentialReady ? void 0 : "credential unavailable"
3178
6150
  ].filter(Boolean).join(", ");
3179
- out.log(` ${label}: ${route.provider}/${route.model} \xB7 policy v${route.policyVersion} \xB7 ${readiness}`);
3180
- out.log(` bounds: ${route.maxCallsPerRun} calls/run \xB7 ${route.maxInputBytes} input bytes/call \xB7 ${route.maxOutputTokens} output tokens/call`);
6151
+ out.log(` ${label}: ${route2.provider}/${route2.model} \xB7 policy v${route2.policyVersion} \xB7 ${readiness}`);
6152
+ out.log(` bounds: ${route2.maxCallsPerRun} calls/run \xB7 ${route2.maxInputBytes} input bytes/call \xB7 ${route2.maxOutputTokens} output tokens/call`);
3181
6153
  }
3182
6154
  function printHostedCoverage(out, job) {
3183
6155
  const coverage = job.coverage;
3184
6156
  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}` : ""}`);
3185
6157
  }
3186
- function routeLabel(route) {
3187
- return `${route.provider}/${route.model}${route.policyVersion ? ` policy v${route.policyVersion}` : ""}`;
6158
+ function routeLabel(route2) {
6159
+ return `${route2.provider}/${route2.model}${route2.policyVersion ? ` policy v${route2.policyVersion}` : ""}`;
3188
6160
  }
3189
6161
  var HOSTED_SEVERITIES = ["informational", "low", "medium", "high", "critical"];
3190
6162
  function hostedSeverity(value, flag) {
@@ -3198,9 +6170,9 @@ function hostedSeverity(value, flag) {
3198
6170
  var import_security2 = require("@odla-ai/security");
3199
6171
 
3200
6172
  // src/security.ts
3201
- var import_node_path7 = require("path");
6173
+ var import_node_path8 = require("path");
3202
6174
  var import_security = require("@odla-ai/security");
3203
- var import_node = require("@odla-ai/security/node");
6175
+ var import_node2 = require("@odla-ai/security/node");
3204
6176
  async function runHostedSecurity(options) {
3205
6177
  if (options.sourceDisclosureAck !== "redacted") {
3206
6178
  throw new Error("Hosted security requires sourceDisclosureAck=redacted before repository source may leave this process");
@@ -3210,9 +6182,9 @@ async function runHostedSecurity(options) {
3210
6182
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
3211
6183
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
3212
6184
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
3213
- const target = (0, import_node_path7.resolve)(options.target ?? cfg?.rootDir ?? ".");
3214
- const output = (0, import_node_path7.resolve)(options.out ?? (0, import_node_path7.resolve)(target, ".odla/security/hosted"));
3215
- const outputRelative = (0, import_node_path7.relative)(target, output).split(import_node_path7.sep).join("/");
6185
+ const target = (0, import_node_path8.resolve)(options.target ?? cfg?.rootDir ?? ".");
6186
+ const output = (0, import_node_path8.resolve)(options.out ?? (0, import_node_path8.resolve)(target, ".odla/security/hosted"));
6187
+ const outputRelative = (0, import_node_path8.relative)(target, output).split(import_node_path8.sep).join("/");
3216
6188
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
3217
6189
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
3218
6190
  const tokenRequest = {
@@ -3223,8 +6195,8 @@ async function runHostedSecurity(options) {
3223
6195
  scope: selfAudit ? "platform:security:self" : null
3224
6196
  };
3225
6197
  const token = await injectedToken(options, tokenRequest);
3226
- const snapshot = await (0, import_node.snapshotDirectory)(target, {
3227
- exclude: !outputRelative.startsWith("../") && !(0, import_node_path7.isAbsolute)(outputRelative) ? [outputRelative] : []
6198
+ const snapshot = await (0, import_node2.snapshotDirectory)(target, {
6199
+ exclude: !outputRelative.startsWith("../") && !(0, import_node_path8.isAbsolute)(outputRelative) ? [outputRelative] : []
3228
6200
  });
3229
6201
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
3230
6202
  platform,
@@ -3242,7 +6214,7 @@ async function runHostedSecurity(options) {
3242
6214
  });
3243
6215
  const harness = (0, import_security.createSecurityHarness)({
3244
6216
  profile,
3245
- store: new import_node.FileRunStore((0, import_node_path7.resolve)(output, "state")),
6217
+ store: new import_node2.FileRunStore((0, import_node_path8.resolve)(output, "state")),
3246
6218
  discoveryReasoner: hosted.discoveryReasoner,
3247
6219
  validationReasoner: hosted.validationReasoner,
3248
6220
  policy: {
@@ -3251,22 +6223,22 @@ async function runHostedSecurity(options) {
3251
6223
  allowNetwork: false
3252
6224
  }
3253
6225
  });
3254
- const report = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
3255
- await (0, import_node.writeSecurityArtifacts)(output, report);
3256
- const reportDigest = await (0, import_security.securityFingerprint)(report);
6226
+ const report2 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
6227
+ await (0, import_node2.writeSecurityArtifacts)(output, report2);
6228
+ const reportDigest = await (0, import_security.securityFingerprint)(report2);
3257
6229
  await hosted.complete({
3258
6230
  reportDigest,
3259
- coverageStatus: report.coverageStatus,
3260
- confirmed: report.metrics.confirmed,
3261
- candidates: report.metrics.candidates
6231
+ coverageStatus: report2.coverageStatus,
6232
+ confirmed: report2.metrics.confirmed,
6233
+ candidates: report2.metrics.candidates
3262
6234
  }, { signal: options.signal });
3263
- printSummary(options.stdout ?? console, appId, env, hosted.run, report, output);
3264
- return Object.freeze({ report, run: hosted.run, output });
6235
+ printSummary(options.stdout ?? console, appId, env, hosted.run, report2, output);
6236
+ return Object.freeze({ report: report2, run: hosted.run, output });
3265
6237
  }
3266
6238
  function selectEnv(requested, declared, configPath, rootDir) {
3267
6239
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
3268
6240
  if (!env || !declared.includes(env)) {
3269
- const shown = (0, import_node_path7.relative)(rootDir, configPath) || configPath;
6241
+ const shown = (0, import_node_path8.relative)(rootDir, configPath) || configPath;
3270
6242
  throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
3271
6243
  }
3272
6244
  return env;
@@ -3287,234 +6259,20 @@ function profileFor(name, maxHuntTasks) {
3287
6259
  if (!Number.isSafeInteger(maxHuntTasks) || maxHuntTasks < 1) throw new Error("maxHuntTasks must be a positive integer");
3288
6260
  return { ...profile, maxHuntTasks };
3289
6261
  }
3290
- function printSummary(out, appId, env, run, report, output) {
3291
- const complete = report.coverage.filter((cell) => cell.state === "complete").length;
6262
+ function printSummary(out, appId, env, run, report2, output) {
6263
+ const complete = report2.coverage.filter((cell) => cell.state === "complete").length;
3292
6264
  out.log(`security: ${appId}/${env} run=${run.runId} profile=${run.profileVersion}`);
3293
6265
  out.log(` discovery: ${run.discovery.identity.provider}/${run.discovery.identity.model}`);
3294
6266
  out.log(` validation: ${run.validation.identity.provider}/${run.validation.identity.model}`);
3295
- 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}`);
3296
- if (report.callBudget) out.log(` calls: discovery=${formatBudget(report.callBudget.discovery)} validation=${formatBudget(report.callBudget.validation)}`);
3297
- out.log(` findings: confirmed=${report.metrics.confirmed} needs_reproduction=${report.metrics.needsReproduction} candidates=${report.metrics.candidates}`);
3298
- out.log(` report: ${(0, import_node_path7.resolve)(output, "REPORT.md")}`);
6267
+ 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}`);
6268
+ if (report2.callBudget) out.log(` calls: discovery=${formatBudget(report2.callBudget.discovery)} validation=${formatBudget(report2.callBudget.validation)}`);
6269
+ out.log(` findings: confirmed=${report2.metrics.confirmed} needs_reproduction=${report2.metrics.needsReproduction} candidates=${report2.metrics.candidates}`);
6270
+ out.log(` report: ${(0, import_node_path8.resolve)(output, "REPORT.md")}`);
3299
6271
  }
3300
6272
  function formatBudget(usage) {
3301
6273
  return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
3302
6274
  }
3303
6275
 
3304
- // src/security-hosted-github.ts
3305
- var import_node_child_process4 = require("child_process");
3306
- var import_node_util2 = require("util");
3307
-
3308
- // src/security-hosted-request.ts
3309
- async function requestHostedSecurityJson(options, path, init, action) {
3310
- const platform = platformAudience(options.platform);
3311
- const token = hostedSecurityCredential(options.token);
3312
- const requestInit = {
3313
- ...init,
3314
- redirect: "error",
3315
- credentials: "omit",
3316
- cache: "no-store",
3317
- signal: options.signal,
3318
- headers: {
3319
- authorization: `Bearer ${token}`,
3320
- "content-type": "application/json",
3321
- ...init.headers
3322
- }
3323
- };
3324
- const response = await (options.fetch ?? fetch)(new URL(path, platform), requestInit);
3325
- const body = await response.json().catch(() => ({}));
3326
- if (!response.ok) {
3327
- const code = optionalHostedText(body.error?.code, "error code", 128);
3328
- const message = optionalHostedText(body.error?.message, "error message", 300);
3329
- throw new Error(`${action} failed (${response.status})${code ? ` ${code}` : ""}${message ? `: ${message}` : ""}`);
3330
- }
3331
- return body;
3332
- }
3333
- function hostedIdentifier(value, name) {
3334
- const result = optionalHostedText(value, name, 180);
3335
- if (!result || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(result)) {
3336
- throw new Error(`${name} is invalid`);
3337
- }
3338
- return result;
3339
- }
3340
- function positivePolicyVersion(value, name) {
3341
- if (!Number.isSafeInteger(value) || value < 1) {
3342
- throw new Error(`${name} is invalid`);
3343
- }
3344
- return value;
3345
- }
3346
- function optionalHostedText(value, name, maxLength) {
3347
- if (value === void 0 || value === null || value === "") return void 0;
3348
- if (typeof value !== "string" || value.length > maxLength || /[\u0000-\u001f\u007f]/.test(value)) {
3349
- throw new Error(`${name} is invalid`);
3350
- }
3351
- return value;
3352
- }
3353
- function githubRepositoryName(value) {
3354
- if (typeof value !== "string" || value.length > 201) {
3355
- throw new Error("repository must be owner/name");
3356
- }
3357
- const parts = value.split("/");
3358
- const owner = parts[0] ?? "";
3359
- const name = (parts[1] ?? "").replace(/\.git$/i, "");
3360
- 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 === "..") {
3361
- throw new Error("repository must be owner/name");
3362
- }
3363
- return `${owner}/${name}`;
3364
- }
3365
- function trustedGitHubInstallUrl(value) {
3366
- let url;
3367
- try {
3368
- url = new URL(value);
3369
- } catch {
3370
- throw new Error("odla.ai returned an invalid GitHub installation URL");
3371
- }
3372
- if (url.protocol !== "https:" || url.hostname !== "github.com" || url.username || url.password) {
3373
- throw new Error("odla.ai returned an untrusted GitHub installation URL");
3374
- }
3375
- return url.toString();
3376
- }
3377
- function hostedPollInterval(value = 2e3) {
3378
- if (!Number.isSafeInteger(value) || value < 100 || value > 3e4) {
3379
- throw new Error("poll interval must be 100-30000ms");
3380
- }
3381
- return value;
3382
- }
3383
- function hostedPollTimeout(value = 10 * 6e4) {
3384
- if (!Number.isSafeInteger(value) || value < 1e3 || value > 60 * 6e4) {
3385
- throw new Error("poll timeout must be 1000-3600000ms");
3386
- }
3387
- return value;
3388
- }
3389
- async function waitForHostedPoll(milliseconds, signal) {
3390
- if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
3391
- await new Promise((resolve7, reject) => {
3392
- const timer = setTimeout(resolve7, milliseconds);
3393
- signal?.addEventListener("abort", () => {
3394
- clearTimeout(timer);
3395
- reject(signal.reason ?? new DOMException("aborted", "AbortError"));
3396
- }, { once: true });
3397
- });
3398
- }
3399
- function isValidHostedSecurityPlan(value, env) {
3400
- 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;
3401
- 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;
3402
- return validRoute(value.routes?.discovery, "security.discovery") && validRoute(value.routes?.validation, "security.validation");
3403
- }
3404
- function hostedSecurityCredential(value) {
3405
- if (typeof value !== "string" || value.length < 8 || value.length > 8192 || /\s|[\u0000-\u001f\u007f]/.test(value)) {
3406
- throw new Error("Hosted security requires an injected odla developer token");
3407
- }
3408
- return value;
3409
- }
3410
-
3411
- // src/security-hosted-github.ts
3412
- async function connectGitHubSecuritySource(options) {
3413
- const out = options.stdout ?? console;
3414
- const repository = githubRepositoryName(options.repository);
3415
- const attempt = await requestHostedSecurityJson(
3416
- options,
3417
- "/registry/github/connect",
3418
- {
3419
- method: "POST",
3420
- body: JSON.stringify({
3421
- appId: hostedIdentifier(options.appId, "appId"),
3422
- env: hostedIdentifier(options.env, "env"),
3423
- repository
3424
- })
3425
- },
3426
- "start GitHub connection"
3427
- );
3428
- const serverExpiry = Date.parse(attempt?.expiresAt ?? "");
3429
- if (!attempt?.attemptId || !attempt.installUrl || !Number.isFinite(serverExpiry)) {
3430
- throw new Error("odla.ai returned an invalid GitHub connection attempt");
3431
- }
3432
- const installUrl = trustedGitHubInstallUrl(attempt.installUrl);
3433
- out.log(`GitHub approval: ${installUrl}`);
3434
- if (options.open !== false) {
3435
- await (options.openInstallUrl ?? openUrl)(installUrl);
3436
- out.log("github: opened installation approval in your browser");
3437
- }
3438
- const interval = hostedPollInterval(options.pollIntervalMs);
3439
- const now = options.now ?? Date.now;
3440
- const deadline = Math.min(serverExpiry, now() + hostedPollTimeout(options.pollTimeoutMs));
3441
- const wait = options.wait ?? waitForHostedPoll;
3442
- while (true) {
3443
- const state = await requestHostedSecurityJson(
3444
- options,
3445
- `/registry/github/connect/${encodeURIComponent(attempt.attemptId)}`,
3446
- {},
3447
- "check GitHub connection"
3448
- );
3449
- if (state.status !== "pending") {
3450
- if (state.status === "connected") return state;
3451
- throw new Error(`GitHub connection ${state.status}${state.failureCode ? `: ${state.failureCode}` : ""}`);
3452
- }
3453
- if (now() >= deadline) throw new Error("GitHub connection approval timed out");
3454
- await wait(Math.min(interval, Math.max(0, deadline - now())), options.signal);
3455
- }
3456
- }
3457
- async function listGitHubSecuritySources(options) {
3458
- const appId = hostedIdentifier(options.appId, "appId");
3459
- const env = hostedIdentifier(options.env, "env");
3460
- const body = await requestHostedSecurityJson(
3461
- options,
3462
- `/registry/apps/${encodeURIComponent(appId)}/github/sources?env=${encodeURIComponent(env)}`,
3463
- {},
3464
- "list GitHub security sources"
3465
- );
3466
- return Array.isArray(body.sources) ? body.sources : [];
3467
- }
3468
- async function disconnectGitHubSecuritySource(options) {
3469
- const appId = hostedIdentifier(options.appId, "appId");
3470
- const sourceId = hostedIdentifier(options.sourceId, "sourceId");
3471
- await requestHostedSecurityJson(
3472
- options,
3473
- `/registry/apps/${encodeURIComponent(appId)}/github/sources/${encodeURIComponent(sourceId)}`,
3474
- { method: "DELETE" },
3475
- "disconnect GitHub security source"
3476
- );
3477
- }
3478
- function repositoryFromGitRemote(remoteInput) {
3479
- const remote = remoteInput.trim();
3480
- const scp = /^git@github\.com:([^/\s]+)\/([^/\s]+?)\/?$/.exec(remote);
3481
- if (scp) return githubRepositoryName(`${scp[1]}/${scp[2].replace(/\.git$/, "")}`);
3482
- let url;
3483
- try {
3484
- url = new URL(remote);
3485
- } catch {
3486
- throw new Error("origin must be a github.com HTTPS or SSH repository URL");
3487
- }
3488
- if (url.hostname !== "github.com" || url.protocol !== "https:" && url.protocol !== "ssh:") {
3489
- throw new Error("origin must be a github.com HTTPS or SSH repository URL");
3490
- }
3491
- if (url.password || url.protocol === "https:" && url.username || url.search || url.hash) {
3492
- throw new Error("credential-bearing GitHub origin URLs are not accepted");
3493
- }
3494
- const parts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
3495
- if (parts.length !== 2) {
3496
- throw new Error("origin must identify one GitHub owner/name repository");
3497
- }
3498
- return githubRepositoryName(`${parts[0]}/${parts[1].replace(/\.git$/, "")}`);
3499
- }
3500
- async function inferGitHubRepository(cwd = process.cwd(), readOrigin = defaultReadOrigin) {
3501
- let remote;
3502
- try {
3503
- remote = await readOrigin(cwd);
3504
- } catch {
3505
- throw new Error("Could not read git origin; pass --repo owner/name");
3506
- }
3507
- return repositoryFromGitRemote(remote);
3508
- }
3509
- async function defaultReadOrigin(cwd) {
3510
- const result = await (0, import_node_util2.promisify)(import_node_child_process4.execFile)(
3511
- "git",
3512
- ["remote", "get-url", "origin"],
3513
- { cwd, encoding: "utf8" }
3514
- );
3515
- return result.stdout;
3516
- }
3517
-
3518
6276
  // src/security-hosted-intent.ts
3519
6277
  async function getHostedSecurityIntent(options) {
3520
6278
  const appId = hostedIdentifier(options.appId, "appId");
@@ -3524,19 +6282,19 @@ async function getHostedSecurityIntent(options) {
3524
6282
  const query = new URLSearchParams({ env, sourceId });
3525
6283
  if (ref) query.set("ref", ref);
3526
6284
  if (options.profile) query.set("profile", options.profile);
3527
- const response = await requestHostedSecurityJson(
6285
+ const response2 = await requestHostedSecurityJson(
3528
6286
  options,
3529
6287
  `/registry/apps/${encodeURIComponent(appId)}/security/intent?${query.toString()}`,
3530
6288
  {},
3531
6289
  "read hosted security execution intent"
3532
6290
  );
3533
- if (!isValidHostedSecurityPlan(response.plan, env) || !isValidIntent(response.intent, { appId, env, sourceId })) {
6291
+ if (!isValidHostedSecurityPlan(response2.plan, env) || !isValidIntent(response2.intent, { appId, env, sourceId })) {
3534
6292
  throw new Error("odla.ai returned an invalid hosted security execution intent");
3535
6293
  }
3536
- if (response.intent.planDigest !== response.plan.planDigest) {
6294
+ if (response2.intent.planDigest !== response2.plan.planDigest) {
3537
6295
  throw new Error("odla.ai returned a hosted security intent for a different processing plan");
3538
6296
  }
3539
- return response;
6297
+ return response2;
3540
6298
  }
3541
6299
  function isValidIntent(value, expected) {
3542
6300
  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";
@@ -3599,8 +6357,8 @@ async function startHostedSecurityJob(options) {
3599
6357
  "start hosted security job"
3600
6358
  );
3601
6359
  } catch (error) {
3602
- const message = error instanceof Error ? error.message : String(error);
3603
- if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message)) {
6360
+ const message3 = error instanceof Error ? error.message : String(error);
6361
+ if (/\b(?:plan_conflict|policy_conflict|intent_conflict|security_ai_not_ready)\b/.test(message3)) {
3604
6362
  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 });
3605
6363
  }
3606
6364
  throw error;
@@ -3635,7 +6393,7 @@ async function followHostedSecurityJob(options) {
3635
6393
  const interval = hostedPollInterval(options.pollIntervalMs);
3636
6394
  const now = options.now ?? Date.now;
3637
6395
  const deadline = now() + hostedPollTimeout(options.pollTimeoutMs ?? 60 * 6e4);
3638
- const wait = options.wait ?? waitForHostedPoll;
6396
+ const wait2 = options.wait ?? waitForHostedPoll;
3639
6397
  let prior = "";
3640
6398
  while (true) {
3641
6399
  const job = await getHostedSecurityJob(options);
@@ -3646,7 +6404,7 @@ async function followHostedSecurityJob(options) {
3646
6404
  if (now() >= deadline) {
3647
6405
  throw new Error(`Hosted security job ${job.jobId} did not finish before the polling deadline`);
3648
6406
  }
3649
- await wait(Math.min(interval, Math.max(0, deadline - now())), options.signal);
6407
+ await wait2(Math.min(interval, Math.max(0, deadline - now())), options.signal);
3650
6408
  }
3651
6409
  }
3652
6410
  async function getHostedSecurityReport(options) {
@@ -3755,13 +6513,13 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
3755
6513
  }
3756
6514
  throw new Error(`hosted security job ${result.jobId} ended ${result.status}${result.errorCode ? `: ${result.errorCode}` : ""}`);
3757
6515
  }
3758
- const report = await getHostedSecurityReport({ ...context, jobId: result.jobId });
6516
+ const report2 = await getHostedSecurityReport({ ...context, jobId: result.jobId });
3759
6517
  if (parsed.options.json === true) {
3760
- context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report }, null, 2));
6518
+ context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report: report2 }, null, 2));
3761
6519
  } else {
3762
- printHostedReport(context.stdout, report);
6520
+ printHostedReport(context.stdout, report2);
3763
6521
  }
3764
- enforceHostedReportGate(report, parsed, context.stdout, parsed.options.json !== true);
6522
+ enforceHostedReportGate(report2, parsed, context.stdout, parsed.options.json !== true);
3765
6523
  }
3766
6524
  async function runLocalSecurityCommand(parsed, dependencies) {
3767
6525
  if (parsed.options.source === true) {
@@ -3828,13 +6586,13 @@ async function runLocalSecurityCommand(parsed, dependencies) {
3828
6586
  });
3829
6587
  enforceLocalGate(result.report, parsed);
3830
6588
  }
3831
- function enforceLocalGate(report, parsed) {
6589
+ function enforceLocalGate(report2, parsed) {
3832
6590
  const failOn = severityOpt(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
3833
6591
  const candidateValue = parsed.options["fail-on-candidates"];
3834
6592
  const failOnCandidates = candidateValue === false ? void 0 : severityOpt(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
3835
- const confirmed = (0, import_security2.findingsAtOrAbove)(report, failOn);
3836
- const leads = failOnCandidates ? (0, import_security2.findingsAtOrAbove)(report, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
3837
- const incomplete = report.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
6593
+ const confirmed = (0, import_security2.findingsAtOrAbove)(report2, failOn);
6594
+ const leads = failOnCandidates ? (0, import_security2.findingsAtOrAbove)(report2, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
6595
+ const incomplete = report2.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
3838
6596
  if (confirmed.length || leads.length || incomplete) {
3839
6597
  throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
3840
6598
  }
@@ -3873,9 +6631,9 @@ async function securityCommand(parsed, dependencies) {
3873
6631
  assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 3);
3874
6632
  const jobId = requiredSecurityPositional(parsed, 2, "job id");
3875
6633
  const context = await hostedSecurityContext(parsed, dependencies);
3876
- const report = await getHostedSecurityReport({ ...context, jobId });
3877
- if (parsed.options.json === true) context.stdout.log(JSON.stringify(report, null, 2));
3878
- else printHostedReport(context.stdout, report);
6634
+ const report2 = await getHostedSecurityReport({ ...context, jobId });
6635
+ if (parsed.options.json === true) context.stdout.log(JSON.stringify(report2, null, 2));
6636
+ else printHostedReport(context.stdout, report2);
3879
6637
  return;
3880
6638
  }
3881
6639
  if (sub !== "run") {
@@ -3958,9 +6716,9 @@ async function securityStatus(parsed, dependencies) {
3958
6716
  }
3959
6717
 
3960
6718
  // src/skill.ts
3961
- var import_node_fs10 = require("fs");
3962
- var import_node_os = require("os");
3963
- var import_node_path8 = require("path");
6719
+ var import_node_fs11 = require("fs");
6720
+ var import_node_os2 = require("os");
6721
+ var import_node_path9 = require("path");
3964
6722
  var import_node_url2 = require("url");
3965
6723
 
3966
6724
  // src/skill-adapters.ts
@@ -4021,8 +6779,8 @@ function installSkill(options = {}) {
4021
6779
  const files = listFiles(sourceDir);
4022
6780
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
4023
6781
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
4024
- const root = (0, import_node_path8.resolve)(options.dir ?? process.cwd());
4025
- const home = (0, import_node_path8.resolve)(options.homeDir ?? (0, import_node_os.homedir)());
6782
+ const root = (0, import_node_path9.resolve)(options.dir ?? process.cwd());
6783
+ const home = (0, import_node_path9.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
4026
6784
  const plans = /* @__PURE__ */ new Map();
4027
6785
  const targets = /* @__PURE__ */ new Map();
4028
6786
  const rememberTarget = (harness, target) => {
@@ -4036,48 +6794,48 @@ function installSkill(options = {}) {
4036
6794
  plans.set(target, { target, content, boundary, managedMerge });
4037
6795
  };
4038
6796
  const planSkillTree = (targetDir2, boundary = root) => {
4039
- for (const rel of files) plan((0, import_node_path8.join)(targetDir2, rel), (0, import_node_fs10.readFileSync)((0, import_node_path8.join)(sourceDir, rel), "utf8"), false, boundary);
6797
+ for (const rel of files) plan((0, import_node_path9.join)(targetDir2, rel), (0, import_node_fs11.readFileSync)((0, import_node_path9.join)(sourceDir, rel), "utf8"), false, boundary);
4040
6798
  };
4041
6799
  let targetDir;
4042
6800
  if (options.global) {
4043
- const claudeRoot = (0, import_node_path8.join)(home, ".claude", "skills");
4044
- const codexRoot = (0, import_node_path8.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path8.join)(home, ".codex"), "skills");
6801
+ const claudeRoot = (0, import_node_path9.join)(home, ".claude", "skills");
6802
+ const codexRoot = (0, import_node_path9.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path9.join)(home, ".codex"), "skills");
4045
6803
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
4046
6804
  for (const harness of harnesses) {
4047
6805
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
4048
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path8.dirname)((0, import_node_path8.dirname)(codexRoot)));
6806
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path9.dirname)((0, import_node_path9.dirname)(codexRoot)));
4049
6807
  rememberTarget(harness, skillRoot);
4050
6808
  }
4051
6809
  } else {
4052
- const sharedRoot = (0, import_node_path8.join)(root, ".agents", "skills");
6810
+ const sharedRoot = (0, import_node_path9.join)(root, ".agents", "skills");
4053
6811
  planSkillTree(sharedRoot);
4054
- const claudeRoot = (0, import_node_path8.join)(root, ".claude", "skills");
6812
+ const claudeRoot = (0, import_node_path9.join)(root, ".claude", "skills");
4055
6813
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
4056
6814
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
4057
6815
  if (harnesses.includes("claude")) {
4058
6816
  for (const skill of skillNames(files)) {
4059
- const canonical = (0, import_node_fs10.readFileSync)((0, import_node_path8.join)(sourceDir, skill, "SKILL.md"), "utf8");
4060
- plan((0, import_node_path8.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
6817
+ const canonical = (0, import_node_fs11.readFileSync)((0, import_node_path9.join)(sourceDir, skill, "SKILL.md"), "utf8");
6818
+ plan((0, import_node_path9.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
4061
6819
  }
4062
6820
  rememberTarget("claude", claudeRoot);
4063
6821
  }
4064
6822
  if (harnesses.includes("cursor")) {
4065
- const cursorRule = (0, import_node_path8.join)(root, ".cursor", "rules", "odla.mdc");
6823
+ const cursorRule = (0, import_node_path9.join)(root, ".cursor", "rules", "odla.mdc");
4066
6824
  plan(cursorRule, CURSOR_RULE);
4067
6825
  rememberTarget("cursor", cursorRule);
4068
6826
  }
4069
6827
  if (harnesses.includes("agents")) {
4070
- const agentsFile = (0, import_node_path8.join)(root, "AGENTS.md");
6828
+ const agentsFile = (0, import_node_path9.join)(root, "AGENTS.md");
4071
6829
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
4072
6830
  rememberTarget("agents", agentsFile);
4073
6831
  }
4074
6832
  if (harnesses.includes("copilot")) {
4075
- const copilotFile = (0, import_node_path8.join)(root, ".github", "copilot-instructions.md");
6833
+ const copilotFile = (0, import_node_path9.join)(root, ".github", "copilot-instructions.md");
4076
6834
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
4077
6835
  rememberTarget("copilot", copilotFile);
4078
6836
  }
4079
6837
  if (harnesses.includes("gemini")) {
4080
- const geminiFile = (0, import_node_path8.join)(root, "GEMINI.md");
6838
+ const geminiFile = (0, import_node_path9.join)(root, "GEMINI.md");
4081
6839
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
4082
6840
  rememberTarget("gemini", geminiFile);
4083
6841
  }
@@ -4091,11 +6849,11 @@ function installSkill(options = {}) {
4091
6849
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
4092
6850
  continue;
4093
6851
  }
4094
- if (!(0, import_node_fs10.existsSync)(file.target)) {
6852
+ if (!(0, import_node_fs11.existsSync)(file.target)) {
4095
6853
  writtenPaths.add(file.target);
4096
6854
  continue;
4097
6855
  }
4098
- const current = (0, import_node_fs10.readFileSync)(file.target, "utf8");
6856
+ const current = (0, import_node_fs11.readFileSync)(file.target, "utf8");
4099
6857
  if (current === file.content) {
4100
6858
  unchangedPaths.add(file.target);
4101
6859
  } else if (file.managedMerge || options.force) {
@@ -4112,9 +6870,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
4112
6870
  );
4113
6871
  }
4114
6872
  for (const file of plans.values()) {
4115
- if (!(0, import_node_fs10.existsSync)(file.target) || (0, import_node_fs10.readFileSync)(file.target, "utf8") !== file.content) {
4116
- (0, import_node_fs10.mkdirSync)((0, import_node_path8.dirname)(file.target), { recursive: true });
4117
- (0, import_node_fs10.writeFileSync)(file.target, file.content);
6873
+ if (!(0, import_node_fs11.existsSync)(file.target) || (0, import_node_fs11.readFileSync)(file.target, "utf8") !== file.content) {
6874
+ (0, import_node_fs11.mkdirSync)((0, import_node_path9.dirname)(file.target), { recursive: true });
6875
+ (0, import_node_fs11.writeFileSync)(file.target, file.content);
4118
6876
  }
4119
6877
  }
4120
6878
  const skills = skillNames(files);
@@ -4133,7 +6891,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
4133
6891
  };
4134
6892
  }
4135
6893
  function pathsUnder(root, paths) {
4136
- return [...paths].map((path) => (0, import_node_path8.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path8.sep}`) && !(0, import_node_path8.isAbsolute)(path)).sort();
6894
+ return [...paths].map((path) => (0, import_node_path9.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path9.sep}`) && !(0, import_node_path9.isAbsolute)(path)).sort();
4137
6895
  }
4138
6896
  function normalizeHarnesses(values, global) {
4139
6897
  const requested = values?.length ? values : ["claude"];
@@ -4155,9 +6913,9 @@ function normalizeHarnesses(values, global) {
4155
6913
  function managedFileContent(path, block, force, boundary) {
4156
6914
  const symlink = symlinkedComponent(boundary, path);
4157
6915
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
4158
- if (!(0, import_node_fs10.existsSync)(path)) return `${block}
6916
+ if (!(0, import_node_fs11.existsSync)(path)) return `${block}
4159
6917
  `;
4160
- const current = (0, import_node_fs10.readFileSync)(path, "utf8");
6918
+ const current = (0, import_node_fs11.readFileSync)(path, "utf8");
4161
6919
  const start = "<!-- odla-ai agent setup:start -->";
4162
6920
  const end = "<!-- odla-ai agent setup:end -->";
4163
6921
  const startAt = current.indexOf(start);
@@ -4178,15 +6936,15 @@ function managedFileContent(path, block, force, boundary) {
4178
6936
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
4179
6937
  }
4180
6938
  function symlinkedComponent(boundary, target) {
4181
- const rel = (0, import_node_path8.relative)(boundary, target);
4182
- if (rel === ".." || rel.startsWith(`..${import_node_path8.sep}`) || (0, import_node_path8.isAbsolute)(rel)) {
6939
+ const rel = (0, import_node_path9.relative)(boundary, target);
6940
+ if (rel === ".." || rel.startsWith(`..${import_node_path9.sep}`) || (0, import_node_path9.isAbsolute)(rel)) {
4183
6941
  throw new Error(`agent setup target escapes its install root: ${target}`);
4184
6942
  }
4185
6943
  let current = boundary;
4186
- for (const part of rel.split(import_node_path8.sep).filter(Boolean)) {
4187
- current = (0, import_node_path8.join)(current, part);
6944
+ for (const part of rel.split(import_node_path9.sep).filter(Boolean)) {
6945
+ current = (0, import_node_path9.join)(current, part);
4188
6946
  try {
4189
- if ((0, import_node_fs10.lstatSync)(current).isSymbolicLink()) return current;
6947
+ if ((0, import_node_fs11.lstatSync)(current).isSymbolicLink()) return current;
4190
6948
  } catch (error) {
4191
6949
  if (error.code !== "ENOENT") throw error;
4192
6950
  }
@@ -4197,13 +6955,13 @@ function skillNames(files) {
4197
6955
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
4198
6956
  }
4199
6957
  function listFiles(dir) {
4200
- if (!(0, import_node_fs10.existsSync)(dir)) return [];
6958
+ if (!(0, import_node_fs11.existsSync)(dir)) return [];
4201
6959
  const results = [];
4202
6960
  const walk = (current) => {
4203
- for (const entry of (0, import_node_fs10.readdirSync)(current, { withFileTypes: true })) {
4204
- const path = (0, import_node_path8.join)(current, entry.name);
6961
+ for (const entry of (0, import_node_fs11.readdirSync)(current, { withFileTypes: true })) {
6962
+ const path = (0, import_node_path9.join)(current, entry.name);
4205
6963
  if (entry.isDirectory()) walk(path);
4206
- else results.push((0, import_node_path8.relative)(dir, path));
6964
+ else results.push((0, import_node_path9.relative)(dir, path));
4207
6965
  }
4208
6966
  };
4209
6967
  walk(dir);
@@ -4387,6 +7145,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
4387
7145
  printCapabilities(parsed.options.json === true);
4388
7146
  return;
4389
7147
  }
7148
+ if (command === "code") {
7149
+ await codeCommand(parsed, dependencies);
7150
+ return;
7151
+ }
4390
7152
  if (command === "admin") {
4391
7153
  await adminCommand(parsed);
4392
7154
  return;
@@ -4543,6 +7305,8 @@ async function calendarCommand(parsed, dependencies) {
4543
7305
  0 && (module.exports = {
4544
7306
  AGENT_HARNESSES,
4545
7307
  CAPABILITIES,
7308
+ CODE_BUILD_RECIPES,
7309
+ CODE_PI_IMAGE,
4546
7310
  GOOGLE_CALENDAR_EVENTS_SCOPE,
4547
7311
  SYSTEM_AI_PURPOSES,
4548
7312
  adminAi,
@@ -4552,6 +7316,7 @@ async function calendarCommand(parsed, dependencies) {
4552
7316
  calendarDisconnect,
4553
7317
  calendarServiceConfig,
4554
7318
  calendarStatus,
7319
+ codeConnect,
4555
7320
  connectGitHubSecuritySource,
4556
7321
  disconnectGitHubSecuritySource,
4557
7322
  doctor,
@@ -4573,6 +7338,7 @@ async function calendarCommand(parsed, dependencies) {
4573
7338
  redactSecrets,
4574
7339
  repositoryFromGitRemote,
4575
7340
  runCli,
7341
+ runCodeRuntime,
4576
7342
  runHostedSecurity,
4577
7343
  secretsPush,
4578
7344
  secretsSet,