@odla-ai/cli 0.46.12 → 0.48.1

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
@@ -1385,6 +1385,7 @@ var COMMAND_SURFACE = {
1385
1385
  watch: {}
1386
1386
  },
1387
1387
  provision: {},
1388
+ promotion: { plan: {}, inspect: {}, status: {}, apply: {}, rollback: {}, next: {}, record: {}, cancel: {} },
1388
1389
  runbook: {
1389
1390
  ask: {},
1390
1391
  search: {},
@@ -3733,9 +3734,9 @@ function canonicalValue(value2) {
3733
3734
  }
3734
3735
  if (Array.isArray(value2)) return value2.map(canonicalValue);
3735
3736
  if (value2 && typeof value2 === "object") {
3736
- const record11 = value2;
3737
+ const record12 = value2;
3737
3738
  return Object.fromEntries(
3738
- Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
3739
+ Object.keys(record12).filter((key) => record12[key] !== void 0).sort().map((key) => [key, canonicalValue(record12[key])])
3739
3740
  );
3740
3741
  }
3741
3742
  throw new TypeError("canonical JSON rejects unsupported values");
@@ -6119,10 +6120,10 @@ var import_node_fs16 = require("fs");
6119
6120
  var import_node_os5 = require("os");
6120
6121
  var import_node_path16 = require("path");
6121
6122
 
6122
- // ../harness/dist/chunk-I43KTCJ2.js
6123
+ // ../harness/dist/chunk-U324RQ4N.js
6123
6124
  var HARNESS_PROTOCOL_VERSION = 1;
6124
6125
 
6125
- // ../harness/dist/chunk-HDIR4MM5.js
6126
+ // ../harness/dist/chunk-5LRYJKUI.js
6126
6127
  var import_child_process = require("child_process");
6127
6128
  var import_fs = require("fs");
6128
6129
  var import_promises2 = require("fs/promises");
@@ -6290,8 +6291,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
6290
6291
  const maxFiles = options.maxFiles ?? 2e4;
6291
6292
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
6292
6293
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
6293
- const entries = inventory.flatMap((record11) => {
6294
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
6294
+ const entries = inventory.flatMap((record12) => {
6295
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record12);
6295
6296
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
6296
6297
  });
6297
6298
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -6495,7 +6496,165 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
6495
6496
  }
6496
6497
  }
6497
6498
 
6498
- // ../harness/dist/chunk-4EPJJMFG.js
6499
+ // ../harness/dist/chunk-FAN2R3GW.js
6500
+ var text3 = (value2, maximum) => {
6501
+ if (typeof value2 !== "string") return void 0;
6502
+ const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
6503
+ return bounded ? bounded.slice(0, maximum) : void 0;
6504
+ };
6505
+ var integer = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
6506
+ var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
6507
+ var excerpt = (value2, tail = false) => {
6508
+ if (typeof value2 !== "string") return void 0;
6509
+ const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
6510
+ const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
6511
+ if (!source) return void 0;
6512
+ const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
6513
+ const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
6514
+ return text3(selected.join("\n"), 2400);
6515
+ };
6516
+ var paths = (value2) => {
6517
+ if (!Array.isArray(value2)) return void 0;
6518
+ const items = value2.flatMap((item) => {
6519
+ const path = text3(item, 1024);
6520
+ return path ? [path] : [];
6521
+ }).slice(0, 12);
6522
+ return items.length ? items : void 0;
6523
+ };
6524
+ function patchStats(value2) {
6525
+ if (typeof value2 !== "string") return {};
6526
+ let additions = 0;
6527
+ let deletions = 0;
6528
+ for (const line2 of value2.slice(0, 262144).split("\n")) {
6529
+ if (line2.startsWith("+++") || line2.startsWith("---")) continue;
6530
+ if (line2.startsWith("+")) additions += 1;
6531
+ else if (line2.startsWith("-")) deletions += 1;
6532
+ }
6533
+ return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
6534
+ }
6535
+ function searchResults(value2) {
6536
+ if (typeof value2 !== "string") return void 0;
6537
+ const results = value2.split("\n").flatMap((line2) => {
6538
+ const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
6539
+ if (!match) return [];
6540
+ const lineNumber = Number(match[2]);
6541
+ const itemText = text3(match[3], 240);
6542
+ if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
6543
+ return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
6544
+ }).slice(0, 5);
6545
+ return results.length ? results : void 0;
6546
+ }
6547
+ function codeToolRequestPresentation(request3) {
6548
+ const input = request3.input;
6549
+ if (request3.tool === "sandbox.read") {
6550
+ const path = text3(input.path, 1024);
6551
+ if (!path) return void 0;
6552
+ const startLine = integer(input.startLine);
6553
+ const endLine = integer(input.endLine);
6554
+ return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
6555
+ }
6556
+ if (request3.tool === "sandbox.list") {
6557
+ const scope = text3(input.prefix, 1024);
6558
+ return { kind: "list", ...scope ? { scope } : {} };
6559
+ }
6560
+ if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
6561
+ const query = text3(input.query, 512);
6562
+ const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
6563
+ if (request3.tool === "sandbox.search" && !query) return void 0;
6564
+ return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
6565
+ }
6566
+ if (request3.tool === "sandbox.apply_patch") {
6567
+ return { kind: "patch", ...patchStats(input.patch) };
6568
+ }
6569
+ const recipeId = text3(input.recipeId, 120);
6570
+ return recipeId ? { kind: "recipe", recipeId } : void 0;
6571
+ }
6572
+ function codeToolResultPresentation(request3, response2) {
6573
+ const started = codeToolRequestPresentation(request3);
6574
+ if (!started || !response2.ok) return started;
6575
+ const details = record5(response2.details);
6576
+ if (started.kind === "read") {
6577
+ return {
6578
+ ...started,
6579
+ ...integer(details?.startLine) ? { startLine: integer(details?.startLine) } : {},
6580
+ ...integer(details?.endLine) ? { endLine: integer(details?.endLine) } : {},
6581
+ ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
6582
+ };
6583
+ }
6584
+ if (started.kind === "list") {
6585
+ const listed = response2.content.split("\n").filter((line2) => line2 && !line2.startsWith("\u2026") && !line2.startsWith("Workspace ")).map((line2) => text3(line2, 1024)).filter((line2) => Boolean(line2)).slice(0, 8);
6586
+ return {
6587
+ ...started,
6588
+ ...integer(details?.count) !== void 0 ? { count: integer(details?.count) } : {},
6589
+ ...listed.length ? { paths: listed } : {}
6590
+ };
6591
+ }
6592
+ if (started.kind === "query") {
6593
+ const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
6594
+ const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
6595
+ return {
6596
+ ...started,
6597
+ ...integer(details?.count) !== void 0 ? { count: integer(details?.count) } : {},
6598
+ ...results ? { results } : {},
6599
+ ...resultExcerpt ? { excerpt: resultExcerpt } : {}
6600
+ };
6601
+ }
6602
+ if (started.kind === "patch") {
6603
+ return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
6604
+ }
6605
+ const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
6606
+ return {
6607
+ ...started,
6608
+ ...integer(details?.exitCode) !== void 0 ? { exitCode: integer(details?.exitCode) } : {},
6609
+ ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
6610
+ ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
6611
+ ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
6612
+ };
6613
+ }
6614
+ var MAX_FAILURE_REASON = 240;
6615
+ function toolFailureReason(response2) {
6616
+ if (response2.ok) return void 0;
6617
+ const supplied = response2.details?.failureReason;
6618
+ const reason = typeof supplied === "string" && supplied || DEFAULT_FAILURE_REASON[response2.content] || response2.content || "tool request failed; inspect the tool input and workspace state";
6619
+ return reason.slice(0, MAX_FAILURE_REASON);
6620
+ }
6621
+ var DEFAULT_FAILURE_REASON = {
6622
+ "tool denied by CaMeL policy": "tool denied by CaMeL policy",
6623
+ "review sessions are read-only": "review session is read-only; workspace changes are not permitted"
6624
+ };
6625
+ function observedBroker(input) {
6626
+ const now = input.now ?? Date.now;
6627
+ return {
6628
+ execute: async (context, request3) => {
6629
+ const startedAt = now();
6630
+ const operationId = input.operationIdFor(request3.requestId);
6631
+ const startedPresentation = codeToolRequestPresentation(request3);
6632
+ await input.emit({
6633
+ type: "tool",
6634
+ phase: "started",
6635
+ tool: request3.tool,
6636
+ operationId,
6637
+ ...startedPresentation ? { presentation: startedPresentation } : {}
6638
+ });
6639
+ const response2 = await input.broker.execute(context, request3);
6640
+ const completedPresentation = codeToolResultPresentation(request3, response2);
6641
+ const failureReason = toolFailureReason(response2);
6642
+ await input.emit({
6643
+ type: "tool",
6644
+ phase: "completed",
6645
+ tool: request3.tool,
6646
+ ok: response2.ok,
6647
+ durationMs: now() - startedAt,
6648
+ operationId,
6649
+ ...failureReason ? { failureReason } : {},
6650
+ ...completedPresentation ? { presentation: completedPresentation } : {}
6651
+ });
6652
+ return response2;
6653
+ }
6654
+ };
6655
+ }
6656
+
6657
+ // ../harness/dist/chunk-Q4NL5XO3.js
6499
6658
  var import_crypto = require("crypto");
6500
6659
  var import_promises5 = require("fs/promises");
6501
6660
  var import_path5 = require("path");
@@ -6539,8 +6698,8 @@ function normalize(value2) {
6539
6698
  if (Array.isArray(value2)) return value2.map(normalize);
6540
6699
  if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
6541
6700
  if (typeof value2 === "object") {
6542
- const record11 = value2;
6543
- return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
6701
+ const record12 = value2;
6702
+ return Object.fromEntries(Object.keys(record12).filter((key) => record12[key] !== void 0).sort().map((key) => [key, normalize(record12[key])]));
6544
6703
  }
6545
6704
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
6546
6705
  }
@@ -6832,7 +6991,7 @@ function validateSnapshot(snapshot, limits) {
6832
6991
  }
6833
6992
  }
6834
6993
 
6835
- // ../harness/dist/chunk-4EPJJMFG.js
6994
+ // ../harness/dist/chunk-Q4NL5XO3.js
6836
6995
  var import_child_process4 = require("child_process");
6837
6996
  var import_promises6 = require("fs/promises");
6838
6997
  var import_path6 = require("path");
@@ -7136,7 +7295,7 @@ function looksLikeDestination(value2) {
7136
7295
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text4) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text4);
7137
7296
  }
7138
7297
 
7139
- // ../harness/dist/chunk-4EPJJMFG.js
7298
+ // ../harness/dist/chunk-Q4NL5XO3.js
7140
7299
  var import_promises10 = require("fs/promises");
7141
7300
  var import_promises11 = require("fs/promises");
7142
7301
  var import_path10 = require("path");
@@ -7404,7 +7563,7 @@ async function buildCodeGraph(input) {
7404
7563
  return builder.build();
7405
7564
  }
7406
7565
 
7407
- // ../harness/dist/chunk-4EPJJMFG.js
7566
+ // ../harness/dist/chunk-Q4NL5XO3.js
7408
7567
  var import_crypto4 = require("crypto");
7409
7568
  async function digestStagedWorkspace(root, limits) {
7410
7569
  const files = [];
@@ -7463,7 +7622,7 @@ function createCodeRuntimeControlClient(options) {
7463
7622
  }
7464
7623
  const value2 = await response2.json().catch(() => null);
7465
7624
  if (!response2.ok) {
7466
- const problem = record5(record5(value2)?.error);
7625
+ const problem = record6(record6(value2)?.error);
7467
7626
  throw new CodeRuntimeControlError(
7468
7627
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
7469
7628
  response2.status,
@@ -7484,12 +7643,12 @@ function createCodeRuntimeControlClient(options) {
7484
7643
  await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
7485
7644
  ),
7486
7645
  infer: async (sessionId, inference) => {
7487
- const value2 = record5(await call4(
7646
+ const value2 = record6(await call4(
7488
7647
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
7489
7648
  inference,
7490
7649
  modelRequestTimeoutMs
7491
7650
  ));
7492
- if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
7651
+ if (!value2 || value2.requestId !== inference.requestId || !record6(value2.response) || !record6(value2.receipt)) {
7493
7652
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
7494
7653
  }
7495
7654
  return value2;
@@ -7669,12 +7828,12 @@ function validateHeartbeat(version, capabilities) {
7669
7828
  }
7670
7829
  }
7671
7830
  function parseSnapshot(value2) {
7672
- const root = record5(value2);
7673
- const host = record5(root?.host);
7831
+ const root = record6(value2);
7832
+ const host = record6(root?.host);
7674
7833
  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");
7675
7834
  const bindingIds = /* @__PURE__ */ new Set();
7676
7835
  const bindings = root.bindings.map((item) => {
7677
- const binding = record5(item);
7836
+ const binding = record6(item);
7678
7837
  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)) {
7679
7838
  throw invalid("binding");
7680
7839
  }
@@ -7684,10 +7843,10 @@ function parseSnapshot(value2) {
7684
7843
  const commandIds = /* @__PURE__ */ new Set();
7685
7844
  const commandSequences = /* @__PURE__ */ new Set();
7686
7845
  const commands = root.commands.map((item) => {
7687
- const command = record5(item);
7846
+ const command = record6(item);
7688
7847
  const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
7689
7848
  const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
7690
- 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)) || !record5(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
7849
+ 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)) || !record6(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
7691
7850
  commandIds.add(command.commandId);
7692
7851
  commandSequences.add(sequenceKey);
7693
7852
  return command;
@@ -7696,10 +7855,10 @@ function parseSnapshot(value2) {
7696
7855
  }
7697
7856
  async function parseSource(value2) {
7698
7857
  const repositoryLimits = { maximumFiles: 1e5, maximumBytes: 80 * 1024 * 1024 };
7699
- const snapshot = record5(record5(value2)?.snapshot);
7858
+ const snapshot = record6(record6(value2)?.snapshot);
7700
7859
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
7701
7860
  const files = snapshot.files.map((value22) => {
7702
- const file = record5(value22);
7861
+ const file = record6(value22);
7703
7862
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
7704
7863
  return { path: file.path, content: file.content };
7705
7864
  });
@@ -7708,11 +7867,11 @@ async function parseSource(value2) {
7708
7867
  const aliases = /* @__PURE__ */ new Set();
7709
7868
  const references = [];
7710
7869
  for (const item of referencesValue) {
7711
- const reference = record5(item);
7870
+ const reference = record6(item);
7712
7871
  if (!reference || typeof reference.alias !== "string" || !/^[a-z][a-z0-9-]{0,39}$/.test(reference.alias) || aliases.has(reference.alias) || reference.alias === "primary" || typeof reference.repository !== "string" || typeof reference.commitSha !== "string" || typeof reference.treeDigest !== "string" || !Array.isArray(reference.files)) throw invalid("reference source");
7713
7872
  aliases.add(reference.alias);
7714
7873
  const referenceFiles = reference.files.map((entry) => {
7715
- const file = record5(entry);
7874
+ const file = record6(entry);
7716
7875
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
7717
7876
  return { path: file.path, content: file.content };
7718
7877
  });
@@ -7727,31 +7886,31 @@ async function parseSource(value2) {
7727
7886
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
7728
7887
  }
7729
7888
  function parseReview(value2) {
7730
- const review = record5(record5(value2)?.review);
7731
- 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");
7889
+ const review = record6(record6(value2)?.review);
7890
+ 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 || !Number.isSafeInteger(review.score) || Number(review.score) < 0 || Number(review.score) > 100 || typeof review.summary !== "string" || !Array.isArray(review.findings) || review.findings.length > 12) throw invalid("review");
7732
7891
  return review;
7733
7892
  }
7734
7893
  function parseCandidate(value2) {
7735
- const candidate = record5(record5(value2)?.candidate);
7894
+ const candidate = record6(record6(value2)?.candidate);
7736
7895
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
7737
7896
  throw invalid("candidate");
7738
7897
  }
7739
7898
  return { candidateId: candidate.candidateId, status: candidate.status };
7740
7899
  }
7741
7900
  function parseCollaborationSkills(value2) {
7742
- const items = record5(value2)?.skills;
7901
+ const items = record6(value2)?.skills;
7743
7902
  if (!Array.isArray(items) || items.length > 16) throw invalid("collaboration skills");
7744
7903
  const skillNames2 = /* @__PURE__ */ new Set();
7745
7904
  const toolNames = /* @__PURE__ */ new Set();
7746
7905
  return items.map((item) => {
7747
- const skill = record5(item);
7906
+ const skill = record6(item);
7748
7907
  if (!skill || !validManifestName(skill.name) || skillNames2.has(skill.name) || skill.instructions !== void 0 && (typeof skill.instructions !== "string" || utf8Bytes(skill.instructions) > 32e3) || !Array.isArray(skill.tools) || !skill.tools.length || skill.tools.length > 128) {
7749
7908
  throw invalid("collaboration skill");
7750
7909
  }
7751
7910
  skillNames2.add(skill.name);
7752
7911
  const tools = skill.tools.map((candidate) => {
7753
- const tool = record5(candidate);
7754
- const inputSchema = record5(tool?.inputSchema);
7912
+ const tool = record6(candidate);
7913
+ const inputSchema = record6(tool?.inputSchema);
7755
7914
  if (!tool || !validManifestName(tool.name) || toolNames.has(tool.name) || typeof tool.description !== "string" || utf8Bytes(tool.description) > 8e3 || !inputSchema || jsonBytes(inputSchema) > 64e3 || tool.concurrency !== void 0 && tool.concurrency !== "parallel") {
7756
7915
  throw invalid("collaboration tool");
7757
7916
  }
@@ -7776,12 +7935,12 @@ function parseCollaborationSkills(value2) {
7776
7935
  }
7777
7936
  function validateCollaborationToolRequest(value2) {
7778
7937
  validCommandId(value2.commandId);
7779
- if (typeof value2.toolCallId !== "string" || value2.toolCallId.length > 256 || !/^[^\s\u0000-\u001f\u007f]+$/.test(value2.toolCallId) || !validManifestName(value2.skill) || !validManifestName(value2.tool) || !record5(value2.input) || jsonBytes(value2.input) > 128e3) {
7938
+ if (typeof value2.toolCallId !== "string" || value2.toolCallId.length > 256 || !/^[^\s\u0000-\u001f\u007f]+$/.test(value2.toolCallId) || !validManifestName(value2.skill) || !validManifestName(value2.tool) || !record6(value2.input) || jsonBytes(value2.input) > 128e3) {
7780
7939
  throw new TypeError("invalid Code collaboration tool request");
7781
7940
  }
7782
7941
  }
7783
7942
  function parseCollaborationToolOutput(value2) {
7784
- const output = record5(record5(value2)?.output);
7943
+ const output = record6(record6(value2)?.output);
7785
7944
  if (!output || output.isError !== void 0 && typeof output.isError !== "boolean") {
7786
7945
  throw invalid("collaboration tool");
7787
7946
  }
@@ -7790,7 +7949,7 @@ function parseCollaborationToolOutput(value2) {
7790
7949
  return { content: output.content, ...output.isError === true ? { isError: true } : {} };
7791
7950
  }
7792
7951
  if (!Array.isArray(output.content) || output.content.length > 64 || jsonBytes(output.content) > 1e6 || !output.content.every((block2) => {
7793
- const item = record5(block2);
7952
+ const item = record6(block2);
7794
7953
  return item && ["text", "image", "audio", "document", "tool_use", "tool_result", "thinking"].includes(String(item.type));
7795
7954
  })) throw invalid("collaboration tool");
7796
7955
  return {
@@ -7823,17 +7982,48 @@ function jsonBytes(value2) {
7823
7982
  return Number.POSITIVE_INFINITY;
7824
7983
  }
7825
7984
  }
7826
- var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7985
+ var record6 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7827
7986
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
7828
7987
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
7829
7988
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
7830
7989
  var PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
7831
7990
  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;
7832
7991
  function stripPatchEnvelope(patch2) {
7833
- if (!/^\*\*\* (?:Begin|End) Patch\s*$/m.test(patch2)) return patch2;
7992
+ if (!/^\*\*\* (?:Begin|End) Patch\s*$/m.test(patch2)) return applyPatchDialectToDiff(patch2);
7834
7993
  const kept = patch2.split("\n").filter((line2) => !/^\*\*\* (?:Begin|End) Patch\s*$/.test(line2));
7835
7994
  const stripped = kept.join("\n");
7836
- return /^diff --git /m.test(stripped) ? stripped : patch2;
7995
+ if (/^diff --git /m.test(stripped)) return stripped;
7996
+ const translated = applyPatchDialectToDiff(stripped);
7997
+ return translated === stripped ? patch2 : translated;
7998
+ }
7999
+ function applyPatchDialectToDiff(patch2) {
8000
+ if (!/^\*\*\* (?:Update|Add|Delete) File: /m.test(patch2)) return patch2;
8001
+ const out = [];
8002
+ let open = false;
8003
+ for (const line2 of patch2.split("\n")) {
8004
+ const file = /^\*\*\* (Update|Add|Delete) File: (.+?)\s*$/.exec(line2);
8005
+ if (file) {
8006
+ const [, verb, raw] = file;
8007
+ const path = raw.trim();
8008
+ if (!PATH.test(path)) return patch2;
8009
+ out.push(`diff --git a/${path} b/${path}`);
8010
+ if (verb === "Add") out.push("new file mode 100644", "--- /dev/null", `+++ b/${path}`);
8011
+ else if (verb === "Delete") out.push(`--- a/${path}`, "+++ /dev/null");
8012
+ else out.push(`--- a/${path}`, `+++ b/${path}`);
8013
+ open = true;
8014
+ continue;
8015
+ }
8016
+ if (/^\*\*\* /.test(line2)) continue;
8017
+ if (!open) continue;
8018
+ if (/^@@/.test(line2)) {
8019
+ out.push("@@ -1 +1 @@");
8020
+ continue;
8021
+ }
8022
+ out.push(line2);
8023
+ }
8024
+ if (!open) return patch2;
8025
+ return `${out.join("\n").replace(/\n+$/, "")}
8026
+ `;
7837
8027
  }
7838
8028
  function validateCodePatch(rawPatch, maxBytes) {
7839
8029
  const patch2 = stripPatchEnvelope(rawPatch);
@@ -7886,17 +8076,20 @@ function resolveCodePath(workspaceDir, path) {
7886
8076
  if (target !== root && !target.startsWith(`${root}${import_path6.sep}`)) throw new TypeError("path escapes the staged workspace");
7887
8077
  return target;
7888
8078
  }
8079
+ function hasContextFreeHunk(patch2) {
8080
+ const bodies = patch2.split(/^@@.*$/m).slice(1);
8081
+ return bodies.some((body) => !body.split("\n").some((line2) => line2.startsWith(" ") && line2.trim().length > 0));
8082
+ }
7889
8083
  function describePatchFailure(patch2, detail) {
7890
8084
  const hunks = patch2.split("\n").filter((line2) => line2.startsWith("@@"));
7891
- const bodies = patch2.split(/^@@.*$/m).slice(1);
7892
- const contextless = bodies.some((body) => !body.split("\n").some((line2) => line2.startsWith(" ") && line2.trim().length > 0));
7893
- const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
8085
+ const hint = hunks.length > 0 && hasContextFreeHunk(patch2) ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
7894
8086
  return `patch did not apply: ${detail}${hint}`;
7895
8087
  }
7896
8088
  async function applyCodePatch(workspaceDir, rawPatch, paths2) {
7897
8089
  const patch2 = stripPatchEnvelope(rawPatch);
7898
- await gitApply(workspaceDir, patch2, true);
7899
- await gitApply(workspaceDir, patch2, false);
8090
+ const zero = hasContextFreeHunk(patch2);
8091
+ await gitApply(workspaceDir, patch2, true, zero);
8092
+ await gitApply(workspaceDir, patch2, false, zero);
7900
8093
  for (const path of paths2) {
7901
8094
  try {
7902
8095
  const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
@@ -7908,9 +8101,16 @@ async function applyCodePatch(workspaceDir, rawPatch, paths2) {
7908
8101
  }
7909
8102
  }
7910
8103
  }
7911
- function gitApply(cwd, patch2, check) {
8104
+ function gitApply(cwd, patch2, check, unidiffZero = false) {
7912
8105
  return new Promise((accept, reject) => {
7913
- const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
8106
+ const args = [
8107
+ "apply",
8108
+ "--recount",
8109
+ ...unidiffZero ? ["--unidiff-zero"] : [],
8110
+ "--whitespace=nowarn",
8111
+ ...check ? ["--check"] : [],
8112
+ "-"
8113
+ ];
7914
8114
  const child = (0, import_child_process4.spawn)("git", args, {
7915
8115
  cwd,
7916
8116
  shell: false,
@@ -7919,8 +8119,8 @@ function gitApply(cwd, patch2, check) {
7919
8119
  });
7920
8120
  let stderr2 = "";
7921
8121
  child.stderr.setEncoding("utf8");
7922
- child.stderr.on("data", (text22) => {
7923
- if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
8122
+ child.stderr.on("data", (text4) => {
8123
+ if (stderr2.length < 4e3) stderr2 += text4.slice(0, 4e3);
7924
8124
  });
7925
8125
  child.once("error", reject);
7926
8126
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
@@ -8166,7 +8366,7 @@ function validate2(input) {
8166
8366
  maximumFiles: policy.maximumFiles ?? 2e4,
8167
8367
  maximumBytes: policy.maximumBytes ?? 512 * 1024 * 1024
8168
8368
  };
8169
- 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)) {
8369
+ if ([...result.testPathPrefixes, ...result.testPathSuffixes].some((rule) => !RULE.test(rule)) || !integer2(result.maximumChangedTests, 0, 1e4) || !integer2(result.maximumPatchBytes, 1, 4 * 1024 * 1024) || !integer2(result.maximumFiles, 1, 1e5) || !integer2(result.maximumBytes, 1, 2 * 1024 * 1024 * 1024)) {
8170
8370
  throw new TypeError("clean verification policy exceeds its bounds");
8171
8371
  }
8172
8372
  return result;
@@ -8217,7 +8417,7 @@ function hashFile(path) {
8217
8417
  });
8218
8418
  }
8219
8419
  function checkedResult(result, maximumOutputBytes) {
8220
- 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") {
8420
+ if (!integer2(result.exitCode, 0, 255) || !integer2(result.durationMs, 0, 30 * 6e4) || typeof result.stdout !== "string" || typeof result.stderr !== "string" || typeof result.outputLimitExceeded !== "boolean" || typeof result.timedOut !== "boolean") {
8221
8421
  throw new TypeError("recipe executor returned an invalid result");
8222
8422
  }
8223
8423
  const bytes = Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr);
@@ -8267,7 +8467,7 @@ function digestJson(value2) {
8267
8467
  function digestBytes(value2) {
8268
8468
  return `sha256:${(0, import_crypto3.createHash)("sha256").update(value2).digest("hex")}`;
8269
8469
  }
8270
- function integer(value2, minimum, maximum) {
8470
+ function integer2(value2, minimum, maximum) {
8271
8471
  return Number.isSafeInteger(value2) && value2 >= minimum && value2 <= maximum;
8272
8472
  }
8273
8473
  async function prepareRuntimeCheckpoint(input) {
@@ -8294,9 +8494,9 @@ async function prepareRuntimeCheckpoint(input) {
8294
8494
  if (evidence.receipt.outcome === "passed") {
8295
8495
  verification = evidence.receipt;
8296
8496
  review = await input.review(patch2, verification);
8297
- note = review.verdict === "approved" ? "Clean verification and independent review passed" : "Clean verification passed; independent review rejected the candidate";
8497
+ note = describeReview(review);
8298
8498
  } else {
8299
- note = `Clean verification failed: ${evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed").map((recipe2) => `${recipe2.recipeId}=${recipe2.status}`).join(", ")}`;
8499
+ note = describeGateFailure(evidence);
8300
8500
  }
8301
8501
  } catch (cause) {
8302
8502
  note = `Candidate verification or review failed closed: ${message(cause)}`;
@@ -8321,6 +8521,23 @@ async function prepareRuntimeCheckpoint(input) {
8321
8521
  });
8322
8522
  return { checkpoint, verification, review, note };
8323
8523
  }
8524
+ function describeReview(review) {
8525
+ const findings = review.findings.map((finding) => ` - [${finding.severity}] ${finding.detail}`).join("\n");
8526
+ const head = review.verdict === "approved" ? `Clean verification and independent review passed (score ${review.score}/100).` : `Clean verification passed; independent review REJECTED this candidate (score ${review.score}/100). Address the findings and checkpoint again.`;
8527
+ return [head, review.summary, findings].filter(Boolean).join("\n").slice(0, 4e3);
8528
+ }
8529
+ function describeGateFailure(evidence) {
8530
+ const failed = evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed");
8531
+ const detail = failed.map((recipe2) => {
8532
+ const log = evidence.logs.find((entry) => entry.recipeId === recipe2.recipeId);
8533
+ const output = `${log?.stdout ?? ""}
8534
+ ${log?.stderr ?? ""}`.trim();
8535
+ return `${recipe2.recipeId}=${recipe2.status}${output ? `
8536
+ ${output.slice(0, 1500)}` : ""}`;
8537
+ }).join("\n\n");
8538
+ return `Clean verification failed. Fix this and checkpoint again:
8539
+ ${detail}`.slice(0, 4e3);
8540
+ }
8324
8541
  var message = (value2) => (value2 instanceof Error ? value2.message : String(value2)).slice(0, 500);
8325
8542
  var CodeRuntimeCheckpointManager = class {
8326
8543
  constructor(options) {
@@ -9339,8 +9556,14 @@ function optionalInteger(value2) {
9339
9556
  if (!Number.isSafeInteger(value2) || value2 < 1) throw new TypeError("line bounds must be positive integers");
9340
9557
  return value2;
9341
9558
  }
9559
+ var DEFAULT_FAILURE_REASON2 = {
9560
+ "tool denied by CaMeL policy": "tool denied by CaMeL policy",
9561
+ "review sessions are read-only": "review session is read-only; workspace changes are not permitted"
9562
+ };
9342
9563
  function response(request3, ok, content2, details) {
9343
- return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
9564
+ if (ok) return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
9565
+ const failureReason = typeof details?.failureReason === "string" && details.failureReason || DEFAULT_FAILURE_REASON2[content2] || content2 || "tool request failed; inspect the tool input and workspace state";
9566
+ return { requestId: request3.requestId, ok, content: content2, details: { ...details, failureReason } };
9344
9567
  }
9345
9568
  var cache = /* @__PURE__ */ new Map();
9346
9569
  function workspaceGraphs(workspaceDir, paths2) {
@@ -9916,120 +10139,6 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
9916
10139
  }
9917
10140
  var digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
9918
10141
  var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
9919
- var text3 = (value2, maximum) => {
9920
- if (typeof value2 !== "string") return void 0;
9921
- const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
9922
- return bounded ? bounded.slice(0, maximum) : void 0;
9923
- };
9924
- var integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
9925
- var record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
9926
- var excerpt = (value2, tail = false) => {
9927
- if (typeof value2 !== "string") return void 0;
9928
- const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
9929
- const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
9930
- if (!source) return void 0;
9931
- const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
9932
- const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
9933
- return text3(selected.join("\n"), 2400);
9934
- };
9935
- var paths = (value2) => {
9936
- if (!Array.isArray(value2)) return void 0;
9937
- const items = value2.flatMap((item) => {
9938
- const path = text3(item, 1024);
9939
- return path ? [path] : [];
9940
- }).slice(0, 12);
9941
- return items.length ? items : void 0;
9942
- };
9943
- function patchStats(value2) {
9944
- if (typeof value2 !== "string") return {};
9945
- let additions = 0;
9946
- let deletions = 0;
9947
- for (const line2 of value2.slice(0, 262144).split("\n")) {
9948
- if (line2.startsWith("+++") || line2.startsWith("---")) continue;
9949
- if (line2.startsWith("+")) additions += 1;
9950
- else if (line2.startsWith("-")) deletions += 1;
9951
- }
9952
- return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
9953
- }
9954
- function searchResults(value2) {
9955
- if (typeof value2 !== "string") return void 0;
9956
- const results = value2.split("\n").flatMap((line2) => {
9957
- const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
9958
- if (!match) return [];
9959
- const lineNumber = Number(match[2]);
9960
- const itemText = text3(match[3], 240);
9961
- if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
9962
- return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
9963
- }).slice(0, 5);
9964
- return results.length ? results : void 0;
9965
- }
9966
- function codeToolRequestPresentation(request3) {
9967
- const input = request3.input;
9968
- if (request3.tool === "sandbox.read") {
9969
- const path = text3(input.path, 1024);
9970
- if (!path) return void 0;
9971
- const startLine = integer2(input.startLine);
9972
- const endLine = integer2(input.endLine);
9973
- return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
9974
- }
9975
- if (request3.tool === "sandbox.list") {
9976
- const scope = text3(input.prefix, 1024);
9977
- return { kind: "list", ...scope ? { scope } : {} };
9978
- }
9979
- if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
9980
- const query = text3(input.query, 512);
9981
- const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
9982
- if (request3.tool === "sandbox.search" && !query) return void 0;
9983
- return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
9984
- }
9985
- if (request3.tool === "sandbox.apply_patch") {
9986
- return { kind: "patch", ...patchStats(input.patch) };
9987
- }
9988
- const recipeId = text3(input.recipeId, 120);
9989
- return recipeId ? { kind: "recipe", recipeId } : void 0;
9990
- }
9991
- function codeToolResultPresentation(request3, response2) {
9992
- const started = codeToolRequestPresentation(request3);
9993
- if (!started || !response2.ok) return started;
9994
- const details = record32(response2.details);
9995
- if (started.kind === "read") {
9996
- return {
9997
- ...started,
9998
- ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
9999
- ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
10000
- ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
10001
- };
10002
- }
10003
- if (started.kind === "list") {
10004
- const listed = response2.content.split("\n").filter((line2) => line2 && !line2.startsWith("\u2026") && !line2.startsWith("Workspace ")).map((line2) => text3(line2, 1024)).filter((line2) => Boolean(line2)).slice(0, 8);
10005
- return {
10006
- ...started,
10007
- ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10008
- ...listed.length ? { paths: listed } : {}
10009
- };
10010
- }
10011
- if (started.kind === "query") {
10012
- const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
10013
- const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
10014
- return {
10015
- ...started,
10016
- ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10017
- ...results ? { results } : {},
10018
- ...resultExcerpt ? { excerpt: resultExcerpt } : {}
10019
- };
10020
- }
10021
- if (started.kind === "patch") {
10022
- return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
10023
- }
10024
- const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
10025
- return {
10026
- ...started,
10027
- ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
10028
- ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
10029
- ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
10030
- ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
10031
- };
10032
- }
10033
10142
  function codeRuntimeAcknowledgementGate(signal) {
10034
10143
  let settle;
10035
10144
  let settled = false;
@@ -10331,36 +10440,11 @@ var TheseusRuntimeEngine = class {
10331
10440
  }
10332
10441
  /** Report every brokered effect as it starts and finishes. */
10333
10442
  #observed(command, active, broker) {
10334
- return {
10335
- execute: async (context, request3) => {
10336
- const startedAt = Date.now();
10337
- const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
10338
- const startedPresentation = codeToolRequestPresentation(request3);
10339
- await this.#event(
10340
- command,
10341
- {
10342
- type: "tool",
10343
- phase: "started",
10344
- tool: request3.tool,
10345
- operationId,
10346
- ...startedPresentation ? { presentation: startedPresentation } : {}
10347
- },
10348
- active.conversationRefs
10349
- ).catch(() => void 0);
10350
- const response2 = await broker.execute(context, request3);
10351
- const completedPresentation = codeToolResultPresentation(request3, response2);
10352
- await this.#event(command, {
10353
- type: "tool",
10354
- phase: "completed",
10355
- tool: request3.tool,
10356
- ok: response2.ok,
10357
- durationMs: Date.now() - startedAt,
10358
- operationId,
10359
- ...completedPresentation ? { presentation: completedPresentation } : {}
10360
- }, active.conversationRefs).catch(() => void 0);
10361
- return response2;
10362
- }
10363
- };
10443
+ return observedBroker({
10444
+ broker,
10445
+ operationIdFor: (requestId) => digestRuntimeValue(`${command.commandId}:${requestId}`),
10446
+ emit: (event) => this.#event(command, event, active.conversationRefs).catch(() => void 0)
10447
+ });
10364
10448
  }
10365
10449
  async #checkpoint(command) {
10366
10450
  const active = this.#active.get(command.sessionId);
@@ -10670,6 +10754,19 @@ function digestText(value2) {
10670
10754
  // src/code-runtime-config.ts
10671
10755
  var CODE_NODE_IMAGE = "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd";
10672
10756
  var CODE_BUILD_RECIPES = Object.freeze([{
10757
+ id: "odla-code-gates",
10758
+ image: CODE_NODE_IMAGE,
10759
+ // Selects the repository's fast, no-capability gates from the same manifest
10760
+ // CI and `npm run preflight` read, so a new gate is picked up without a
10761
+ // second list to keep in sync. The container has no shell: this is argv.
10762
+ command: ["node", "scripts/recipe-gates.mjs"],
10763
+ timeoutMs: 18e4,
10764
+ maxOutputBytes: 1024 * 1024,
10765
+ cpus: 1,
10766
+ // check:secrets holds ~2,500 source files in memory at once.
10767
+ memory: "1g",
10768
+ pids: 128
10769
+ }, {
10673
10770
  id: "odla-code-contracts",
10674
10771
  image: CODE_NODE_IMAGE,
10675
10772
  command: [
@@ -10849,20 +10946,20 @@ async function runCodeRuntime(input) {
10849
10946
  }
10850
10947
  }
10851
10948
  function parseConnection(value2, appId, appEnv) {
10852
- const root = record6(value2);
10853
- const host = record6(root?.host);
10854
- const offer = record6(root?.offer);
10855
- const binding = record6(root?.binding);
10949
+ const root = record7(value2);
10950
+ const host = record7(root?.host);
10951
+ const offer = record7(root?.offer);
10952
+ const binding = record7(root?.binding);
10856
10953
  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)) {
10857
10954
  throw new Error("connect Code host returned an invalid response");
10858
10955
  }
10859
10956
  return root;
10860
10957
  }
10861
10958
  function apiFailure(action2, status, value2) {
10862
- const message2 = record6(record6(value2)?.error)?.message;
10959
+ const message2 = record7(record7(value2)?.error)?.message;
10863
10960
  return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
10864
10961
  }
10865
- function record6(value2) {
10962
+ function record7(value2) {
10866
10963
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
10867
10964
  }
10868
10965
 
@@ -11368,6 +11465,14 @@ Usage:
11368
11465
  odla-ai ai models [--config odla.config.mjs] [--env dev] [--provider <id>] [--json]
11369
11466
  odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
11370
11467
  odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
11468
+ odla-ai promotion plan --file <promotion-plan.json> [--env dev] [--json]
11469
+ odla-ai promotion inspect <plan-id> [--env dev] [--json]
11470
+ odla-ai promotion status <plan-id> [--env dev] [--json]
11471
+ odla-ai promotion apply <plan-id> --approval <approval-id> [--idempotency-key <key>] [--env dev] [--json]
11472
+ odla-ai promotion rollback <plan-id> --approval <approval-id> [--idempotency-key <key>] [--env dev] [--json]
11473
+ odla-ai promotion next <plan-id> <operation-id> [--env dev] [--json]
11474
+ odla-ai promotion record <plan-id> <operation-id> <step-id> --file <step-receipt.json> [--env dev] [--json]
11475
+ odla-ai promotion cancel <plan-id> <operation-id> [--env dev] [--json]
11371
11476
  odla-ai operations get <operation-id> [--json]
11372
11477
  odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
11373
11478
  odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
@@ -11534,6 +11639,11 @@ Commands:
11534
11639
  init Create a generic odla.config.mjs plus starter schema/rules files.
11535
11640
  doctor Validate and summarize the project config without network calls.
11536
11641
  config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
11642
+ promotion Plan and inspect exact selective-release bundles without target
11643
+ mutation, then queue and resume a human-approved apply or rollback
11644
+ from an enrolled device. Each target step is leased and receipt-
11645
+ journaled; approval remains bound to the immutable plan digest,
11646
+ target set, action, and expiry, so every mismatch fails closed.
11537
11647
  operations Inspect or wait on one exact, durable config-operation receipt.
11538
11648
  calendar Inspect, connect, or disconnect the live Google booking connection.
11539
11649
  app Archive (suspend, data retained), restore, export, import, or
@@ -12409,17 +12519,17 @@ function collectEntityFields(entity, parsed, allowClear) {
12409
12519
  if (entity === "task" && fields.column === "ready") fields.column = "todo";
12410
12520
  return fields;
12411
12521
  }
12412
- function statusCol(entity, record11) {
12413
- if (entity === "bug") return `${record11.status ?? ""}/${record11.severity ?? ""}`;
12522
+ function statusCol(entity, record12) {
12523
+ if (entity === "bug") return `${record12.status ?? ""}/${record12.severity ?? ""}`;
12414
12524
  if (entity === "task") {
12415
- const state2 = record11.column === "todo" ? "ready" : String(record11.column ?? "");
12416
- return record11.revision ? `${state2}; r${record11.revision}` : state2;
12525
+ const state2 = record12.column === "todo" ? "ready" : String(record12.column ?? "");
12526
+ return record12.revision ? `${state2}; r${record12.revision}` : state2;
12417
12527
  }
12418
- return String(record11.status ?? "");
12528
+ return String(record12.status ?? "");
12419
12529
  }
12420
- function referenceMarkup(entity, record11) {
12421
- const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
12422
- return `@[${label}](pm:${entity}/${record11.id})`;
12530
+ function referenceMarkup(entity, record12) {
12531
+ const label = (record12.title?.trim() || `${entity} ${record12.id}`).replaceAll("]", ")");
12532
+ return `@[${label}](pm:${entity}/${record12.id})`;
12423
12533
  }
12424
12534
  var STUDIO_SECTION = {
12425
12535
  goal: "goals",
@@ -12433,13 +12543,13 @@ function studioRecordUrl(ctx, entity, id2) {
12433
12543
  ctx.platformUrl
12434
12544
  ).href;
12435
12545
  }
12436
- function studioRecordLink(ctx, entity, record11) {
12437
- const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
12438
- return `[${label}](${studioRecordUrl(ctx, entity, record11.id)})`;
12546
+ function studioRecordLink(ctx, entity, record12) {
12547
+ const label = (record12.title?.trim() || `${entity} ${record12.id}`).replaceAll("]", ")");
12548
+ return `[${label}](${studioRecordUrl(ctx, entity, record12.id)})`;
12439
12549
  }
12440
- function printRecord(ctx, entity, record11) {
12550
+ function printRecord(ctx, entity, record12) {
12441
12551
  ctx.out.log(
12442
- `${record11.id} [${statusCol(entity, record11)}] ${record11.appId} ${studioRecordLink(ctx, entity, record11)}`
12552
+ `${record12.id} [${statusCol(entity, record12)}] ${record12.appId} ${studioRecordLink(ctx, entity, record12)}`
12443
12553
  );
12444
12554
  }
12445
12555
  function emit2(ctx, value2, human) {
@@ -12493,8 +12603,8 @@ async function pmAdd(ctx, entity, parsed) {
12493
12603
  input,
12494
12604
  mutationId: writeMutationId2(parsed)
12495
12605
  });
12496
- const record11 = { id: res.id, appId, title: String(input.title) };
12497
- emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record11)}`));
12606
+ const record12 = { id: res.id, appId, title: String(input.title) };
12607
+ emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record12)}`));
12498
12608
  }
12499
12609
  var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12500
12610
  var UUID_PREFIX = /^[0-9a-f-]{8,35}$/i;
@@ -12514,7 +12624,7 @@ async function resolvePmId(ctx, entity, supplied) {
12514
12624
  "GET",
12515
12625
  `/${entity}?limit=${limit}&offset=${offset}`
12516
12626
  );
12517
- matches.push(...page2.records.map((record11) => record11.id).filter((id2) => id2.toLowerCase().startsWith(prefix)));
12627
+ matches.push(...page2.records.map((record12) => record12.id).filter((id2) => id2.toLowerCase().startsWith(prefix)));
12518
12628
  offset += page2.records.length;
12519
12629
  if (matches.length > 1 || page2.records.length === 0 || offset >= page2.total) break;
12520
12630
  }
@@ -12528,17 +12638,17 @@ async function resolvePmId(ctx, entity, supplied) {
12528
12638
  }
12529
12639
  async function pmGet(ctx, entity, id2) {
12530
12640
  const resolved = await resolvePmId(ctx, entity, id2);
12531
- const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(resolved)}`);
12532
- emit2(ctx, record11, () => printRecord(ctx, entity, record11));
12641
+ const { record: record12 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(resolved)}`);
12642
+ emit2(ctx, record12, () => printRecord(ctx, entity, record12));
12533
12643
  }
12534
12644
  async function pmReference(ctx, entity, id2) {
12535
- const { record: record11 } = await pmRequest(
12645
+ const { record: record12 } = await pmRequest(
12536
12646
  ctx,
12537
12647
  "GET",
12538
12648
  `/${entity}/${encodeURIComponent(id2)}`
12539
12649
  );
12540
- const markup = referenceMarkup(entity, record11);
12541
- emit2(ctx, { kind: `pm:${entity}`, id: record11.id, label: record11.title ?? "", markup }, () => {
12650
+ const markup = referenceMarkup(entity, record12);
12651
+ emit2(ctx, { kind: `pm:${entity}`, id: record12.id, label: record12.title ?? "", markup }, () => {
12542
12652
  ctx.out.log(markup);
12543
12653
  });
12544
12654
  }
@@ -12623,16 +12733,16 @@ var PER_ENTITY = {
12623
12733
  decision: ["status", "body", "supersedesId"],
12624
12734
  bug: ["status", "severity", "description", "goalId", "assigneeId", "decisionId"]
12625
12735
  };
12626
- function leanRecord(entity, record11) {
12736
+ function leanRecord(entity, record12) {
12627
12737
  const out = {};
12628
12738
  for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
12629
- const value2 = record11[key];
12739
+ const value2 = record12[key];
12630
12740
  if (value2 !== void 0 && value2 !== null) out[key] = value2;
12631
12741
  }
12632
12742
  return out;
12633
12743
  }
12634
12744
  function leanRecords(entity, records, verbose) {
12635
- return verbose ? records : records.map((record11) => leanRecord(entity, record11));
12745
+ return verbose ? records : records.map((record12) => leanRecord(entity, record12));
12636
12746
  }
12637
12747
 
12638
12748
  // src/pm-intake.ts
@@ -12665,8 +12775,8 @@ async function pmNext(ctx, parsed) {
12665
12775
  // One request for both live columns; the split below is over open work only.
12666
12776
  listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
12667
12777
  ]);
12668
- const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
12669
- const doing = tasks.filter((record11) => record11.column === "doing");
12778
+ const ready = tasks.filter((record12) => record12.column === READY_COLUMN);
12779
+ const doing = tasks.filter((record12) => record12.column === "doing");
12670
12780
  const guidance = !goals.length ? "discuss alignment with the user before creating or claiming project work" : !ready.length ? 'refine a linked Backlog task and mark it Ready with "pm task ready <id>"' : `claim the top Ready task in one call: odla-ai pm start --app ${appId}`;
12671
12781
  const monitor = `odla-ai pm watch --app ${appId} --jsonl`;
12672
12782
  const result = {
@@ -12687,7 +12797,7 @@ async function pmNext(ctx, parsed) {
12687
12797
  ]) {
12688
12798
  ctx.out.log(`${label}:`);
12689
12799
  if (!records.length) ctx.out.log("- (none)");
12690
- else for (const record11 of records) printRecord(ctx, entity, record11);
12800
+ else for (const record12 of records) printRecord(ctx, entity, record12);
12691
12801
  }
12692
12802
  ctx.out.log(`next: ${guidance}`);
12693
12803
  ctx.out.log(`monitor: ${monitor}`);
@@ -12726,7 +12836,7 @@ async function pmHandoff(ctx, parsed) {
12726
12836
  ]) {
12727
12837
  ctx.out.log(`${label}:`);
12728
12838
  if (!records.length) ctx.out.log("- (none)");
12729
- else for (const record11 of records) printRecord(ctx, entity, record11);
12839
+ else for (const record12 of records) printRecord(ctx, entity, record12);
12730
12840
  }
12731
12841
  ctx.out.log(`monitor: ${monitor}`);
12732
12842
  });
@@ -12801,14 +12911,14 @@ async function pmStart(ctx, parsed) {
12801
12911
 
12802
12912
  // src/pm-links.ts
12803
12913
  async function pmLink(ctx, entity, id2) {
12804
- const { record: record11 } = await pmRequest(
12914
+ const { record: record12 } = await pmRequest(
12805
12915
  ctx,
12806
12916
  "GET",
12807
12917
  `/${entity}/${encodeURIComponent(id2)}`
12808
12918
  );
12809
- const url = studioRecordUrl(ctx, entity, record11.id);
12810
- const markdown = studioRecordLink(ctx, entity, record11);
12811
- emit2(ctx, { kind: entity, id: record11.id, label: record11.title ?? "", url, markdown }, () => {
12919
+ const url = studioRecordUrl(ctx, entity, record12.id);
12920
+ const markdown = studioRecordLink(ctx, entity, record12);
12921
+ emit2(ctx, { kind: entity, id: record12.id, label: record12.title ?? "", url, markdown }, () => {
12812
12922
  ctx.out.log(markdown);
12813
12923
  });
12814
12924
  }
@@ -12935,16 +13045,16 @@ async function page(ctx, appId, cursor) {
12935
13045
  }
12936
13046
  return data;
12937
13047
  }
12938
- function recordState(record11) {
12939
- if (record11.column) return record11.column === "todo" ? "ready" : record11.column;
12940
- return String(record11.status ?? "");
13048
+ function recordState(record12) {
13049
+ if (record12.column) return record12.column === "todo" ? "ready" : record12.column;
13050
+ return String(record12.status ?? "");
12941
13051
  }
12942
13052
  function eventRecord(event) {
12943
13053
  return event.payload.payload;
12944
13054
  }
12945
13055
  function eventLabel(event) {
12946
- const record11 = eventRecord(event);
12947
- if (record11) return String(record11.title ?? event.payload.entityId);
13056
+ const record12 = eventRecord(event);
13057
+ if (record12) return String(record12.title ?? event.payload.entityId);
12948
13058
  const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
12949
13059
  return body || event.payload.entityId;
12950
13060
  }
@@ -12952,10 +13062,10 @@ function report3(ctx, parsed, result) {
12952
13062
  if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
12953
13063
  else if (parsed.options.jsonl !== true && result.found) {
12954
13064
  for (const event of result.events ?? []) {
12955
- const record11 = eventRecord(event);
12956
- const state2 = record11 ? recordState(record11) : "comment";
13065
+ const record12 = eventRecord(event);
13066
+ const state2 = record12 ? recordState(record12) : "comment";
12957
13067
  ctx.out.log(
12958
- `${event.id} ${event.type} ${state2}${record11?.revision ? `; r${record11.revision}` : ""} ${eventLabel(event)}`
13068
+ `${event.id} ${event.type} ${state2}${record12?.revision ? `; r${record12.revision}` : ""} ${eventLabel(event)}`
12959
13069
  );
12960
13070
  }
12961
13071
  }
@@ -13029,8 +13139,8 @@ async function pmWatch(ctx, parsed) {
13029
13139
  }
13030
13140
  firstSuccess = false;
13031
13141
  const matching = current.events.filter((event) => {
13032
- const record11 = eventRecord(event);
13033
- const state2 = record11 ? recordState(record11).toLowerCase() : "";
13142
+ const record12 = eventRecord(event);
13143
+ const state2 = record12 ? recordState(record12).toLowerCase() : "";
13034
13144
  return (!entity || event.payload.entityKind === entity) && (!action2 || event.payload.action === action2) && (!wantedState || state2 === wantedState || wantedState === "todo" && state2 === "ready") && (!by || event.actor.id === by) && (!self || event.actor.id !== self);
13035
13145
  });
13036
13146
  for (const event of matching) {
@@ -13461,17 +13571,17 @@ async function platformStatus(parsed, deps) {
13461
13571
  }
13462
13572
  }
13463
13573
  function isPlatformStatus(value2) {
13464
- if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
13465
- if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
13466
- if (!record7(value2.catalog) || !record7(value2.summary)) return false;
13574
+ if (!record8(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
13575
+ if (!record8(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
13576
+ if (!record8(value2.catalog) || !record8(value2.summary)) return false;
13467
13577
  return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
13468
13578
  }
13469
13579
  function apiMessage(value2) {
13470
- if (!record7(value2)) return "request failed";
13471
- const error = record7(value2.error) ? value2.error : value2;
13580
+ if (!record8(value2)) return "request failed";
13581
+ const error = record8(value2.error) ? value2.error : value2;
13472
13582
  return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
13473
13583
  }
13474
- function record7(value2) {
13584
+ function record8(value2) {
13475
13585
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
13476
13586
  }
13477
13587
 
@@ -13512,7 +13622,7 @@ function statusVerdict(reads) {
13512
13622
  severity: "degraded"
13513
13623
  });
13514
13624
  }
13515
- const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
13625
+ const performance = record9(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
13516
13626
  if (performance?.status === "unavailable") {
13517
13627
  reasons.push({
13518
13628
  source: "liveSync",
@@ -13593,7 +13703,7 @@ function statusVerdict(reads) {
13593
13703
  reasons
13594
13704
  };
13595
13705
  }
13596
- function record8(value2) {
13706
+ function record9(value2) {
13597
13707
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
13598
13708
  }
13599
13709
  function numeric2(value2) {
@@ -13621,7 +13731,7 @@ function printO11yStatus(status, out) {
13621
13731
  out.log(
13622
13732
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
13623
13733
  );
13624
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
13734
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record10) : [];
13625
13735
  const requests = routes.reduce(
13626
13736
  (total, row) => total + numeric3(row.requests),
13627
13737
  0
@@ -13633,39 +13743,39 @@ function printO11yStatus(status, out) {
13633
13743
  out.log(
13634
13744
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
13635
13745
  );
13636
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
13746
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record10) : [];
13637
13747
  out.log(
13638
13748
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
13639
13749
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
13640
13750
  ).join(", ") : "none observed"}`
13641
13751
  );
13642
13752
  out.log(liveSyncLine(status.liveSync));
13643
- const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
13753
+ const canaryDurations = record10(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
13644
13754
  out.log(
13645
13755
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
13646
13756
  );
13647
- const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
13648
- const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
13757
+ const collectorIngest = record10(status.collector.body.ingest) ? status.collector.body.ingest : {};
13758
+ const collectorStorage = record10(collectorIngest.storage) ? collectorIngest.storage : {};
13649
13759
  out.log(
13650
13760
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
13651
13761
  );
13652
- const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
13653
- const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
13654
- const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
13762
+ const providerMetrics = record10(status.provider.body.metrics) ? status.provider.body.metrics : {};
13763
+ const providerCapacity = record10(status.provider.body.capacity) ? status.provider.body.capacity : {};
13764
+ const workerMemory = record10(providerCapacity.memory) ? providerCapacity.memory : {};
13655
13765
  out.log(
13656
13766
  `cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors ${optionalBytes(workerMemory.headroomBytes)} isolate memory headroom`
13657
13767
  );
13658
13768
  for (const line2 of providerCapacityLines(status.providerCapacity)) {
13659
13769
  out.log(line2);
13660
13770
  }
13661
- const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
13662
- const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
13663
- const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
13771
+ const coverage = record10(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
13772
+ const coverageCounts = record10(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
13773
+ const coverageBudget = record10(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
13664
13774
  out.log(
13665
13775
  `request-coverage ${status.providerReconciliation.httpStatus} ${String(status.providerReconciliation.body.status ?? status.providerReconciliation.body.error ?? "unavailable")} ${optionalPercent(coverage.applicationCoverage)} application/provider ${numeric3(coverageCounts.applicationRequests)}/${numeric3(coverageCounts.providerRequests)} requests \xB1${optionalPercent(coverageBudget.maxRelativeError)} budget`
13666
13776
  );
13667
13777
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
13668
- const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
13778
+ const providerFreshness = record10(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
13669
13779
  out.log(
13670
13780
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
13671
13781
  );
@@ -13674,17 +13784,17 @@ function printO11yStatus(status, out) {
13674
13784
  );
13675
13785
  }
13676
13786
  function providerCapacityLines(read3) {
13677
- const resources = record9(read3.body.resources) ? read3.body.resources : {};
13678
- const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
13679
- const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
13680
- const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
13681
- const d1 = record9(resources.d1) ? resources.d1 : {};
13682
- const d1Activity = record9(d1.activity) ? d1.activity : {};
13683
- const d1Storage = record9(d1.storage) ? d1.storage : {};
13684
- const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
13685
- const r2 = record9(resources.r2) ? resources.r2 : {};
13686
- const r2Operations = record9(r2.operations) ? r2.operations : {};
13687
- const r2Storage = record9(r2.storage) ? r2.storage : {};
13787
+ const resources = record10(read3.body.resources) ? read3.body.resources : {};
13788
+ const durableObjects = record10(resources.durableObjects) ? resources.durableObjects : {};
13789
+ const periodic = record10(durableObjects.periodic) ? durableObjects.periodic : {};
13790
+ const storage = record10(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
13791
+ const d1 = record10(resources.d1) ? resources.d1 : {};
13792
+ const d1Activity = record10(d1.activity) ? d1.activity : {};
13793
+ const d1Storage = record10(d1.storage) ? d1.storage : {};
13794
+ const d1Latency = record10(d1Activity.latency) ? d1Activity.latency : {};
13795
+ const r2 = record10(resources.r2) ? resources.r2 : {};
13796
+ const r2Operations = record10(r2.operations) ? r2.operations : {};
13797
+ const r2Storage = record10(r2.storage) ? r2.storage : {};
13688
13798
  const status = String(
13689
13799
  read3.body.status ?? read3.body.error ?? "unavailable"
13690
13800
  );
@@ -13695,11 +13805,11 @@ function providerCapacityLines(read3) {
13695
13805
  ];
13696
13806
  }
13697
13807
  function liveSyncLine(read3) {
13698
- const performance = record9(read3.body.performance) ? read3.body.performance : {};
13699
- const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
13808
+ const performance = record10(read3.body.performance) ? read3.body.performance : {};
13809
+ const commitToSend = record10(performance.commitToSend) ? performance.commitToSend : {};
13700
13810
  return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
13701
13811
  }
13702
- function record9(value2) {
13812
+ function record10(value2) {
13703
13813
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
13704
13814
  }
13705
13815
  function numeric3(value2) {
@@ -14037,7 +14147,7 @@ async function monitorCommand(parsed, deps = {}) {
14037
14147
  if (action2 === "plan" || action2 === "apply") {
14038
14148
  const desired = monitoringWireConfig(context.cfg, env);
14039
14149
  const live = await request2(`${base}?env=${encodeURIComponent(env)}`, { headers }, doFetch);
14040
- const currentRevision = record10(live.config) ? string(live.config.revision) : null;
14150
+ const currentRevision = record11(live.config) ? string(live.config.revision) : null;
14041
14151
  const changed = currentRevision !== desired.revision;
14042
14152
  const plan = {
14043
14153
  schemaVersion: 1,
@@ -14082,7 +14192,7 @@ async function monitorCommand(parsed, deps = {}) {
14082
14192
  headers
14083
14193
  }, doFetch);
14084
14194
  emit3(result2, jsonOutput, out, () => {
14085
- const run = record10(result2.run) ? result2.run : {};
14195
+ const run = record11(result2.run) ? result2.run : {};
14086
14196
  out.log(`monitor run ${appId}/${env}/${probeId}: ${string(run.outcome) ?? "unknown"}${run.failure_code ? ` (${String(run.failure_code)})` : ""}`);
14087
14197
  });
14088
14198
  return;
@@ -14107,12 +14217,12 @@ async function request2(url, init, doFetch) {
14107
14217
  let body = {};
14108
14218
  try {
14109
14219
  const parsed = text4 ? JSON.parse(text4) : {};
14110
- body = record10(parsed) ? parsed : { value: parsed };
14220
+ body = record11(parsed) ? parsed : { value: parsed };
14111
14221
  } catch {
14112
14222
  body = { message: text4.slice(0, 500) };
14113
14223
  }
14114
14224
  if (!response2.ok) {
14115
- const error = record10(body.error) ? body.error : body;
14225
+ const error = record11(body.error) ? body.error : body;
14116
14226
  throw new Error(string(error.message) ?? string(error.code) ?? `monitor request failed (${response2.status})`);
14117
14227
  }
14118
14228
  return body;
@@ -14120,7 +14230,7 @@ async function request2(url, init, doFetch) {
14120
14230
  function printRead(action2, appId, env, result, out) {
14121
14231
  if (action2 === "status") {
14122
14232
  out.log(`monitor status ${appId}/${env}: ${String(result.overall ?? (result.configured === false ? "not configured" : "unknown"))}`);
14123
- const slos = Array.isArray(result.slos) ? result.slos.filter(record10) : [];
14233
+ const slos = Array.isArray(result.slos) ? result.slos.filter(record11) : [];
14124
14234
  for (const slo of slos) out.log(`slo ${String(slo.id)} ${String(slo.state)} ${percent(slo.observed)} observed ${percent(slo.budgetRemaining)} budget remaining`);
14125
14235
  const incidents = Array.isArray(result.openIncidents) ? result.openIncidents.length : 0;
14126
14236
  const gaps = Array.isArray(result.monitoringGaps) ? result.monitoringGaps.length : 0;
@@ -14129,20 +14239,20 @@ function printRead(action2, appId, env, result, out) {
14129
14239
  return;
14130
14240
  }
14131
14241
  if (action2 === "incidents") {
14132
- const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record10) : [];
14242
+ const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record11) : [];
14133
14243
  out.log(`monitor incidents ${appId}/${env}: ${incidents.length}`);
14134
14244
  for (const incident2 of incidents) out.log(`${String(incident2.state)} ${String(incident2.kind)} ${String(incident2.slo_id)} ${new Date(Number(incident2.opened_at)).toISOString()}`);
14135
14245
  return;
14136
14246
  }
14137
14247
  out.log(`monitor report ${appId}/${env}: ${String(result.period)} ${String(result.overall)}`);
14138
- const probes = Array.isArray(result.probes) ? result.probes.filter(record10) : [];
14248
+ const probes = Array.isArray(result.probes) ? result.probes.filter(record11) : [];
14139
14249
  for (const probe of probes) out.log(`probe ${String(probe.id)} ${Number(probe.good)} good ${Number(probe.bad)} bad ${Number(probe.unknown)} unknown`);
14140
14250
  }
14141
14251
  function emit3(value2, json, out, human) {
14142
14252
  if (json) out.log(JSON.stringify(value2, null, 2));
14143
14253
  else human();
14144
14254
  }
14145
- function record10(value2) {
14255
+ function record11(value2) {
14146
14256
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
14147
14257
  }
14148
14258
  function string(value2) {
@@ -14633,6 +14743,122 @@ async function provision(options) {
14633
14743
  }
14634
14744
  }
14635
14745
 
14746
+ // src/promotion-command.ts
14747
+ var import_promises12 = require("fs/promises");
14748
+ var import_apps14 = require("@odla-ai/apps");
14749
+
14750
+ // src/security-command-context.ts
14751
+ async function hostedSecurityContext(parsed, dependencies) {
14752
+ const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
14753
+ const cfg = await loadProjectConfig(configPath);
14754
+ const env = stringOpt(parsed.options.env) ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
14755
+ if (!env || !cfg.envs.includes(env)) {
14756
+ throw new Error(`env "${env ?? ""}" is not declared in ${configPath}`);
14757
+ }
14758
+ const platform = platformAudience(stringOpt(parsed.options.platform) ?? cfg.platformUrl);
14759
+ if (platformAudience(cfg.platformUrl) !== platform) {
14760
+ throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
14761
+ }
14762
+ const doFetch = dependencies.fetch ?? fetch;
14763
+ const stdout = dependencies.stdout ?? console;
14764
+ const open = parsed.options.open === false ? false : parsed.options.open === true ? true : void 0;
14765
+ const token = await getDeveloperToken(
14766
+ cfg,
14767
+ { configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
14768
+ doFetch,
14769
+ stdout,
14770
+ { optionalProjectCapabilities: ["app.manage"] }
14771
+ );
14772
+ return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
14773
+ }
14774
+ function requiredSecurityPositional(parsed, index, label) {
14775
+ const value2 = parsed.positionals[index];
14776
+ if (!value2) throw new Error(`${label} is required`);
14777
+ return value2;
14778
+ }
14779
+ function securityProfile(value2) {
14780
+ if (value2 === void 0) return void 0;
14781
+ if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
14782
+ throw new Error("--profile must be odla, cloudflare-app, or generic");
14783
+ }
14784
+
14785
+ // src/promotion-command.ts
14786
+ var render = (value2, json, out) => {
14787
+ if (json) out.log(JSON.stringify(value2));
14788
+ else out.log(JSON.stringify(value2, null, 2));
14789
+ };
14790
+ async function promotionCommand(parsed, dependencies) {
14791
+ const action2 = parsed.positionals[1];
14792
+ if (!action2 || !["plan", "inspect", "status", "apply", "rollback", "next", "record", "cancel"].includes(action2)) rejectWord(["promotion"], action2);
14793
+ assertArgs(parsed, ["config", "env", "platform", "email", "open", "json", "file", "approval", "idempotency-key"], 5);
14794
+ const context = await hostedSecurityContext(parsed, dependencies);
14795
+ const client = (0, import_apps14.createAppsClient)({
14796
+ token: context.token,
14797
+ fetcher: { fetch: (request3, init) => {
14798
+ const internal = new URL(typeof request3 === "string" ? request3 : request3.url);
14799
+ return context.fetch(`${context.platform}${internal.pathname}${internal.search}`, init);
14800
+ } }
14801
+ });
14802
+ const json = parsed.options.json === true;
14803
+ if (action2 === "plan") {
14804
+ if (parsed.positionals[2]) throw new Error("promotion plan takes --file, not a positional plan id");
14805
+ const file = stringOpt(parsed.options.file);
14806
+ if (!file) throw new Error("--file <promotion-plan.json> is required");
14807
+ const source = JSON.parse(await (0, import_promises12.readFile)(file, "utf8"));
14808
+ const plan2 = await client.createPromotionReleasePlan(context.appId, source);
14809
+ render(plan2, json, context.stdout);
14810
+ return;
14811
+ }
14812
+ const planId = requiredSecurityPositional(parsed, 2, "plan id");
14813
+ if (action2 === "inspect") {
14814
+ if (parsed.positionals[3]) throw new Error("promotion inspect takes only a plan id");
14815
+ const plan2 = await client.getPromotionReleasePlan(context.appId, planId);
14816
+ if (!plan2) throw new Error(`promotion release ${planId} was not found`);
14817
+ render(plan2, json, context.stdout);
14818
+ return;
14819
+ }
14820
+ if (action2 === "status") {
14821
+ if (parsed.positionals[3]) throw new Error("promotion status takes only a plan id");
14822
+ const status = await client.getPromotionReleaseStatus(context.appId, planId);
14823
+ if (!status) throw new Error(`promotion release ${planId} was not found`);
14824
+ render(status, json, context.stdout);
14825
+ return;
14826
+ }
14827
+ if (action2 === "next" || action2 === "cancel" || action2 === "record") {
14828
+ const operationId = requiredSecurityPositional(parsed, 3, "operation id");
14829
+ if (action2 === "next") {
14830
+ if (parsed.positionals[4]) throw new Error("promotion next takes only plan and operation ids");
14831
+ render(await client.claimPromotionReleaseWork(context.appId, planId, operationId), json, context.stdout);
14832
+ return;
14833
+ }
14834
+ if (action2 === "cancel") {
14835
+ if (parsed.positionals[4]) throw new Error("promotion cancel takes only plan and operation ids");
14836
+ render(await client.cancelPromotionReleaseOperation(context.appId, planId, operationId), json, context.stdout);
14837
+ return;
14838
+ }
14839
+ const stepId = requiredSecurityPositional(parsed, 4, "step id");
14840
+ const file = stringOpt(parsed.options.file);
14841
+ if (!file) throw new Error("--file <step-receipt.json> is required");
14842
+ const receipt = JSON.parse(await (0, import_promises12.readFile)(file, "utf8"));
14843
+ render(await client.recordPromotionReleaseWork(context.appId, planId, operationId, stepId, receipt), json, context.stdout);
14844
+ return;
14845
+ }
14846
+ const approvalId = stringOpt(parsed.options.approval);
14847
+ if (parsed.positionals[3]) throw new Error(`promotion ${action2} takes only a plan id`);
14848
+ if (!approvalId) throw new Error(`--approval <approval-id> is required for promotion ${action2}`);
14849
+ const plan = await client.getPromotionReleasePlan(context.appId, planId);
14850
+ if (!plan) throw new Error(`promotion release ${planId} was not found`);
14851
+ if (plan.state !== "reviewable") throw new Error(`promotion release is ${plan.state}: ${plan.blockers.join("; ")}`);
14852
+ const operationAction = action2;
14853
+ const operation = await client.requestPromotionReleaseOperation(context.appId, planId, operationAction, {
14854
+ approvalId,
14855
+ planDigest: plan.planDigest,
14856
+ targetSetDigest: plan.targetSetDigest,
14857
+ idempotencyKey: stringOpt(parsed.options["idempotency-key"]) ?? `${operationAction}:${planId}`
14858
+ });
14859
+ render(operation, json, context.stdout);
14860
+ }
14861
+
14636
14862
  // src/record.ts
14637
14863
  var import_node_fs18 = require("fs");
14638
14864
  var import_node_process18 = __toESM(require("process"), 1);
@@ -14652,12 +14878,12 @@ function recordInvocation(parsed) {
14652
14878
  }
14653
14879
 
14654
14880
  // src/advisory-output.ts
14655
- var import_apps14 = require("@odla-ai/apps");
14881
+ var import_apps15 = require("@odla-ai/apps");
14656
14882
  function advisoryCollectingFetch(inner, sink) {
14657
14883
  return (async (input, init) => {
14658
14884
  const response2 = await inner(input, init);
14659
14885
  try {
14660
- sink.push(...(0, import_apps14.parseAdvisories)(response2));
14886
+ sink.push(...(0, import_apps15.parseAdvisories)(response2));
14661
14887
  } catch {
14662
14888
  }
14663
14889
  return response2;
@@ -14677,7 +14903,7 @@ function renderAdvisories(out, advisories, env = process.env) {
14677
14903
  const key = `${advisory.code}:${advisory.message}`;
14678
14904
  if (seen.has(key)) continue;
14679
14905
  seen.add(key);
14680
- out.error((0, import_apps14.formatAdvisory)(advisory));
14906
+ out.error((0, import_apps15.formatAdvisory)(advisory));
14681
14907
  }
14682
14908
  }
14683
14909
 
@@ -14964,7 +15190,7 @@ async function bySlug(ctx, slug) {
14964
15190
  "GET",
14965
15191
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
14966
15192
  );
14967
- const filtered = page2.records.find((record11) => record11.slug === slug);
15193
+ const filtered = page2.records.find((record12) => record12.slug === slug);
14968
15194
  if (filtered) return filtered;
14969
15195
  const limit = 100;
14970
15196
  for (let offset = 0; ; offset += limit) {
@@ -14973,7 +15199,7 @@ async function bySlug(ctx, slug) {
14973
15199
  "GET",
14974
15200
  `/runbook?app=${encodeURIComponent(ctx.appId)}&limit=${limit}&offset=${offset}`
14975
15201
  );
14976
- const found = fallback.records.find((record11) => record11.slug === slug);
15202
+ const found = fallback.records.find((record12) => record12.slug === slug);
14977
15203
  if (found) return found;
14978
15204
  if (!fallback.records.length || offset + fallback.records.length >= fallback.total) break;
14979
15205
  }
@@ -15773,41 +15999,6 @@ async function runbookCommand(parsed, deps = {}) {
15773
15999
  }
15774
16000
  }
15775
16001
 
15776
- // src/security-command-context.ts
15777
- async function hostedSecurityContext(parsed, dependencies) {
15778
- const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
15779
- const cfg = await loadProjectConfig(configPath);
15780
- const env = stringOpt(parsed.options.env) ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
15781
- if (!env || !cfg.envs.includes(env)) {
15782
- throw new Error(`env "${env ?? ""}" is not declared in ${configPath}`);
15783
- }
15784
- const platform = platformAudience(stringOpt(parsed.options.platform) ?? cfg.platformUrl);
15785
- if (platformAudience(cfg.platformUrl) !== platform) {
15786
- throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
15787
- }
15788
- const doFetch = dependencies.fetch ?? fetch;
15789
- const stdout = dependencies.stdout ?? console;
15790
- const open = parsed.options.open === false ? false : parsed.options.open === true ? true : void 0;
15791
- const token = await getDeveloperToken(
15792
- cfg,
15793
- { configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
15794
- doFetch,
15795
- stdout,
15796
- { optionalProjectCapabilities: ["app.manage"] }
15797
- );
15798
- return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
15799
- }
15800
- function requiredSecurityPositional(parsed, index, label) {
15801
- const value2 = parsed.positionals[index];
15802
- if (!value2) throw new Error(`${label} is required`);
15803
- return value2;
15804
- }
15805
- function securityProfile(value2) {
15806
- if (value2 === void 0) return void 0;
15807
- if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
15808
- throw new Error("--profile must be odla, cloudflare-app, or generic");
15809
- }
15810
-
15811
16002
  // src/security-command-output.ts
15812
16003
  function printHostedSecurityPlan(out, plan, appId) {
15813
16004
  out.log(`Hosted security plan for ${appId}/${plan.env}: ${plan.ready ? "ready" : "not ready"}`);
@@ -16630,6 +16821,10 @@ async function dispatchCli(argv, dependencies) {
16630
16821
  await securityCommand(parsed, runtime);
16631
16822
  return;
16632
16823
  }
16824
+ if (command === "promotion") {
16825
+ await promotionCommand(parsed, runtime);
16826
+ return;
16827
+ }
16633
16828
  if (command === "brand") {
16634
16829
  await brandCommand(parsed, runtime);
16635
16830
  return;