@odla-ai/cli 0.46.11 → 0.48.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/bin.cjs CHANGED
@@ -1783,6 +1783,7 @@ var init_surface = __esm({
1783
1783
  watch: {}
1784
1784
  },
1785
1785
  provision: {},
1786
+ promotion: { plan: {}, inspect: {}, status: {}, apply: {}, rollback: {}, next: {}, record: {}, cancel: {} },
1786
1787
  runbook: {
1787
1788
  ask: {},
1788
1789
  search: {},
@@ -4270,9 +4271,9 @@ function canonicalValue(value2) {
4270
4271
  }
4271
4272
  if (Array.isArray(value2)) return value2.map(canonicalValue);
4272
4273
  if (value2 && typeof value2 === "object") {
4273
- const record11 = value2;
4274
+ const record12 = value2;
4274
4275
  return Object.fromEntries(
4275
- Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
4276
+ Object.keys(record12).filter((key) => record12[key] !== void 0).sort().map((key) => [key, canonicalValue(record12[key])])
4276
4277
  );
4277
4278
  }
4278
4279
  throw new TypeError("canonical JSON rejects unsupported values");
@@ -6878,17 +6879,17 @@ var init_cli_project = __esm({
6878
6879
  }
6879
6880
  });
6880
6881
 
6881
- // ../harness/dist/chunk-RXNHCGWE.js
6882
+ // ../harness/dist/chunk-U324RQ4N.js
6882
6883
  var HARNESS_PROTOCOL_VERSION;
6883
- var init_chunk_RXNHCGWE = __esm({
6884
- "../harness/dist/chunk-RXNHCGWE.js"() {
6884
+ var init_chunk_U324RQ4N = __esm({
6885
+ "../harness/dist/chunk-U324RQ4N.js"() {
6885
6886
  "use strict";
6886
6887
  init_cjs_shims();
6887
6888
  HARNESS_PROTOCOL_VERSION = 1;
6888
6889
  }
6889
6890
  });
6890
6891
 
6891
- // ../harness/dist/chunk-CR6RE3A2.js
6892
+ // ../harness/dist/chunk-5LRYJKUI.js
6892
6893
  function assertPinnedImage(image) {
6893
6894
  if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
6894
6895
  }
@@ -7032,8 +7033,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
7032
7033
  const maxFiles = options.maxFiles ?? 2e4;
7033
7034
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
7034
7035
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
7035
- const entries = inventory.flatMap((record11) => {
7036
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
7036
+ const entries = inventory.flatMap((record12) => {
7037
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record12);
7037
7038
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
7038
7039
  });
7039
7040
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -7237,8 +7238,8 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
7237
7238
  }
7238
7239
  }
7239
7240
  var import_child_process, import_fs, import_promises2, import_path, import_process, import_promises3, import_os, import_path2, import_child_process2, import_path3, import_promises4, import_os2, import_path4, import_child_process3, DIGEST_IMAGE, SKIP_WORKSPACE_DIRS, SECRET_WORKSPACE_FILE;
7240
- var init_chunk_CR6RE3A2 = __esm({
7241
- "../harness/dist/chunk-CR6RE3A2.js"() {
7241
+ var init_chunk_5LRYJKUI = __esm({
7242
+ "../harness/dist/chunk-5LRYJKUI.js"() {
7242
7243
  "use strict";
7243
7244
  init_cjs_shims();
7244
7245
  import_child_process = require("child_process");
@@ -7268,6 +7269,171 @@ var init_chunk_CR6RE3A2 = __esm({
7268
7269
  }
7269
7270
  });
7270
7271
 
7272
+ // ../harness/dist/chunk-FAN2R3GW.js
7273
+ function patchStats(value2) {
7274
+ if (typeof value2 !== "string") return {};
7275
+ let additions = 0;
7276
+ let deletions = 0;
7277
+ for (const line2 of value2.slice(0, 262144).split("\n")) {
7278
+ if (line2.startsWith("+++") || line2.startsWith("---")) continue;
7279
+ if (line2.startsWith("+")) additions += 1;
7280
+ else if (line2.startsWith("-")) deletions += 1;
7281
+ }
7282
+ return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
7283
+ }
7284
+ function searchResults(value2) {
7285
+ if (typeof value2 !== "string") return void 0;
7286
+ const results = value2.split("\n").flatMap((line2) => {
7287
+ const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
7288
+ if (!match) return [];
7289
+ const lineNumber = Number(match[2]);
7290
+ const itemText = text3(match[3], 240);
7291
+ if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
7292
+ return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
7293
+ }).slice(0, 5);
7294
+ return results.length ? results : void 0;
7295
+ }
7296
+ function codeToolRequestPresentation(request3) {
7297
+ const input = request3.input;
7298
+ if (request3.tool === "sandbox.read") {
7299
+ const path = text3(input.path, 1024);
7300
+ if (!path) return void 0;
7301
+ const startLine = integer(input.startLine);
7302
+ const endLine = integer(input.endLine);
7303
+ return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
7304
+ }
7305
+ if (request3.tool === "sandbox.list") {
7306
+ const scope = text3(input.prefix, 1024);
7307
+ return { kind: "list", ...scope ? { scope } : {} };
7308
+ }
7309
+ if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
7310
+ const query = text3(input.query, 512);
7311
+ const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
7312
+ if (request3.tool === "sandbox.search" && !query) return void 0;
7313
+ return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
7314
+ }
7315
+ if (request3.tool === "sandbox.apply_patch") {
7316
+ return { kind: "patch", ...patchStats(input.patch) };
7317
+ }
7318
+ const recipeId = text3(input.recipeId, 120);
7319
+ return recipeId ? { kind: "recipe", recipeId } : void 0;
7320
+ }
7321
+ function codeToolResultPresentation(request3, response2) {
7322
+ const started = codeToolRequestPresentation(request3);
7323
+ if (!started || !response2.ok) return started;
7324
+ const details = record5(response2.details);
7325
+ if (started.kind === "read") {
7326
+ return {
7327
+ ...started,
7328
+ ...integer(details?.startLine) ? { startLine: integer(details?.startLine) } : {},
7329
+ ...integer(details?.endLine) ? { endLine: integer(details?.endLine) } : {},
7330
+ ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
7331
+ };
7332
+ }
7333
+ if (started.kind === "list") {
7334
+ 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);
7335
+ return {
7336
+ ...started,
7337
+ ...integer(details?.count) !== void 0 ? { count: integer(details?.count) } : {},
7338
+ ...listed.length ? { paths: listed } : {}
7339
+ };
7340
+ }
7341
+ if (started.kind === "query") {
7342
+ const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
7343
+ const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
7344
+ return {
7345
+ ...started,
7346
+ ...integer(details?.count) !== void 0 ? { count: integer(details?.count) } : {},
7347
+ ...results ? { results } : {},
7348
+ ...resultExcerpt ? { excerpt: resultExcerpt } : {}
7349
+ };
7350
+ }
7351
+ if (started.kind === "patch") {
7352
+ return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
7353
+ }
7354
+ const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
7355
+ return {
7356
+ ...started,
7357
+ ...integer(details?.exitCode) !== void 0 ? { exitCode: integer(details?.exitCode) } : {},
7358
+ ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
7359
+ ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
7360
+ ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
7361
+ };
7362
+ }
7363
+ function toolFailureReason(response2) {
7364
+ if (response2.ok) return void 0;
7365
+ const supplied = response2.details?.failureReason;
7366
+ const reason = typeof supplied === "string" && supplied || DEFAULT_FAILURE_REASON[response2.content] || response2.content || "tool request failed; inspect the tool input and workspace state";
7367
+ return reason.slice(0, MAX_FAILURE_REASON);
7368
+ }
7369
+ function observedBroker(input) {
7370
+ const now = input.now ?? Date.now;
7371
+ return {
7372
+ execute: async (context, request3) => {
7373
+ const startedAt = now();
7374
+ const operationId = input.operationIdFor(request3.requestId);
7375
+ const startedPresentation = codeToolRequestPresentation(request3);
7376
+ await input.emit({
7377
+ type: "tool",
7378
+ phase: "started",
7379
+ tool: request3.tool,
7380
+ operationId,
7381
+ ...startedPresentation ? { presentation: startedPresentation } : {}
7382
+ });
7383
+ const response2 = await input.broker.execute(context, request3);
7384
+ const completedPresentation = codeToolResultPresentation(request3, response2);
7385
+ const failureReason = toolFailureReason(response2);
7386
+ await input.emit({
7387
+ type: "tool",
7388
+ phase: "completed",
7389
+ tool: request3.tool,
7390
+ ok: response2.ok,
7391
+ durationMs: now() - startedAt,
7392
+ operationId,
7393
+ ...failureReason ? { failureReason } : {},
7394
+ ...completedPresentation ? { presentation: completedPresentation } : {}
7395
+ });
7396
+ return response2;
7397
+ }
7398
+ };
7399
+ }
7400
+ var text3, integer, record5, excerpt, paths, MAX_FAILURE_REASON, DEFAULT_FAILURE_REASON;
7401
+ var init_chunk_FAN2R3GW = __esm({
7402
+ "../harness/dist/chunk-FAN2R3GW.js"() {
7403
+ "use strict";
7404
+ init_cjs_shims();
7405
+ text3 = (value2, maximum) => {
7406
+ if (typeof value2 !== "string") return void 0;
7407
+ const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
7408
+ return bounded ? bounded.slice(0, maximum) : void 0;
7409
+ };
7410
+ integer = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
7411
+ record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
7412
+ excerpt = (value2, tail = false) => {
7413
+ if (typeof value2 !== "string") return void 0;
7414
+ const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
7415
+ const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
7416
+ if (!source) return void 0;
7417
+ const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
7418
+ const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
7419
+ return text3(selected.join("\n"), 2400);
7420
+ };
7421
+ paths = (value2) => {
7422
+ if (!Array.isArray(value2)) return void 0;
7423
+ const items = value2.flatMap((item) => {
7424
+ const path = text3(item, 1024);
7425
+ return path ? [path] : [];
7426
+ }).slice(0, 12);
7427
+ return items.length ? items : void 0;
7428
+ };
7429
+ MAX_FAILURE_REASON = 240;
7430
+ DEFAULT_FAILURE_REASON = {
7431
+ "tool denied by CaMeL policy": "tool denied by CaMeL policy",
7432
+ "review sessions are read-only": "review session is read-only; workspace changes are not permitted"
7433
+ };
7434
+ }
7435
+ });
7436
+
7271
7437
  // ../camel/dist/chunk-7FHPOQVP.js
7272
7438
  var CamelError;
7273
7439
  var init_chunk_7FHPOQVP = __esm({
@@ -7314,8 +7480,8 @@ function normalize(value2) {
7314
7480
  if (Array.isArray(value2)) return value2.map(normalize);
7315
7481
  if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
7316
7482
  if (typeof value2 === "object") {
7317
- const record11 = value2;
7318
- return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
7483
+ const record12 = value2;
7484
+ return Object.fromEntries(Object.keys(record12).filter((key) => record12[key] !== void 0).sort().map((key) => [key, normalize(record12[key])]));
7319
7485
  }
7320
7486
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
7321
7487
  }
@@ -8236,7 +8402,7 @@ var init_code2 = __esm({
8236
8402
  }
8237
8403
  });
8238
8404
 
8239
- // ../harness/dist/chunk-J7XU5QB7.js
8405
+ // ../harness/dist/chunk-Q4NL5XO3.js
8240
8406
  async function digestStagedWorkspace(root, limits) {
8241
8407
  const files = [];
8242
8408
  const walk = async (directory) => {
@@ -8294,7 +8460,7 @@ function createCodeRuntimeControlClient(options) {
8294
8460
  }
8295
8461
  const value2 = await response2.json().catch(() => null);
8296
8462
  if (!response2.ok) {
8297
- const problem = record5(record5(value2)?.error);
8463
+ const problem = record6(record6(value2)?.error);
8298
8464
  throw new CodeRuntimeControlError(
8299
8465
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
8300
8466
  response2.status,
@@ -8315,12 +8481,12 @@ function createCodeRuntimeControlClient(options) {
8315
8481
  await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
8316
8482
  ),
8317
8483
  infer: async (sessionId, inference) => {
8318
- const value2 = record5(await call4(
8484
+ const value2 = record6(await call4(
8319
8485
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
8320
8486
  inference,
8321
8487
  modelRequestTimeoutMs
8322
8488
  ));
8323
- if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
8489
+ if (!value2 || value2.requestId !== inference.requestId || !record6(value2.response) || !record6(value2.receipt)) {
8324
8490
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
8325
8491
  }
8326
8492
  return value2;
@@ -8451,12 +8617,12 @@ function validateHeartbeat(version, capabilities) {
8451
8617
  }
8452
8618
  }
8453
8619
  function parseSnapshot(value2) {
8454
- const root = record5(value2);
8455
- const host = record5(root?.host);
8620
+ const root = record6(value2);
8621
+ const host = record6(root?.host);
8456
8622
  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");
8457
8623
  const bindingIds = /* @__PURE__ */ new Set();
8458
8624
  const bindings = root.bindings.map((item) => {
8459
- const binding = record5(item);
8625
+ const binding = record6(item);
8460
8626
  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)) {
8461
8627
  throw invalid("binding");
8462
8628
  }
@@ -8466,10 +8632,10 @@ function parseSnapshot(value2) {
8466
8632
  const commandIds = /* @__PURE__ */ new Set();
8467
8633
  const commandSequences = /* @__PURE__ */ new Set();
8468
8634
  const commands = root.commands.map((item) => {
8469
- const command = record5(item);
8635
+ const command = record6(item);
8470
8636
  const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
8471
8637
  const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
8472
- 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");
8638
+ 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");
8473
8639
  commandIds.add(command.commandId);
8474
8640
  commandSequences.add(sequenceKey);
8475
8641
  return command;
@@ -8478,10 +8644,10 @@ function parseSnapshot(value2) {
8478
8644
  }
8479
8645
  async function parseSource(value2) {
8480
8646
  const repositoryLimits = { maximumFiles: 1e5, maximumBytes: 80 * 1024 * 1024 };
8481
- const snapshot = record5(record5(value2)?.snapshot);
8647
+ const snapshot = record6(record6(value2)?.snapshot);
8482
8648
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
8483
8649
  const files = snapshot.files.map((value22) => {
8484
- const file = record5(value22);
8650
+ const file = record6(value22);
8485
8651
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
8486
8652
  return { path: file.path, content: file.content };
8487
8653
  });
@@ -8490,11 +8656,11 @@ async function parseSource(value2) {
8490
8656
  const aliases = /* @__PURE__ */ new Set();
8491
8657
  const references = [];
8492
8658
  for (const item of referencesValue) {
8493
- const reference = record5(item);
8659
+ const reference = record6(item);
8494
8660
  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");
8495
8661
  aliases.add(reference.alias);
8496
8662
  const referenceFiles = reference.files.map((entry) => {
8497
- const file = record5(entry);
8663
+ const file = record6(entry);
8498
8664
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
8499
8665
  return { path: file.path, content: file.content };
8500
8666
  });
@@ -8509,31 +8675,31 @@ async function parseSource(value2) {
8509
8675
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
8510
8676
  }
8511
8677
  function parseReview(value2) {
8512
- const review = record5(record5(value2)?.review);
8513
- 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");
8678
+ const review = record6(record6(value2)?.review);
8679
+ 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");
8514
8680
  return review;
8515
8681
  }
8516
8682
  function parseCandidate(value2) {
8517
- const candidate = record5(record5(value2)?.candidate);
8683
+ const candidate = record6(record6(value2)?.candidate);
8518
8684
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
8519
8685
  throw invalid("candidate");
8520
8686
  }
8521
8687
  return { candidateId: candidate.candidateId, status: candidate.status };
8522
8688
  }
8523
8689
  function parseCollaborationSkills(value2) {
8524
- const items = record5(value2)?.skills;
8690
+ const items = record6(value2)?.skills;
8525
8691
  if (!Array.isArray(items) || items.length > 16) throw invalid("collaboration skills");
8526
8692
  const skillNames2 = /* @__PURE__ */ new Set();
8527
8693
  const toolNames = /* @__PURE__ */ new Set();
8528
8694
  return items.map((item) => {
8529
- const skill = record5(item);
8695
+ const skill = record6(item);
8530
8696
  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) {
8531
8697
  throw invalid("collaboration skill");
8532
8698
  }
8533
8699
  skillNames2.add(skill.name);
8534
8700
  const tools = skill.tools.map((candidate) => {
8535
- const tool = record5(candidate);
8536
- const inputSchema = record5(tool?.inputSchema);
8701
+ const tool = record6(candidate);
8702
+ const inputSchema = record6(tool?.inputSchema);
8537
8703
  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") {
8538
8704
  throw invalid("collaboration tool");
8539
8705
  }
@@ -8558,12 +8724,12 @@ function parseCollaborationSkills(value2) {
8558
8724
  }
8559
8725
  function validateCollaborationToolRequest(value2) {
8560
8726
  validCommandId(value2.commandId);
8561
- 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) {
8727
+ 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) {
8562
8728
  throw new TypeError("invalid Code collaboration tool request");
8563
8729
  }
8564
8730
  }
8565
8731
  function parseCollaborationToolOutput(value2) {
8566
- const output = record5(record5(value2)?.output);
8732
+ const output = record6(record6(value2)?.output);
8567
8733
  if (!output || output.isError !== void 0 && typeof output.isError !== "boolean") {
8568
8734
  throw invalid("collaboration tool");
8569
8735
  }
@@ -8572,7 +8738,7 @@ function parseCollaborationToolOutput(value2) {
8572
8738
  return { content: output.content, ...output.isError === true ? { isError: true } : {} };
8573
8739
  }
8574
8740
  if (!Array.isArray(output.content) || output.content.length > 64 || jsonBytes(output.content) > 1e6 || !output.content.every((block2) => {
8575
- const item = record5(block2);
8741
+ const item = record6(block2);
8576
8742
  return item && ["text", "image", "audio", "document", "tool_use", "tool_result", "thinking"].includes(String(item.type));
8577
8743
  })) throw invalid("collaboration tool");
8578
8744
  return {
@@ -8606,10 +8772,41 @@ function jsonBytes(value2) {
8606
8772
  }
8607
8773
  }
8608
8774
  function stripPatchEnvelope(patch2) {
8609
- if (!/^\*\*\* (?:Begin|End) Patch\s*$/m.test(patch2)) return patch2;
8775
+ if (!/^\*\*\* (?:Begin|End) Patch\s*$/m.test(patch2)) return applyPatchDialectToDiff(patch2);
8610
8776
  const kept = patch2.split("\n").filter((line2) => !/^\*\*\* (?:Begin|End) Patch\s*$/.test(line2));
8611
8777
  const stripped = kept.join("\n");
8612
- return /^diff --git /m.test(stripped) ? stripped : patch2;
8778
+ if (/^diff --git /m.test(stripped)) return stripped;
8779
+ const translated = applyPatchDialectToDiff(stripped);
8780
+ return translated === stripped ? patch2 : translated;
8781
+ }
8782
+ function applyPatchDialectToDiff(patch2) {
8783
+ if (!/^\*\*\* (?:Update|Add|Delete) File: /m.test(patch2)) return patch2;
8784
+ const out = [];
8785
+ let open = false;
8786
+ for (const line2 of patch2.split("\n")) {
8787
+ const file = /^\*\*\* (Update|Add|Delete) File: (.+?)\s*$/.exec(line2);
8788
+ if (file) {
8789
+ const [, verb, raw] = file;
8790
+ const path = raw.trim();
8791
+ if (!PATH.test(path)) return patch2;
8792
+ out.push(`diff --git a/${path} b/${path}`);
8793
+ if (verb === "Add") out.push("new file mode 100644", "--- /dev/null", `+++ b/${path}`);
8794
+ else if (verb === "Delete") out.push(`--- a/${path}`, "+++ /dev/null");
8795
+ else out.push(`--- a/${path}`, `+++ b/${path}`);
8796
+ open = true;
8797
+ continue;
8798
+ }
8799
+ if (/^\*\*\* /.test(line2)) continue;
8800
+ if (!open) continue;
8801
+ if (/^@@/.test(line2)) {
8802
+ out.push("@@ -1 +1 @@");
8803
+ continue;
8804
+ }
8805
+ out.push(line2);
8806
+ }
8807
+ if (!open) return patch2;
8808
+ return `${out.join("\n").replace(/\n+$/, "")}
8809
+ `;
8613
8810
  }
8614
8811
  function validateCodePatch(rawPatch, maxBytes) {
8615
8812
  const patch2 = stripPatchEnvelope(rawPatch);
@@ -8662,17 +8859,20 @@ function resolveCodePath(workspaceDir, path) {
8662
8859
  if (target !== root && !target.startsWith(`${root}${import_path6.sep}`)) throw new TypeError("path escapes the staged workspace");
8663
8860
  return target;
8664
8861
  }
8862
+ function hasContextFreeHunk(patch2) {
8863
+ const bodies = patch2.split(/^@@.*$/m).slice(1);
8864
+ return bodies.some((body) => !body.split("\n").some((line2) => line2.startsWith(" ") && line2.trim().length > 0));
8865
+ }
8665
8866
  function describePatchFailure(patch2, detail) {
8666
8867
  const hunks = patch2.split("\n").filter((line2) => line2.startsWith("@@"));
8667
- const bodies = patch2.split(/^@@.*$/m).slice(1);
8668
- const contextless = bodies.some((body) => !body.split("\n").some((line2) => line2.startsWith(" ") && line2.trim().length > 0));
8669
- const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
8868
+ const hint = hunks.length > 0 && hasContextFreeHunk(patch2) ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
8670
8869
  return `patch did not apply: ${detail}${hint}`;
8671
8870
  }
8672
8871
  async function applyCodePatch(workspaceDir, rawPatch, paths2) {
8673
8872
  const patch2 = stripPatchEnvelope(rawPatch);
8674
- await gitApply(workspaceDir, patch2, true);
8675
- await gitApply(workspaceDir, patch2, false);
8873
+ const zero = hasContextFreeHunk(patch2);
8874
+ await gitApply(workspaceDir, patch2, true, zero);
8875
+ await gitApply(workspaceDir, patch2, false, zero);
8676
8876
  for (const path of paths2) {
8677
8877
  try {
8678
8878
  const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
@@ -8684,9 +8884,16 @@ async function applyCodePatch(workspaceDir, rawPatch, paths2) {
8684
8884
  }
8685
8885
  }
8686
8886
  }
8687
- function gitApply(cwd, patch2, check) {
8887
+ function gitApply(cwd, patch2, check, unidiffZero = false) {
8688
8888
  return new Promise((accept, reject) => {
8689
- const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
8889
+ const args = [
8890
+ "apply",
8891
+ "--recount",
8892
+ ...unidiffZero ? ["--unidiff-zero"] : [],
8893
+ "--whitespace=nowarn",
8894
+ ...check ? ["--check"] : [],
8895
+ "-"
8896
+ ];
8690
8897
  const child = (0, import_child_process4.spawn)("git", args, {
8691
8898
  cwd,
8692
8899
  shell: false,
@@ -8695,8 +8902,8 @@ function gitApply(cwd, patch2, check) {
8695
8902
  });
8696
8903
  let stderr2 = "";
8697
8904
  child.stderr.setEncoding("utf8");
8698
- child.stderr.on("data", (text22) => {
8699
- if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
8905
+ child.stderr.on("data", (text4) => {
8906
+ if (stderr2.length < 4e3) stderr2 += text4.slice(0, 4e3);
8700
8907
  });
8701
8908
  child.once("error", reject);
8702
8909
  child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
@@ -8934,7 +9141,7 @@ function validate2(input) {
8934
9141
  maximumFiles: policy.maximumFiles ?? 2e4,
8935
9142
  maximumBytes: policy.maximumBytes ?? 512 * 1024 * 1024
8936
9143
  };
8937
- 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)) {
9144
+ 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)) {
8938
9145
  throw new TypeError("clean verification policy exceeds its bounds");
8939
9146
  }
8940
9147
  return result;
@@ -8985,7 +9192,7 @@ function hashFile(path) {
8985
9192
  });
8986
9193
  }
8987
9194
  function checkedResult(result, maximumOutputBytes) {
8988
- 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") {
9195
+ 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") {
8989
9196
  throw new TypeError("recipe executor returned an invalid result");
8990
9197
  }
8991
9198
  const bytes = Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr);
@@ -9035,7 +9242,7 @@ function digestJson(value2) {
9035
9242
  function digestBytes(value2) {
9036
9243
  return `sha256:${(0, import_crypto3.createHash)("sha256").update(value2).digest("hex")}`;
9037
9244
  }
9038
- function integer(value2, minimum, maximum) {
9245
+ function integer2(value2, minimum, maximum) {
9039
9246
  return Number.isSafeInteger(value2) && value2 >= minimum && value2 <= maximum;
9040
9247
  }
9041
9248
  async function prepareRuntimeCheckpoint(input) {
@@ -9062,9 +9269,9 @@ async function prepareRuntimeCheckpoint(input) {
9062
9269
  if (evidence.receipt.outcome === "passed") {
9063
9270
  verification = evidence.receipt;
9064
9271
  review = await input.review(patch2, verification);
9065
- note = review.verdict === "approved" ? "Clean verification and independent review passed" : "Clean verification passed; independent review rejected the candidate";
9272
+ note = describeReview(review);
9066
9273
  } else {
9067
- note = `Clean verification failed: ${evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed").map((recipe2) => `${recipe2.recipeId}=${recipe2.status}`).join(", ")}`;
9274
+ note = describeGateFailure(evidence);
9068
9275
  }
9069
9276
  } catch (cause) {
9070
9277
  note = `Candidate verification or review failed closed: ${message(cause)}`;
@@ -9089,6 +9296,23 @@ async function prepareRuntimeCheckpoint(input) {
9089
9296
  });
9090
9297
  return { checkpoint, verification, review, note };
9091
9298
  }
9299
+ function describeReview(review) {
9300
+ const findings = review.findings.map((finding) => ` - [${finding.severity}] ${finding.detail}`).join("\n");
9301
+ 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.`;
9302
+ return [head, review.summary, findings].filter(Boolean).join("\n").slice(0, 4e3);
9303
+ }
9304
+ function describeGateFailure(evidence) {
9305
+ const failed = evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed");
9306
+ const detail = failed.map((recipe2) => {
9307
+ const log = evidence.logs.find((entry) => entry.recipeId === recipe2.recipeId);
9308
+ const output = `${log?.stdout ?? ""}
9309
+ ${log?.stderr ?? ""}`.trim();
9310
+ return `${recipe2.recipeId}=${recipe2.status}${output ? `
9311
+ ${output.slice(0, 1500)}` : ""}`;
9312
+ }).join("\n\n");
9313
+ return `Clean verification failed. Fix this and checkpoint again:
9314
+ ${detail}`.slice(0, 4e3);
9315
+ }
9092
9316
  function codeCommandMetadata(payload, resume) {
9093
9317
  const trusted = record22(payload.trustedBase);
9094
9318
  const role = payload.role;
@@ -9969,7 +10193,9 @@ function optionalInteger(value2) {
9969
10193
  return value2;
9970
10194
  }
9971
10195
  function response(request3, ok, content2, details) {
9972
- return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
10196
+ if (ok) return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
10197
+ const failureReason = typeof details?.failureReason === "string" && details.failureReason || DEFAULT_FAILURE_REASON2[content2] || content2 || "tool request failed; inspect the tool input and workspace state";
10198
+ return { requestId: request3.requestId, ok, content: content2, details: { ...details, failureReason } };
9973
10199
  }
9974
10200
  function workspaceGraphs(workspaceDir, paths2) {
9975
10201
  const existing = cache.get(workspaceDir);
@@ -10533,96 +10759,6 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
10533
10759
  const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
10534
10760
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
10535
10761
  }
10536
- function patchStats(value2) {
10537
- if (typeof value2 !== "string") return {};
10538
- let additions = 0;
10539
- let deletions = 0;
10540
- for (const line2 of value2.slice(0, 262144).split("\n")) {
10541
- if (line2.startsWith("+++") || line2.startsWith("---")) continue;
10542
- if (line2.startsWith("+")) additions += 1;
10543
- else if (line2.startsWith("-")) deletions += 1;
10544
- }
10545
- return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
10546
- }
10547
- function searchResults(value2) {
10548
- if (typeof value2 !== "string") return void 0;
10549
- const results = value2.split("\n").flatMap((line2) => {
10550
- const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
10551
- if (!match) return [];
10552
- const lineNumber = Number(match[2]);
10553
- const itemText = text3(match[3], 240);
10554
- if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
10555
- return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
10556
- }).slice(0, 5);
10557
- return results.length ? results : void 0;
10558
- }
10559
- function codeToolRequestPresentation(request3) {
10560
- const input = request3.input;
10561
- if (request3.tool === "sandbox.read") {
10562
- const path = text3(input.path, 1024);
10563
- if (!path) return void 0;
10564
- const startLine = integer2(input.startLine);
10565
- const endLine = integer2(input.endLine);
10566
- return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
10567
- }
10568
- if (request3.tool === "sandbox.list") {
10569
- const scope = text3(input.prefix, 1024);
10570
- return { kind: "list", ...scope ? { scope } : {} };
10571
- }
10572
- if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
10573
- const query = text3(input.query, 512);
10574
- const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
10575
- if (request3.tool === "sandbox.search" && !query) return void 0;
10576
- return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
10577
- }
10578
- if (request3.tool === "sandbox.apply_patch") {
10579
- return { kind: "patch", ...patchStats(input.patch) };
10580
- }
10581
- const recipeId = text3(input.recipeId, 120);
10582
- return recipeId ? { kind: "recipe", recipeId } : void 0;
10583
- }
10584
- function codeToolResultPresentation(request3, response2) {
10585
- const started = codeToolRequestPresentation(request3);
10586
- if (!started || !response2.ok) return started;
10587
- const details = record32(response2.details);
10588
- if (started.kind === "read") {
10589
- return {
10590
- ...started,
10591
- ...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
10592
- ...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
10593
- ...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
10594
- };
10595
- }
10596
- if (started.kind === "list") {
10597
- 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);
10598
- return {
10599
- ...started,
10600
- ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10601
- ...listed.length ? { paths: listed } : {}
10602
- };
10603
- }
10604
- if (started.kind === "query") {
10605
- const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
10606
- const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
10607
- return {
10608
- ...started,
10609
- ...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
10610
- ...results ? { results } : {},
10611
- ...resultExcerpt ? { excerpt: resultExcerpt } : {}
10612
- };
10613
- }
10614
- if (started.kind === "patch") {
10615
- return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
10616
- }
10617
- const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
10618
- return {
10619
- ...started,
10620
- ...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
10621
- ...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
10622
- ...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
10623
- ...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
10624
- };
10625
- }
10626
10762
  function codeRuntimeAcknowledgementGate(signal) {
10627
10763
  let settle;
10628
10764
  let settled = false;
@@ -10640,13 +10776,63 @@ function codeRuntimeAcknowledgementGate(signal) {
10640
10776
  else signal.addEventListener("abort", onAbort, { once: true });
10641
10777
  return { ready, release };
10642
10778
  }
10643
- var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_ai5, import_child_process6, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, MAX_NATIVE_ARG_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, text3, integer2, record32, excerpt, paths, TheseusRuntimeEngine;
10644
- var init_chunk_J7XU5QB7 = __esm({
10645
- "../harness/dist/chunk-J7XU5QB7.js"() {
10779
+ function observeCodeRuntimeSessionSkills(command, skills, emit4) {
10780
+ return skills.map((skill) => ({
10781
+ ...skill,
10782
+ tools: skill.tools.map((tool) => {
10783
+ if (!tool.handler) return tool;
10784
+ const handler = tool.handler;
10785
+ return {
10786
+ ...tool,
10787
+ handler: async (input, context) => {
10788
+ const startedAt = Date.now();
10789
+ const operationId = digestRuntimeValue(
10790
+ `${command.commandId}:${skill.name}:${tool.name}:${context.toolCallId ?? "missing"}`
10791
+ );
10792
+ await emit4({
10793
+ type: "collaboration",
10794
+ phase: "started",
10795
+ skill: skill.name,
10796
+ tool: tool.name,
10797
+ operationId
10798
+ }).catch(() => void 0);
10799
+ try {
10800
+ const output = await handler(input, context);
10801
+ await emit4({
10802
+ type: "collaboration",
10803
+ phase: "completed",
10804
+ skill: skill.name,
10805
+ tool: tool.name,
10806
+ operationId,
10807
+ ok: output.isError !== true,
10808
+ durationMs: Date.now() - startedAt
10809
+ }).catch(() => void 0);
10810
+ return output;
10811
+ } catch (cause) {
10812
+ await emit4({
10813
+ type: "collaboration",
10814
+ phase: "completed",
10815
+ skill: skill.name,
10816
+ tool: tool.name,
10817
+ operationId,
10818
+ ok: false,
10819
+ durationMs: Date.now() - startedAt
10820
+ }).catch(() => void 0);
10821
+ throw cause;
10822
+ }
10823
+ }
10824
+ };
10825
+ })
10826
+ }));
10827
+ }
10828
+ var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_ai5, import_child_process6, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record6, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, MAX_NATIVE_ARG_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, DEFAULT_FAILURE_REASON2, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, TheseusRuntimeEngine;
10829
+ var init_chunk_Q4NL5XO3 = __esm({
10830
+ "../harness/dist/chunk-Q4NL5XO3.js"() {
10646
10831
  "use strict";
10647
10832
  init_cjs_shims();
10648
- init_chunk_CR6RE3A2();
10649
- init_chunk_RXNHCGWE();
10833
+ init_chunk_FAN2R3GW();
10834
+ init_chunk_5LRYJKUI();
10835
+ init_chunk_U324RQ4N();
10650
10836
  import_crypto = require("crypto");
10651
10837
  import_promises5 = require("fs/promises");
10652
10838
  import_path5 = require("path");
@@ -10728,7 +10914,7 @@ var init_chunk_J7XU5QB7 = __esm({
10728
10914
  code;
10729
10915
  name = "CodeRuntimeControlError";
10730
10916
  };
10731
- record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
10917
+ record6 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
10732
10918
  invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
10733
10919
  RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
10734
10920
  SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
@@ -10881,6 +11067,10 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10881
11067
  recipeId: "selector",
10882
11068
  sourceDigest: "payload"
10883
11069
  });
11070
+ DEFAULT_FAILURE_REASON2 = {
11071
+ "tool denied by CaMeL policy": "tool denied by CaMeL policy",
11072
+ "review sessions are read-only": "review session is read-only; workspace changes are not permitted"
11073
+ };
10884
11074
  cache = /* @__PURE__ */ new Map();
10885
11075
  shortId = (id2) => id2.slice(id2.indexOf(":") + 1);
10886
11076
  GRAPH_TOOLS = /* @__PURE__ */ new Set([
@@ -10893,30 +11083,6 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10893
11083
  POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
10894
11084
  digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
10895
11085
  runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
10896
- text3 = (value2, maximum) => {
10897
- if (typeof value2 !== "string") return void 0;
10898
- const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
10899
- return bounded ? bounded.slice(0, maximum) : void 0;
10900
- };
10901
- integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
10902
- record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
10903
- excerpt = (value2, tail = false) => {
10904
- if (typeof value2 !== "string") return void 0;
10905
- const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
10906
- const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
10907
- if (!source) return void 0;
10908
- const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
10909
- const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
10910
- return text3(selected.join("\n"), 2400);
10911
- };
10912
- paths = (value2) => {
10913
- if (!Array.isArray(value2)) return void 0;
10914
- const items = value2.flatMap((item) => {
10915
- const path = text3(item, 1024);
10916
- return path ? [path] : [];
10917
- }).slice(0, 12);
10918
- return items.length ? items : void 0;
10919
- };
10920
11086
  TheseusRuntimeEngine = class {
10921
11087
  constructor(options) {
10922
11088
  this.options = options;
@@ -11113,7 +11279,7 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
11113
11279
  event: (event) => this.#event(command, event, active.conversationRefs)
11114
11280
  });
11115
11281
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
11116
- const extraSkills = await sessionSkillsFor(this.options, command);
11282
+ const extraSkills = observeCodeRuntimeSessionSkills(command, await sessionSkillsFor(this.options, command), (event) => this.#event(command, event, active.conversationRefs));
11117
11283
  const result = await this.#attempt({
11118
11284
  inference,
11119
11285
  broker,
@@ -11152,36 +11318,11 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
11152
11318
  }
11153
11319
  /** Report every brokered effect as it starts and finishes. */
11154
11320
  #observed(command, active, broker) {
11155
- return {
11156
- execute: async (context, request3) => {
11157
- const startedAt = Date.now();
11158
- const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
11159
- const startedPresentation = codeToolRequestPresentation(request3);
11160
- await this.#event(
11161
- command,
11162
- {
11163
- type: "tool",
11164
- phase: "started",
11165
- tool: request3.tool,
11166
- operationId,
11167
- ...startedPresentation ? { presentation: startedPresentation } : {}
11168
- },
11169
- active.conversationRefs
11170
- ).catch(() => void 0);
11171
- const response2 = await broker.execute(context, request3);
11172
- const completedPresentation = codeToolResultPresentation(request3, response2);
11173
- await this.#event(command, {
11174
- type: "tool",
11175
- phase: "completed",
11176
- tool: request3.tool,
11177
- ok: response2.ok,
11178
- durationMs: Date.now() - startedAt,
11179
- operationId,
11180
- ...completedPresentation ? { presentation: completedPresentation } : {}
11181
- }, active.conversationRefs).catch(() => void 0);
11182
- return response2;
11183
- }
11184
- };
11321
+ return observedBroker({
11322
+ broker,
11323
+ operationIdFor: (requestId) => digestRuntimeValue(`${command.commandId}:${requestId}`),
11324
+ emit: (event) => this.#event(command, event, active.conversationRefs).catch(() => void 0)
11325
+ });
11185
11326
  }
11186
11327
  async #checkpoint(command) {
11187
11328
  const active = this.#active.get(command.sessionId);
@@ -11218,8 +11359,8 @@ var init_node = __esm({
11218
11359
  "../harness/dist/node.js"() {
11219
11360
  "use strict";
11220
11361
  init_cjs_shims();
11221
- init_chunk_J7XU5QB7();
11222
- init_chunk_CR6RE3A2();
11362
+ init_chunk_Q4NL5XO3();
11363
+ init_chunk_5LRYJKUI();
11223
11364
  MEASURED_PREMIUM = Object.freeze({
11224
11365
  /** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
11225
11366
  racePerRacer: 0.55,
@@ -11461,6 +11602,19 @@ var init_code_runtime_config = __esm({
11461
11602
  init_cjs_shims();
11462
11603
  CODE_NODE_IMAGE = "node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd";
11463
11604
  CODE_BUILD_RECIPES = Object.freeze([{
11605
+ id: "odla-code-gates",
11606
+ image: CODE_NODE_IMAGE,
11607
+ // Selects the repository's fast, no-capability gates from the same manifest
11608
+ // CI and `npm run preflight` read, so a new gate is picked up without a
11609
+ // second list to keep in sync. The container has no shell: this is argv.
11610
+ command: ["node", "scripts/recipe-gates.mjs"],
11611
+ timeoutMs: 18e4,
11612
+ maxOutputBytes: 1024 * 1024,
11613
+ cpus: 1,
11614
+ // check:secrets holds ~2,500 source files in memory at once.
11615
+ memory: "1g",
11616
+ pids: 128
11617
+ }, {
11464
11618
  id: "odla-code-contracts",
11465
11619
  image: CODE_NODE_IMAGE,
11466
11620
  command: [
@@ -11642,20 +11796,20 @@ async function runCodeRuntime(input) {
11642
11796
  }
11643
11797
  }
11644
11798
  function parseConnection(value2, appId, appEnv) {
11645
- const root = record6(value2);
11646
- const host = record6(root?.host);
11647
- const offer = record6(root?.offer);
11648
- const binding = record6(root?.binding);
11799
+ const root = record7(value2);
11800
+ const host = record7(root?.host);
11801
+ const offer = record7(root?.offer);
11802
+ const binding = record7(root?.binding);
11649
11803
  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)) {
11650
11804
  throw new Error("connect Code host returned an invalid response");
11651
11805
  }
11652
11806
  return root;
11653
11807
  }
11654
11808
  function apiFailure(action2, status, value2) {
11655
- const message2 = record6(record6(value2)?.error)?.message;
11809
+ const message2 = record7(record7(value2)?.error)?.message;
11656
11810
  return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
11657
11811
  }
11658
- function record6(value2) {
11812
+ function record7(value2) {
11659
11813
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
11660
11814
  }
11661
11815
  var import_node_fs18, import_node_os5, import_node_path18;
@@ -12250,6 +12404,14 @@ Usage:
12250
12404
  odla-ai ai models [--config odla.config.mjs] [--env dev] [--provider <id>] [--json]
12251
12405
  odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
12252
12406
  odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
12407
+ odla-ai promotion plan --file <promotion-plan.json> [--env dev] [--json]
12408
+ odla-ai promotion inspect <plan-id> [--env dev] [--json]
12409
+ odla-ai promotion status <plan-id> [--env dev] [--json]
12410
+ odla-ai promotion apply <plan-id> --approval <approval-id> [--idempotency-key <key>] [--env dev] [--json]
12411
+ odla-ai promotion rollback <plan-id> --approval <approval-id> [--idempotency-key <key>] [--env dev] [--json]
12412
+ odla-ai promotion next <plan-id> <operation-id> [--env dev] [--json]
12413
+ odla-ai promotion record <plan-id> <operation-id> <step-id> --file <step-receipt.json> [--env dev] [--json]
12414
+ odla-ai promotion cancel <plan-id> <operation-id> [--env dev] [--json]
12253
12415
  odla-ai operations get <operation-id> [--json]
12254
12416
  odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
12255
12417
  odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
@@ -12418,6 +12580,11 @@ Commands:
12418
12580
  init Create a generic odla.config.mjs plus starter schema/rules files.
12419
12581
  doctor Validate and summarize the project config without network calls.
12420
12582
  config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
12583
+ promotion Plan and inspect exact selective-release bundles without target
12584
+ mutation, then queue and resume a human-approved apply or rollback
12585
+ from an enrolled device. Each target step is leased and receipt-
12586
+ journaled; approval remains bound to the immutable plan digest,
12587
+ target set, action, and expiry, so every mismatch fails closed.
12421
12588
  operations Inspect or wait on one exact, durable config-operation receipt.
12422
12589
  calendar Inspect, connect, or disconnect the live Google booking connection.
12423
12590
  app Archive (suspend, data retained), restore, export, import, or
@@ -13327,17 +13494,17 @@ function collectEntityFields(entity, parsed, allowClear) {
13327
13494
  if (entity === "task" && fields.column === "ready") fields.column = "todo";
13328
13495
  return fields;
13329
13496
  }
13330
- function statusCol(entity, record11) {
13331
- if (entity === "bug") return `${record11.status ?? ""}/${record11.severity ?? ""}`;
13497
+ function statusCol(entity, record12) {
13498
+ if (entity === "bug") return `${record12.status ?? ""}/${record12.severity ?? ""}`;
13332
13499
  if (entity === "task") {
13333
- const state2 = record11.column === "todo" ? "ready" : String(record11.column ?? "");
13334
- return record11.revision ? `${state2}; r${record11.revision}` : state2;
13500
+ const state2 = record12.column === "todo" ? "ready" : String(record12.column ?? "");
13501
+ return record12.revision ? `${state2}; r${record12.revision}` : state2;
13335
13502
  }
13336
- return String(record11.status ?? "");
13503
+ return String(record12.status ?? "");
13337
13504
  }
13338
- function referenceMarkup(entity, record11) {
13339
- const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
13340
- return `@[${label}](pm:${entity}/${record11.id})`;
13505
+ function referenceMarkup(entity, record12) {
13506
+ const label = (record12.title?.trim() || `${entity} ${record12.id}`).replaceAll("]", ")");
13507
+ return `@[${label}](pm:${entity}/${record12.id})`;
13341
13508
  }
13342
13509
  function studioRecordUrl(ctx, entity, id2) {
13343
13510
  return new URL(
@@ -13345,13 +13512,13 @@ function studioRecordUrl(ctx, entity, id2) {
13345
13512
  ctx.platformUrl
13346
13513
  ).href;
13347
13514
  }
13348
- function studioRecordLink(ctx, entity, record11) {
13349
- const label = (record11.title?.trim() || `${entity} ${record11.id}`).replaceAll("]", ")");
13350
- return `[${label}](${studioRecordUrl(ctx, entity, record11.id)})`;
13515
+ function studioRecordLink(ctx, entity, record12) {
13516
+ const label = (record12.title?.trim() || `${entity} ${record12.id}`).replaceAll("]", ")");
13517
+ return `[${label}](${studioRecordUrl(ctx, entity, record12.id)})`;
13351
13518
  }
13352
- function printRecord(ctx, entity, record11) {
13519
+ function printRecord(ctx, entity, record12) {
13353
13520
  ctx.out.log(
13354
- `${record11.id} [${statusCol(entity, record11)}] ${record11.appId} ${studioRecordLink(ctx, entity, record11)}`
13521
+ `${record12.id} [${statusCol(entity, record12)}] ${record12.appId} ${studioRecordLink(ctx, entity, record12)}`
13355
13522
  );
13356
13523
  }
13357
13524
  function emit2(ctx, value2, human) {
@@ -13455,8 +13622,8 @@ async function pmAdd(ctx, entity, parsed) {
13455
13622
  input,
13456
13623
  mutationId: writeMutationId2(parsed)
13457
13624
  });
13458
- const record11 = { id: res.id, appId, title: String(input.title) };
13459
- emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record11)}`));
13625
+ const record12 = { id: res.id, appId, title: String(input.title) };
13626
+ emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record12)}`));
13460
13627
  }
13461
13628
  function looksLikeUuidPrefix(value2) {
13462
13629
  if (!UUID_PREFIX.test(value2) || !/[0-9a-f]/i.test(value2)) return false;
@@ -13474,7 +13641,7 @@ async function resolvePmId(ctx, entity, supplied) {
13474
13641
  "GET",
13475
13642
  `/${entity}?limit=${limit}&offset=${offset}`
13476
13643
  );
13477
- matches.push(...page2.records.map((record11) => record11.id).filter((id2) => id2.toLowerCase().startsWith(prefix)));
13644
+ matches.push(...page2.records.map((record12) => record12.id).filter((id2) => id2.toLowerCase().startsWith(prefix)));
13478
13645
  offset += page2.records.length;
13479
13646
  if (matches.length > 1 || page2.records.length === 0 || offset >= page2.total) break;
13480
13647
  }
@@ -13488,17 +13655,17 @@ async function resolvePmId(ctx, entity, supplied) {
13488
13655
  }
13489
13656
  async function pmGet(ctx, entity, id2) {
13490
13657
  const resolved = await resolvePmId(ctx, entity, id2);
13491
- const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(resolved)}`);
13492
- emit2(ctx, record11, () => printRecord(ctx, entity, record11));
13658
+ const { record: record12 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(resolved)}`);
13659
+ emit2(ctx, record12, () => printRecord(ctx, entity, record12));
13493
13660
  }
13494
13661
  async function pmReference(ctx, entity, id2) {
13495
- const { record: record11 } = await pmRequest(
13662
+ const { record: record12 } = await pmRequest(
13496
13663
  ctx,
13497
13664
  "GET",
13498
13665
  `/${entity}/${encodeURIComponent(id2)}`
13499
13666
  );
13500
- const markup = referenceMarkup(entity, record11);
13501
- emit2(ctx, { kind: `pm:${entity}`, id: record11.id, label: record11.title ?? "", markup }, () => {
13667
+ const markup = referenceMarkup(entity, record12);
13668
+ emit2(ctx, { kind: `pm:${entity}`, id: record12.id, label: record12.title ?? "", markup }, () => {
13502
13669
  ctx.out.log(markup);
13503
13670
  });
13504
13671
  }
@@ -13577,16 +13744,16 @@ var init_pm_actions = __esm({
13577
13744
  });
13578
13745
 
13579
13746
  // src/pm-lean.ts
13580
- function leanRecord(entity, record11) {
13747
+ function leanRecord(entity, record12) {
13581
13748
  const out = {};
13582
13749
  for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
13583
- const value2 = record11[key];
13750
+ const value2 = record12[key];
13584
13751
  if (value2 !== void 0 && value2 !== null) out[key] = value2;
13585
13752
  }
13586
13753
  return out;
13587
13754
  }
13588
13755
  function leanRecords(entity, records, verbose) {
13589
- return verbose ? records : records.map((record11) => leanRecord(entity, record11));
13756
+ return verbose ? records : records.map((record12) => leanRecord(entity, record12));
13590
13757
  }
13591
13758
  var COMMON, PER_ENTITY;
13592
13759
  var init_pm_lean = __esm({
@@ -13638,16 +13805,18 @@ async function pmNext(ctx, parsed) {
13638
13805
  // One request for both live columns; the split below is over open work only.
13639
13806
  listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
13640
13807
  ]);
13641
- const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
13642
- const doing = tasks.filter((record11) => record11.column === "doing");
13808
+ const ready = tasks.filter((record12) => record12.column === READY_COLUMN);
13809
+ const doing = tasks.filter((record12) => record12.column === "doing");
13643
13810
  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}`;
13811
+ const monitor = `odla-ai pm watch --app ${appId} --jsonl`;
13644
13812
  const result = {
13645
13813
  appId,
13646
13814
  projectId,
13647
13815
  openGoals: leanRecords("goal", goals, verbose),
13648
13816
  doing: leanRecords("task", doing, verbose),
13649
13817
  ready: leanRecords("task", ready, verbose),
13650
- next: guidance
13818
+ next: guidance,
13819
+ monitor
13651
13820
  };
13652
13821
  emit2(ctx, result, () => {
13653
13822
  ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
@@ -13658,9 +13827,10 @@ async function pmNext(ctx, parsed) {
13658
13827
  ]) {
13659
13828
  ctx.out.log(`${label}:`);
13660
13829
  if (!records.length) ctx.out.log("- (none)");
13661
- else for (const record11 of records) printRecord(ctx, entity, record11);
13830
+ else for (const record12 of records) printRecord(ctx, entity, record12);
13662
13831
  }
13663
13832
  ctx.out.log(`next: ${guidance}`);
13833
+ ctx.out.log(`monitor: ${monitor}`);
13664
13834
  });
13665
13835
  }
13666
13836
  async function pmHandoff(ctx, parsed) {
@@ -13672,17 +13842,20 @@ async function pmHandoff(ctx, parsed) {
13672
13842
  listFiltered(ctx, "bug", appId, projectId, { status: OPEN_BUG_STATUSES })
13673
13843
  ]);
13674
13844
  const clean4 = !goals.length && !tasks.length && !bugs.length;
13845
+ const monitor = `odla-ai pm watch --app ${appId} --jsonl`;
13675
13846
  const result = {
13676
13847
  appId,
13677
13848
  projectId,
13678
13849
  unmetGoals: leanRecords("goal", goals, verbose),
13679
13850
  activeTasks: leanRecords("task", tasks, verbose),
13680
13851
  openBugs: leanRecords("bug", bugs, verbose),
13681
- clean: clean4
13852
+ clean: clean4,
13853
+ monitor
13682
13854
  };
13683
13855
  emit2(ctx, result, () => {
13684
13856
  if (clean4) {
13685
13857
  ctx.out.log(`${appId}: no unresolved PM work`);
13858
+ ctx.out.log(`monitor: ${monitor}`);
13686
13859
  return;
13687
13860
  }
13688
13861
  ctx.out.log(`${appId}: authoritative PM handoff`);
@@ -13693,8 +13866,9 @@ async function pmHandoff(ctx, parsed) {
13693
13866
  ]) {
13694
13867
  ctx.out.log(`${label}:`);
13695
13868
  if (!records.length) ctx.out.log("- (none)");
13696
- else for (const record11 of records) printRecord(ctx, entity, record11);
13869
+ else for (const record12 of records) printRecord(ctx, entity, record12);
13697
13870
  }
13871
+ ctx.out.log(`monitor: ${monitor}`);
13698
13872
  });
13699
13873
  }
13700
13874
  var READY_COLUMN, UNMET_GOAL_STATUS, ACTIVE_TASK_COLUMNS, OPEN_BUG_STATUSES, wantsVerbose;
@@ -13793,14 +13967,14 @@ var init_pm_start = __esm({
13793
13967
 
13794
13968
  // src/pm-links.ts
13795
13969
  async function pmLink(ctx, entity, id2) {
13796
- const { record: record11 } = await pmRequest(
13970
+ const { record: record12 } = await pmRequest(
13797
13971
  ctx,
13798
13972
  "GET",
13799
13973
  `/${entity}/${encodeURIComponent(id2)}`
13800
13974
  );
13801
- const url = studioRecordUrl(ctx, entity, record11.id);
13802
- const markdown = studioRecordLink(ctx, entity, record11);
13803
- emit2(ctx, { kind: entity, id: record11.id, label: record11.title ?? "", url, markdown }, () => {
13975
+ const url = studioRecordUrl(ctx, entity, record12.id);
13976
+ const markdown = studioRecordLink(ctx, entity, record12);
13977
+ emit2(ctx, { kind: entity, id: record12.id, label: record12.title ?? "", url, markdown }, () => {
13804
13978
  ctx.out.log(markdown);
13805
13979
  });
13806
13980
  }
@@ -13955,16 +14129,16 @@ async function page(ctx, appId, cursor) {
13955
14129
  }
13956
14130
  return data;
13957
14131
  }
13958
- function recordState(record11) {
13959
- if (record11.column) return record11.column === "todo" ? "ready" : record11.column;
13960
- return String(record11.status ?? "");
14132
+ function recordState(record12) {
14133
+ if (record12.column) return record12.column === "todo" ? "ready" : record12.column;
14134
+ return String(record12.status ?? "");
13961
14135
  }
13962
14136
  function eventRecord(event) {
13963
14137
  return event.payload.payload;
13964
14138
  }
13965
14139
  function eventLabel(event) {
13966
- const record11 = eventRecord(event);
13967
- if (record11) return String(record11.title ?? event.payload.entityId);
14140
+ const record12 = eventRecord(event);
14141
+ if (record12) return String(record12.title ?? event.payload.entityId);
13968
14142
  const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
13969
14143
  return body || event.payload.entityId;
13970
14144
  }
@@ -13972,10 +14146,10 @@ function report3(ctx, parsed, result) {
13972
14146
  if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
13973
14147
  else if (parsed.options.jsonl !== true && result.found) {
13974
14148
  for (const event of result.events ?? []) {
13975
- const record11 = eventRecord(event);
13976
- const state2 = record11 ? recordState(record11) : "comment";
14149
+ const record12 = eventRecord(event);
14150
+ const state2 = record12 ? recordState(record12) : "comment";
13977
14151
  ctx.out.log(
13978
- `${event.id} ${event.type} ${state2}${record11?.revision ? `; r${record11.revision}` : ""} ${eventLabel(event)}`
14152
+ `${event.id} ${event.type} ${state2}${record12?.revision ? `; r${record12.revision}` : ""} ${eventLabel(event)}`
13979
14153
  );
13980
14154
  }
13981
14155
  }
@@ -14049,8 +14223,8 @@ async function pmWatch(ctx, parsed) {
14049
14223
  }
14050
14224
  firstSuccess = false;
14051
14225
  const matching = current.events.filter((event) => {
14052
- const record11 = eventRecord(event);
14053
- const state2 = record11 ? recordState(record11).toLowerCase() : "";
14226
+ const record12 = eventRecord(event);
14227
+ const state2 = record12 ? recordState(record12).toLowerCase() : "";
14054
14228
  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);
14055
14229
  });
14056
14230
  for (const event of matching) {
@@ -14538,17 +14712,17 @@ async function platformStatus(parsed, deps) {
14538
14712
  }
14539
14713
  }
14540
14714
  function isPlatformStatus(value2) {
14541
- if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
14542
- if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
14543
- if (!record7(value2.catalog) || !record7(value2.summary)) return false;
14715
+ if (!record8(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
14716
+ if (!record8(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
14717
+ if (!record8(value2.catalog) || !record8(value2.summary)) return false;
14544
14718
  return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
14545
14719
  }
14546
14720
  function apiMessage(value2) {
14547
- if (!record7(value2)) return "request failed";
14548
- const error = record7(value2.error) ? value2.error : value2;
14721
+ if (!record8(value2)) return "request failed";
14722
+ const error = record8(value2.error) ? value2.error : value2;
14549
14723
  return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
14550
14724
  }
14551
- function record7(value2) {
14725
+ function record8(value2) {
14552
14726
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
14553
14727
  }
14554
14728
  var init_platform_command = __esm({
@@ -14600,7 +14774,7 @@ function statusVerdict(reads) {
14600
14774
  severity: "degraded"
14601
14775
  });
14602
14776
  }
14603
- const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
14777
+ const performance = record9(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
14604
14778
  if (performance?.status === "unavailable") {
14605
14779
  reasons.push({
14606
14780
  source: "liveSync",
@@ -14681,7 +14855,7 @@ function statusVerdict(reads) {
14681
14855
  reasons
14682
14856
  };
14683
14857
  }
14684
- function record8(value2) {
14858
+ function record9(value2) {
14685
14859
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
14686
14860
  }
14687
14861
  function numeric2(value2) {
@@ -14715,7 +14889,7 @@ function printO11yStatus(status, out) {
14715
14889
  out.log(
14716
14890
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
14717
14891
  );
14718
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
14892
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record10) : [];
14719
14893
  const requests = routes.reduce(
14720
14894
  (total, row) => total + numeric3(row.requests),
14721
14895
  0
@@ -14727,39 +14901,39 @@ function printO11yStatus(status, out) {
14727
14901
  out.log(
14728
14902
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
14729
14903
  );
14730
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
14904
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record10) : [];
14731
14905
  out.log(
14732
14906
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
14733
14907
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
14734
14908
  ).join(", ") : "none observed"}`
14735
14909
  );
14736
14910
  out.log(liveSyncLine(status.liveSync));
14737
- const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
14911
+ const canaryDurations = record10(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
14738
14912
  out.log(
14739
14913
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
14740
14914
  );
14741
- const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
14742
- const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
14915
+ const collectorIngest = record10(status.collector.body.ingest) ? status.collector.body.ingest : {};
14916
+ const collectorStorage = record10(collectorIngest.storage) ? collectorIngest.storage : {};
14743
14917
  out.log(
14744
14918
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
14745
14919
  );
14746
- const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
14747
- const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
14748
- const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
14920
+ const providerMetrics = record10(status.provider.body.metrics) ? status.provider.body.metrics : {};
14921
+ const providerCapacity = record10(status.provider.body.capacity) ? status.provider.body.capacity : {};
14922
+ const workerMemory = record10(providerCapacity.memory) ? providerCapacity.memory : {};
14749
14923
  out.log(
14750
14924
  `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`
14751
14925
  );
14752
14926
  for (const line2 of providerCapacityLines(status.providerCapacity)) {
14753
14927
  out.log(line2);
14754
14928
  }
14755
- const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
14756
- const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
14757
- const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
14929
+ const coverage = record10(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
14930
+ const coverageCounts = record10(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
14931
+ const coverageBudget = record10(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
14758
14932
  out.log(
14759
14933
  `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`
14760
14934
  );
14761
14935
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
14762
- const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
14936
+ const providerFreshness = record10(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
14763
14937
  out.log(
14764
14938
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
14765
14939
  );
@@ -14768,17 +14942,17 @@ function printO11yStatus(status, out) {
14768
14942
  );
14769
14943
  }
14770
14944
  function providerCapacityLines(read3) {
14771
- const resources = record9(read3.body.resources) ? read3.body.resources : {};
14772
- const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
14773
- const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
14774
- const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
14775
- const d1 = record9(resources.d1) ? resources.d1 : {};
14776
- const d1Activity = record9(d1.activity) ? d1.activity : {};
14777
- const d1Storage = record9(d1.storage) ? d1.storage : {};
14778
- const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
14779
- const r2 = record9(resources.r2) ? resources.r2 : {};
14780
- const r2Operations = record9(r2.operations) ? r2.operations : {};
14781
- const r2Storage = record9(r2.storage) ? r2.storage : {};
14945
+ const resources = record10(read3.body.resources) ? read3.body.resources : {};
14946
+ const durableObjects = record10(resources.durableObjects) ? resources.durableObjects : {};
14947
+ const periodic = record10(durableObjects.periodic) ? durableObjects.periodic : {};
14948
+ const storage = record10(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
14949
+ const d1 = record10(resources.d1) ? resources.d1 : {};
14950
+ const d1Activity = record10(d1.activity) ? d1.activity : {};
14951
+ const d1Storage = record10(d1.storage) ? d1.storage : {};
14952
+ const d1Latency = record10(d1Activity.latency) ? d1Activity.latency : {};
14953
+ const r2 = record10(resources.r2) ? resources.r2 : {};
14954
+ const r2Operations = record10(r2.operations) ? r2.operations : {};
14955
+ const r2Storage = record10(r2.storage) ? r2.storage : {};
14782
14956
  const status = String(
14783
14957
  read3.body.status ?? read3.body.error ?? "unavailable"
14784
14958
  );
@@ -14789,11 +14963,11 @@ function providerCapacityLines(read3) {
14789
14963
  ];
14790
14964
  }
14791
14965
  function liveSyncLine(read3) {
14792
- const performance = record9(read3.body.performance) ? read3.body.performance : {};
14793
- const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
14966
+ const performance = record10(read3.body.performance) ? read3.body.performance : {};
14967
+ const commitToSend = record10(performance.commitToSend) ? performance.commitToSend : {};
14794
14968
  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`;
14795
14969
  }
14796
- function record9(value2) {
14970
+ function record10(value2) {
14797
14971
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
14798
14972
  }
14799
14973
  function numeric3(value2) {
@@ -15141,7 +15315,7 @@ async function monitorCommand(parsed, deps = {}) {
15141
15315
  if (action2 === "plan" || action2 === "apply") {
15142
15316
  const desired = monitoringWireConfig(context.cfg, env);
15143
15317
  const live = await request2(`${base}?env=${encodeURIComponent(env)}`, { headers }, doFetch);
15144
- const currentRevision = record10(live.config) ? string(live.config.revision) : null;
15318
+ const currentRevision = record11(live.config) ? string(live.config.revision) : null;
15145
15319
  const changed = currentRevision !== desired.revision;
15146
15320
  const plan = {
15147
15321
  schemaVersion: 1,
@@ -15186,7 +15360,7 @@ async function monitorCommand(parsed, deps = {}) {
15186
15360
  headers
15187
15361
  }, doFetch);
15188
15362
  emit3(result2, jsonOutput, out, () => {
15189
- const run = record10(result2.run) ? result2.run : {};
15363
+ const run = record11(result2.run) ? result2.run : {};
15190
15364
  out.log(`monitor run ${appId}/${env}/${probeId}: ${string(run.outcome) ?? "unknown"}${run.failure_code ? ` (${String(run.failure_code)})` : ""}`);
15191
15365
  });
15192
15366
  return;
@@ -15211,12 +15385,12 @@ async function request2(url, init, doFetch) {
15211
15385
  let body = {};
15212
15386
  try {
15213
15387
  const parsed = text4 ? JSON.parse(text4) : {};
15214
- body = record10(parsed) ? parsed : { value: parsed };
15388
+ body = record11(parsed) ? parsed : { value: parsed };
15215
15389
  } catch {
15216
15390
  body = { message: text4.slice(0, 500) };
15217
15391
  }
15218
15392
  if (!response2.ok) {
15219
- const error = record10(body.error) ? body.error : body;
15393
+ const error = record11(body.error) ? body.error : body;
15220
15394
  throw new Error(string(error.message) ?? string(error.code) ?? `monitor request failed (${response2.status})`);
15221
15395
  }
15222
15396
  return body;
@@ -15224,7 +15398,7 @@ async function request2(url, init, doFetch) {
15224
15398
  function printRead(action2, appId, env, result, out) {
15225
15399
  if (action2 === "status") {
15226
15400
  out.log(`monitor status ${appId}/${env}: ${String(result.overall ?? (result.configured === false ? "not configured" : "unknown"))}`);
15227
- const slos = Array.isArray(result.slos) ? result.slos.filter(record10) : [];
15401
+ const slos = Array.isArray(result.slos) ? result.slos.filter(record11) : [];
15228
15402
  for (const slo of slos) out.log(`slo ${String(slo.id)} ${String(slo.state)} ${percent(slo.observed)} observed ${percent(slo.budgetRemaining)} budget remaining`);
15229
15403
  const incidents = Array.isArray(result.openIncidents) ? result.openIncidents.length : 0;
15230
15404
  const gaps = Array.isArray(result.monitoringGaps) ? result.monitoringGaps.length : 0;
@@ -15233,20 +15407,20 @@ function printRead(action2, appId, env, result, out) {
15233
15407
  return;
15234
15408
  }
15235
15409
  if (action2 === "incidents") {
15236
- const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record10) : [];
15410
+ const incidents = Array.isArray(result.incidents) ? result.incidents.filter(record11) : [];
15237
15411
  out.log(`monitor incidents ${appId}/${env}: ${incidents.length}`);
15238
15412
  for (const incident2 of incidents) out.log(`${String(incident2.state)} ${String(incident2.kind)} ${String(incident2.slo_id)} ${new Date(Number(incident2.opened_at)).toISOString()}`);
15239
15413
  return;
15240
15414
  }
15241
15415
  out.log(`monitor report ${appId}/${env}: ${String(result.period)} ${String(result.overall)}`);
15242
- const probes = Array.isArray(result.probes) ? result.probes.filter(record10) : [];
15416
+ const probes = Array.isArray(result.probes) ? result.probes.filter(record11) : [];
15243
15417
  for (const probe of probes) out.log(`probe ${String(probe.id)} ${Number(probe.good)} good ${Number(probe.bad)} bad ${Number(probe.unknown)} unknown`);
15244
15418
  }
15245
15419
  function emit3(value2, json, out, human) {
15246
15420
  if (json) out.log(JSON.stringify(value2, null, 2));
15247
15421
  else human();
15248
15422
  }
15249
- function record10(value2) {
15423
+ function record11(value2) {
15250
15424
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
15251
15425
  }
15252
15426
  function string(value2) {
@@ -15814,6 +15988,139 @@ var init_provision = __esm({
15814
15988
  }
15815
15989
  });
15816
15990
 
15991
+ // src/security-command-context.ts
15992
+ async function hostedSecurityContext(parsed, dependencies) {
15993
+ const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
15994
+ const cfg = await loadProjectConfig(configPath);
15995
+ const env = stringOpt(parsed.options.env) ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
15996
+ if (!env || !cfg.envs.includes(env)) {
15997
+ throw new Error(`env "${env ?? ""}" is not declared in ${configPath}`);
15998
+ }
15999
+ const platform = platformAudience(stringOpt(parsed.options.platform) ?? cfg.platformUrl);
16000
+ if (platformAudience(cfg.platformUrl) !== platform) {
16001
+ throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
16002
+ }
16003
+ const doFetch = dependencies.fetch ?? fetch;
16004
+ const stdout = dependencies.stdout ?? console;
16005
+ const open = parsed.options.open === false ? false : parsed.options.open === true ? true : void 0;
16006
+ const token = await getDeveloperToken(
16007
+ cfg,
16008
+ { configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
16009
+ doFetch,
16010
+ stdout,
16011
+ { optionalProjectCapabilities: ["app.manage"] }
16012
+ );
16013
+ return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
16014
+ }
16015
+ function requiredSecurityPositional(parsed, index, label) {
16016
+ const value2 = parsed.positionals[index];
16017
+ if (!value2) throw new Error(`${label} is required`);
16018
+ return value2;
16019
+ }
16020
+ function securityProfile(value2) {
16021
+ if (value2 === void 0) return void 0;
16022
+ if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
16023
+ throw new Error("--profile must be odla, cloudflare-app, or generic");
16024
+ }
16025
+ var init_security_command_context = __esm({
16026
+ "src/security-command-context.ts"() {
16027
+ "use strict";
16028
+ init_cjs_shims();
16029
+ init_argv();
16030
+ init_config();
16031
+ init_token();
16032
+ }
16033
+ });
16034
+
16035
+ // src/promotion-command.ts
16036
+ async function promotionCommand(parsed, dependencies) {
16037
+ const action2 = parsed.positionals[1];
16038
+ if (!action2 || !["plan", "inspect", "status", "apply", "rollback", "next", "record", "cancel"].includes(action2)) rejectWord(["promotion"], action2);
16039
+ assertArgs(parsed, ["config", "env", "platform", "email", "open", "json", "file", "approval", "idempotency-key"], 5);
16040
+ const context = await hostedSecurityContext(parsed, dependencies);
16041
+ const client = (0, import_apps14.createAppsClient)({
16042
+ token: context.token,
16043
+ fetcher: { fetch: (request3, init) => {
16044
+ const internal = new URL(typeof request3 === "string" ? request3 : request3.url);
16045
+ return context.fetch(`${context.platform}${internal.pathname}${internal.search}`, init);
16046
+ } }
16047
+ });
16048
+ const json = parsed.options.json === true;
16049
+ if (action2 === "plan") {
16050
+ if (parsed.positionals[2]) throw new Error("promotion plan takes --file, not a positional plan id");
16051
+ const file = stringOpt(parsed.options.file);
16052
+ if (!file) throw new Error("--file <promotion-plan.json> is required");
16053
+ const source = JSON.parse(await (0, import_promises12.readFile)(file, "utf8"));
16054
+ const plan2 = await client.createPromotionReleasePlan(context.appId, source);
16055
+ render(plan2, json, context.stdout);
16056
+ return;
16057
+ }
16058
+ const planId = requiredSecurityPositional(parsed, 2, "plan id");
16059
+ if (action2 === "inspect") {
16060
+ if (parsed.positionals[3]) throw new Error("promotion inspect takes only a plan id");
16061
+ const plan2 = await client.getPromotionReleasePlan(context.appId, planId);
16062
+ if (!plan2) throw new Error(`promotion release ${planId} was not found`);
16063
+ render(plan2, json, context.stdout);
16064
+ return;
16065
+ }
16066
+ if (action2 === "status") {
16067
+ if (parsed.positionals[3]) throw new Error("promotion status takes only a plan id");
16068
+ const status = await client.getPromotionReleaseStatus(context.appId, planId);
16069
+ if (!status) throw new Error(`promotion release ${planId} was not found`);
16070
+ render(status, json, context.stdout);
16071
+ return;
16072
+ }
16073
+ if (action2 === "next" || action2 === "cancel" || action2 === "record") {
16074
+ const operationId = requiredSecurityPositional(parsed, 3, "operation id");
16075
+ if (action2 === "next") {
16076
+ if (parsed.positionals[4]) throw new Error("promotion next takes only plan and operation ids");
16077
+ render(await client.claimPromotionReleaseWork(context.appId, planId, operationId), json, context.stdout);
16078
+ return;
16079
+ }
16080
+ if (action2 === "cancel") {
16081
+ if (parsed.positionals[4]) throw new Error("promotion cancel takes only plan and operation ids");
16082
+ render(await client.cancelPromotionReleaseOperation(context.appId, planId, operationId), json, context.stdout);
16083
+ return;
16084
+ }
16085
+ const stepId = requiredSecurityPositional(parsed, 4, "step id");
16086
+ const file = stringOpt(parsed.options.file);
16087
+ if (!file) throw new Error("--file <step-receipt.json> is required");
16088
+ const receipt = JSON.parse(await (0, import_promises12.readFile)(file, "utf8"));
16089
+ render(await client.recordPromotionReleaseWork(context.appId, planId, operationId, stepId, receipt), json, context.stdout);
16090
+ return;
16091
+ }
16092
+ const approvalId = stringOpt(parsed.options.approval);
16093
+ if (parsed.positionals[3]) throw new Error(`promotion ${action2} takes only a plan id`);
16094
+ if (!approvalId) throw new Error(`--approval <approval-id> is required for promotion ${action2}`);
16095
+ const plan = await client.getPromotionReleasePlan(context.appId, planId);
16096
+ if (!plan) throw new Error(`promotion release ${planId} was not found`);
16097
+ if (plan.state !== "reviewable") throw new Error(`promotion release is ${plan.state}: ${plan.blockers.join("; ")}`);
16098
+ const operationAction = action2;
16099
+ const operation = await client.requestPromotionReleaseOperation(context.appId, planId, operationAction, {
16100
+ approvalId,
16101
+ planDigest: plan.planDigest,
16102
+ targetSetDigest: plan.targetSetDigest,
16103
+ idempotencyKey: stringOpt(parsed.options["idempotency-key"]) ?? `${operationAction}:${planId}`
16104
+ });
16105
+ render(operation, json, context.stdout);
16106
+ }
16107
+ var import_promises12, import_apps14, render;
16108
+ var init_promotion_command = __esm({
16109
+ "src/promotion-command.ts"() {
16110
+ "use strict";
16111
+ init_cjs_shims();
16112
+ import_promises12 = require("fs/promises");
16113
+ import_apps14 = require("@odla-ai/apps");
16114
+ init_argv();
16115
+ init_security_command_context();
16116
+ init_surface();
16117
+ render = (value2, json, out) => {
16118
+ if (json) out.log(JSON.stringify(value2));
16119
+ else out.log(JSON.stringify(value2, null, 2));
16120
+ };
16121
+ }
16122
+ });
16123
+
15817
16124
  // src/record.ts
15818
16125
  function recordInvocation(parsed) {
15819
16126
  const file = import_node_process19.default.env.ODLA_CLI_RECORD;
@@ -15845,7 +16152,7 @@ function advisoryCollectingFetch(inner, sink) {
15845
16152
  return (async (input, init) => {
15846
16153
  const response2 = await inner(input, init);
15847
16154
  try {
15848
- sink.push(...(0, import_apps14.parseAdvisories)(response2));
16155
+ sink.push(...(0, import_apps15.parseAdvisories)(response2));
15849
16156
  } catch {
15850
16157
  }
15851
16158
  return response2;
@@ -15864,15 +16171,15 @@ function renderAdvisories(out, advisories, env = process.env) {
15864
16171
  const key = `${advisory.code}:${advisory.message}`;
15865
16172
  if (seen.has(key)) continue;
15866
16173
  seen.add(key);
15867
- out.error((0, import_apps14.formatAdvisory)(advisory));
16174
+ out.error((0, import_apps15.formatAdvisory)(advisory));
15868
16175
  }
15869
16176
  }
15870
- var import_apps14, superseded;
16177
+ var import_apps15, superseded;
15871
16178
  var init_advisory_output = __esm({
15872
16179
  "src/advisory-output.ts"() {
15873
16180
  "use strict";
15874
16181
  init_cjs_shims();
15875
- import_apps14 = require("@odla-ai/apps");
16182
+ import_apps15 = require("@odla-ai/apps");
15876
16183
  superseded = /* @__PURE__ */ new Set();
15877
16184
  }
15878
16185
  });
@@ -16131,7 +16438,7 @@ async function bySlug(ctx, slug) {
16131
16438
  "GET",
16132
16439
  `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
16133
16440
  );
16134
- const filtered = page2.records.find((record11) => record11.slug === slug);
16441
+ const filtered = page2.records.find((record12) => record12.slug === slug);
16135
16442
  if (filtered) return filtered;
16136
16443
  const limit = 100;
16137
16444
  for (let offset = 0; ; offset += limit) {
@@ -16140,7 +16447,7 @@ async function bySlug(ctx, slug) {
16140
16447
  "GET",
16141
16448
  `/runbook?app=${encodeURIComponent(ctx.appId)}&limit=${limit}&offset=${offset}`
16142
16449
  );
16143
- const found = fallback.records.find((record11) => record11.slug === slug);
16450
+ const found = fallback.records.find((record12) => record12.slug === slug);
16144
16451
  if (found) return found;
16145
16452
  if (!fallback.records.length || offset + fallback.records.length >= fallback.total) break;
16146
16453
  }
@@ -17025,50 +17332,6 @@ var init_runbook_command = __esm({
17025
17332
  }
17026
17333
  });
17027
17334
 
17028
- // src/security-command-context.ts
17029
- async function hostedSecurityContext(parsed, dependencies) {
17030
- const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
17031
- const cfg = await loadProjectConfig(configPath);
17032
- const env = stringOpt(parsed.options.env) ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
17033
- if (!env || !cfg.envs.includes(env)) {
17034
- throw new Error(`env "${env ?? ""}" is not declared in ${configPath}`);
17035
- }
17036
- const platform = platformAudience(stringOpt(parsed.options.platform) ?? cfg.platformUrl);
17037
- if (platformAudience(cfg.platformUrl) !== platform) {
17038
- throw new Error("--platform cannot reuse a project developer token from another platform; update odla.config.mjs and authenticate there");
17039
- }
17040
- const doFetch = dependencies.fetch ?? fetch;
17041
- const stdout = dependencies.stdout ?? console;
17042
- const open = parsed.options.open === false ? false : parsed.options.open === true ? true : void 0;
17043
- const token = await getDeveloperToken(
17044
- cfg,
17045
- { configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
17046
- doFetch,
17047
- stdout,
17048
- { optionalProjectCapabilities: ["app.manage"] }
17049
- );
17050
- return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
17051
- }
17052
- function requiredSecurityPositional(parsed, index, label) {
17053
- const value2 = parsed.positionals[index];
17054
- if (!value2) throw new Error(`${label} is required`);
17055
- return value2;
17056
- }
17057
- function securityProfile(value2) {
17058
- if (value2 === void 0) return void 0;
17059
- if (value2 === "odla" || value2 === "cloudflare-app" || value2 === "generic") return value2;
17060
- throw new Error("--profile must be odla, cloudflare-app, or generic");
17061
- }
17062
- var init_security_command_context = __esm({
17063
- "src/security-command-context.ts"() {
17064
- "use strict";
17065
- init_cjs_shims();
17066
- init_argv();
17067
- init_config();
17068
- init_token();
17069
- }
17070
- });
17071
-
17072
17335
  // src/security-command-output.ts
17073
17336
  function printHostedSecurityPlan(out, plan, appId) {
17074
17337
  out.log(`Hosted security plan for ${appId}/${plan.env}: ${plan.ready ? "ready" : "not ready"}`);
@@ -17860,6 +18123,10 @@ async function dispatchCli(argv2, dependencies) {
17860
18123
  await securityCommand(parsed, runtime);
17861
18124
  return;
17862
18125
  }
18126
+ if (command === "promotion") {
18127
+ await promotionCommand(parsed, runtime);
18128
+ return;
18129
+ }
17863
18130
  if (command === "brand") {
17864
18131
  await brandCommand(parsed, runtime);
17865
18132
  return;
@@ -17989,6 +18256,7 @@ var init_cli = __esm({
17989
18256
  init_o11y_command();
17990
18257
  init_monitor_command();
17991
18258
  init_provision();
18259
+ init_promotion_command();
17992
18260
  init_record();
17993
18261
  init_advisory_output();
17994
18262
  init_cached_credential();