@odla-ai/cli 0.25.22 → 0.25.23

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
@@ -493,6 +493,7 @@ function audienceBoundEnvToken(token, platform) {
493
493
  var SCOPE_PURPOSE = {
494
494
  "platform:status:read": "read the platform fleet health and deployment snapshot",
495
495
  "app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
496
+ "app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
496
497
  "platform:runbook:write": "add or edit odla's operational runbooks",
497
498
  "platform:ai:policy:write": "change System AI model routing",
498
499
  "platform:ai:policy:read": "read System AI model routing",
@@ -1048,6 +1049,7 @@ function unique(values) {
1048
1049
  var DEFAULT_PLATFORM = "https://odla.ai";
1049
1050
  var DEFAULT_ENVS = ["dev"];
1050
1051
  var DEFAULT_SERVICES = ["db", "ai"];
1052
+ var configImportSerial = 0;
1051
1053
  var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
1052
1054
  async function loadProjectConfig(configPath = "odla.config.mjs") {
1053
1055
  const resolved = (0, import_node_path4.resolve)(configPath);
@@ -1255,7 +1257,8 @@ function validId2(value2) {
1255
1257
  }
1256
1258
  async function loadConfigModule(path) {
1257
1259
  if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
1258
- const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?t=${Date.now()}`);
1260
+ const nonce = `${Date.now()}-${configImportSerial++}`;
1261
+ const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?reload=${nonce}`);
1259
1262
  const value2 = mod.default ?? mod.config;
1260
1263
  if (typeof value2 === "function") return await value2();
1261
1264
  return value2;
@@ -2639,7 +2642,8 @@ var CAPABILITIES = {
2639
2642
  "save and explicitly select non-secret named operator contexts with isolated credential caches; resolve and explain platform, app, environment, and credential provenance without authenticating; then run PM, Discussions, o11y, runbook, and identity operations outside a project checkout",
2640
2643
  "read one versioned o11y status envelope spanning application RED, exact Worker versions and Cloudflare colos observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, account-scoped Durable Object, D1, and R2 evidence under odla-db, and a bounded machine verdict",
2641
2644
  "read one canonical platform fleet snapshot over private service bindings, including release identities, probe latency, Cloudflare load/runtime freshness, explicit unknowns, and stable next actions through a read-only capability",
2642
- "compare one project's checked-in Registry intent with live owner-visible state and freeze a secret-free plan digest through an exact app:config:read capability",
2645
+ "compare one project's checked-in Registry intent with live owner-visible state, freeze a secret-free Registry-revision-bound plan through app:config:read, and conditionally apply checkpoint-free actions through exact app:config:write operation routes",
2646
+ "inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
2643
2647
  "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
2644
2648
  "run app-attributed hosted security discovery and independent validation without provider keys",
2645
2649
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
@@ -2686,10 +2690,31 @@ function printGroup(out, heading, items) {
2686
2690
  out.log("");
2687
2691
  }
2688
2692
 
2689
- // src/config-reconcile-command.ts
2693
+ // src/config-operation-command.ts
2690
2694
  var import_apps6 = require("@odla-ai/apps");
2691
2695
  var import_node_path7 = require("path");
2692
2696
 
2697
+ // src/version.ts
2698
+ var import_node_fs9 = require("fs");
2699
+ function cliVersion() {
2700
+ const pkg = JSON.parse((0, import_node_fs9.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
2701
+ return pkg.version ?? "unknown";
2702
+ }
2703
+
2704
+ // src/config-operation-error.ts
2705
+ var ConfigOperationCommandError = class extends Error {
2706
+ constructor(message2, code) {
2707
+ super(message2);
2708
+ this.code = code;
2709
+ this.name = "ConfigOperationCommandError";
2710
+ }
2711
+ code;
2712
+ };
2713
+
2714
+ // src/config-operation-validate.ts
2715
+ var import_apps3 = require("@odla-ai/apps");
2716
+ var import_node_fs10 = require("fs");
2717
+
2693
2718
  // src/config-reconcile-digest.ts
2694
2719
  var import_node_crypto = require("crypto");
2695
2720
  function canonicalJson(value2) {
@@ -2699,27 +2724,160 @@ function configDigest(value2) {
2699
2724
  return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
2700
2725
  }
2701
2726
  function canonicalValue(value2) {
2727
+ if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
2728
+ if (typeof value2 === "number") {
2729
+ if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
2730
+ return value2;
2731
+ }
2702
2732
  if (Array.isArray(value2)) return value2.map(canonicalValue);
2703
2733
  if (value2 && typeof value2 === "object") {
2734
+ const record10 = value2;
2704
2735
  return Object.fromEntries(
2705
- Object.entries(value2).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalValue(entry)])
2736
+ Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, canonicalValue(record10[key])])
2706
2737
  );
2707
2738
  }
2708
- return value2;
2739
+ throw new TypeError("canonical JSON rejects unsupported values");
2740
+ }
2741
+
2742
+ // src/config-operation-validate.ts
2743
+ var DIGEST = /^sha256:[0-9a-f]{64}$/;
2744
+ var REVISION = /^registry:[1-9][0-9]*$/;
2745
+ var OPERATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2746
+ var ACTION_ID = /^action-[0-9a-f]{16}$/;
2747
+ var ENV = /^[a-z0-9]{2,12}$/;
2748
+ var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
2749
+ function readPlan(path) {
2750
+ let value2;
2751
+ try {
2752
+ const raw = (0, import_node_fs10.readFileSync)(path, "utf8");
2753
+ if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
2754
+ value2 = JSON.parse(raw);
2755
+ } catch (error) {
2756
+ throw new ConfigOperationCommandError(
2757
+ `cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
2758
+ "invalid_plan"
2759
+ );
2760
+ }
2761
+ if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
2762
+ if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
2763
+ invalidPlan("plan scope is invalid");
2764
+ }
2765
+ if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
2766
+ invalidPlan("plan content revisions are invalid");
2767
+ }
2768
+ if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
2769
+ invalidPlan("plan Registry revision or digest is invalid");
2770
+ }
2771
+ if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
2772
+ invalidPlan("plan actions must contain 1-32 entries");
2773
+ }
2774
+ assertActions(value2.actions);
2775
+ const plan = value2;
2776
+ const digest = configDigest({
2777
+ schemaVersion: plan.schemaVersion,
2778
+ desiredRevision: plan.desiredRevision,
2779
+ observedRevision: plan.observedRevision,
2780
+ registryRevision: plan.registryRevision,
2781
+ actions: plan.actions
2782
+ });
2783
+ if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
2784
+ return plan;
2785
+ }
2786
+ function assertPlanContext(plan, appId, platformUrl) {
2787
+ if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
2788
+ throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
2789
+ }
2790
+ }
2791
+ function verifyReceipt(receipt, appId, operationId) {
2792
+ if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
2793
+ throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
2794
+ }
2795
+ if (receipt.receiptDigest) {
2796
+ const { receiptDigest, ...fields } = receipt;
2797
+ if (configDigest(fields) !== receiptDigest) {
2798
+ throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
2799
+ }
2800
+ } else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
2801
+ throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
2802
+ }
2803
+ }
2804
+ function assertOperationId(value2) {
2805
+ if (!OPERATION_ID.test(value2)) {
2806
+ throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
2807
+ }
2808
+ }
2809
+ function assertActions(actions) {
2810
+ const ids = /* @__PURE__ */ new Set();
2811
+ for (const action2 of actions) {
2812
+ if (!record2(action2)) invalidPlan("every plan action must be an object");
2813
+ const id = String(action2.id ?? "");
2814
+ if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
2815
+ ids.add(id);
2816
+ if (typeof action2.path !== "string" || typeof action2.reason !== "string" || !action2.reason || action2.reason.length > 500 || typeof action2.requiresApproval !== "boolean" || !["low", "medium", "high"].includes(String(action2.risk)) || !["provision", "command", "studio"].includes(String(action2.applySupport))) {
2817
+ invalidPlan("plan action metadata is invalid");
2818
+ }
2819
+ if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
2820
+ assertConditionalAction(action2);
2821
+ }
2822
+ }
2823
+ }
2824
+ function assertConditionalAction(action2) {
2825
+ if (action2.kind === "rename_app") {
2826
+ if (action2.path !== "app.name" || action2.applySupport !== "command" || typeof action2.before !== "string" || typeof action2.after !== "string" || action2.after !== action2.after.trim() || !action2.after || action2.after.length > 80) invalidPlan("rename action is invalid");
2827
+ return;
2828
+ }
2829
+ if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
2830
+ if (action2.kind === "set_link") {
2831
+ if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
2832
+ return;
2833
+ }
2834
+ if (!action2.service || !SERVICE.test(action2.service) || !(0, import_apps3.appServiceDefinition)(action2.service)) {
2835
+ invalidPlan("service action names an unknown service");
2836
+ }
2837
+ const base = `environments.${action2.env}.services.${action2.service}`;
2838
+ if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
2839
+ invalidPlan("service action path is invalid");
2840
+ }
2841
+ if (action2.applySupport !== "provision" || !record2(action2.after)) {
2842
+ invalidPlan("service action payload is invalid");
2843
+ }
2844
+ if (action2.kind === "enable_service") {
2845
+ if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
2846
+ invalidPlan("service enable action is invalid");
2847
+ }
2848
+ } else if (!record2(action2.before)) {
2849
+ invalidPlan("service configure action is invalid");
2850
+ }
2851
+ }
2852
+ function linkState(value2) {
2853
+ if (value2 === null) return true;
2854
+ if (typeof value2 !== "string") return false;
2855
+ try {
2856
+ const url = new URL(value2.trim());
2857
+ return url.protocol === "http:" || url.protocol === "https:";
2858
+ } catch {
2859
+ return false;
2860
+ }
2861
+ }
2862
+ function invalidPlan(message2) {
2863
+ throw new ConfigOperationCommandError(message2, "invalid_plan");
2864
+ }
2865
+ function record2(value2) {
2866
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
2709
2867
  }
2710
2868
 
2711
2869
  // src/config-reconcile-desired.ts
2712
- var import_apps4 = require("@odla-ai/apps");
2870
+ var import_apps5 = require("@odla-ai/apps");
2713
2871
 
2714
2872
  // src/provision-helpers.ts
2715
2873
  var import_ai = require("@odla-ai/ai");
2716
- var import_apps3 = require("@odla-ai/apps");
2874
+ var import_apps4 = require("@odla-ai/apps");
2717
2875
  function defaultSecretName(provider) {
2718
2876
  const names = import_ai.DEFAULT_SECRET_NAMES;
2719
2877
  return names[provider] ?? `${provider}_api_key`;
2720
2878
  }
2721
2879
  async function assertTenantAdminAccess(doFetch, cfg, env, token) {
2722
- const tenantId = (0, import_apps3.tenantIdFor)(cfg.app.id, env);
2880
+ const tenantId = (0, import_apps4.tenantIdFor)(cfg.app.id, env);
2723
2881
  const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
2724
2882
  headers: { authorization: `Bearer ${token}` }
2725
2883
  });
@@ -2761,7 +2919,7 @@ async function safeText3(res) {
2761
2919
  // src/config-reconcile-desired.ts
2762
2920
  function desiredRegistryState(cfg) {
2763
2921
  const environments = {};
2764
- const services = (0, import_apps4.orderAppServices)(cfg.services);
2922
+ const services = (0, import_apps5.orderAppServices)(cfg.services);
2765
2923
  for (const env of cfg.envs) {
2766
2924
  const desiredServices = {};
2767
2925
  for (const service of services) {
@@ -2801,8 +2959,208 @@ function managedServiceConfig(cfg, env, service) {
2801
2959
  return {};
2802
2960
  }
2803
2961
 
2962
+ // src/config-reconcile-support.ts
2963
+ var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
2964
+ "rename_app",
2965
+ "enable_service",
2966
+ "configure_service",
2967
+ "set_link"
2968
+ ]);
2969
+ function configApplySupport(reconciliation) {
2970
+ if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
2971
+ return {
2972
+ supported: false,
2973
+ checkpointRequired: false,
2974
+ reason: "the app must already exist in a revision-aware Registry"
2975
+ };
2976
+ }
2977
+ if (!reconciliation.actions.length) {
2978
+ return {
2979
+ supported: false,
2980
+ checkpointRequired: false,
2981
+ reason: "there are no managed changes to apply"
2982
+ };
2983
+ }
2984
+ const blocked = reconciliation.actions.filter(
2985
+ (action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
2986
+ );
2987
+ if (blocked.length) {
2988
+ return {
2989
+ supported: false,
2990
+ checkpointRequired: blocked.some(
2991
+ (action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
2992
+ ),
2993
+ reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
2994
+ };
2995
+ }
2996
+ return {
2997
+ supported: true,
2998
+ checkpointRequired: false,
2999
+ reason: "all actions are low-risk, checkpoint-free Registry changes"
3000
+ };
3001
+ }
3002
+
3003
+ // src/config-operation-command.ts
3004
+ var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
3005
+ var DEFAULT_WAIT_SECONDS = 60;
3006
+ var DEFAULT_INTERVAL_SECONDS = 2;
3007
+ async function configApply(options) {
3008
+ const plan = readPlan(options.planPath);
3009
+ const cfg = await loadProjectConfig(options.configPath);
3010
+ assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
3011
+ const support = configApplySupport(plan);
3012
+ if (!support.supported) {
3013
+ throw new ConfigOperationCommandError(
3014
+ support.reason,
3015
+ support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
3016
+ );
3017
+ }
3018
+ if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
3019
+ throw new ConfigOperationCommandError(
3020
+ "project config changed after this plan was frozen; generate and review a fresh plan",
3021
+ "checkpoint_required"
3022
+ );
3023
+ }
3024
+ const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
3025
+ if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
3026
+ throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
3027
+ }
3028
+ const client = await operationClient(cfg, options, "apply");
3029
+ const request2 = {
3030
+ schemaVersion: "odla.config-operation-request/v1",
3031
+ expectedRevision: plan.registryRevision,
3032
+ desiredRevision: plan.desiredRevision,
3033
+ observedRevision: plan.observedRevision,
3034
+ planDigest: plan.planDigest,
3035
+ idempotencyKey,
3036
+ source: { kind: "cli", version: cliVersion() },
3037
+ actions: plan.actions
3038
+ };
3039
+ let receipt;
3040
+ try {
3041
+ receipt = await client.applyConfigOperation(cfg.app.id, request2);
3042
+ } catch (error) {
3043
+ const retained = retainedReceipt(error);
3044
+ if (retained) {
3045
+ verifyReceipt(retained, cfg.app.id);
3046
+ printReceipt(retained, options);
3047
+ throw failureForReceipt(retained);
3048
+ }
3049
+ throw normalizeRequestError(error);
3050
+ }
3051
+ verifyReceipt(receipt, cfg.app.id);
3052
+ printReceipt(receipt, options);
3053
+ assertApplyCompleted(receipt);
3054
+ return receipt;
3055
+ }
3056
+ async function configOperationGet(options) {
3057
+ assertOperationId(options.operationId);
3058
+ const cfg = await loadProjectConfig(options.configPath);
3059
+ const client = await operationClient(cfg, options, "read");
3060
+ const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
3061
+ throw normalizeRequestError(error);
3062
+ });
3063
+ if (!receipt) {
3064
+ throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
3065
+ }
3066
+ verifyReceipt(receipt, cfg.app.id, options.operationId);
3067
+ printReceipt(receipt, options);
3068
+ return receipt;
3069
+ }
3070
+ async function configOperationWait(options) {
3071
+ assertOperationId(options.operationId);
3072
+ const cfg = await loadProjectConfig(options.configPath);
3073
+ const client = await operationClient(cfg, options, "wait");
3074
+ const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
3075
+ const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
3076
+ const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
3077
+ const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
3078
+ let receipt = null;
3079
+ for (; ; ) {
3080
+ receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
3081
+ throw normalizeRequestError(error);
3082
+ });
3083
+ if (!receipt) {
3084
+ throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
3085
+ }
3086
+ verifyReceipt(receipt, cfg.app.id, options.operationId);
3087
+ if (receipt.state !== "running") break;
3088
+ if (now() >= deadline) {
3089
+ printReceipt(receipt, options);
3090
+ throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
3091
+ }
3092
+ await wait2(Math.min(interval, Math.max(0, deadline - now())));
3093
+ }
3094
+ printReceipt(receipt, options);
3095
+ if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
3096
+ return receipt;
3097
+ }
3098
+ async function operationClient(cfg, options, purpose) {
3099
+ const doFetch = options.fetch ?? fetch;
3100
+ const out = options.stdout ?? console;
3101
+ const token = await resolveAdminPlatformToken({
3102
+ platform: cfg.platformUrl,
3103
+ scope: "app:config:write",
3104
+ token: options.token,
3105
+ tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3106
+ rootDir: cfg.rootDir,
3107
+ email: options.email,
3108
+ open: options.open,
3109
+ fetch: doFetch,
3110
+ stdout: out,
3111
+ openApprovalUrl: options.openApprovalUrl,
3112
+ label: `odla CLI (${cfg.app.id} config operation ${purpose})`
3113
+ });
3114
+ return (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
3115
+ }
3116
+ function printReceipt(receipt, options) {
3117
+ const out = options.stdout ?? console;
3118
+ if (options.json) out.log(JSON.stringify(receipt, null, 2));
3119
+ else {
3120
+ out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
3121
+ out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
3122
+ for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
3123
+ out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
3124
+ out.log(`studio: ${receipt.studioUrl}`);
3125
+ }
3126
+ }
3127
+ function assertApplyCompleted(receipt) {
3128
+ if (receipt.state === "succeeded") return;
3129
+ throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
3130
+ }
3131
+ function failureForReceipt(receipt) {
3132
+ if (receipt.state === "conflict") return new ConfigOperationCommandError(
3133
+ receipt.error?.message ?? "config operation conflicted with current Registry state",
3134
+ "checkpoint_required"
3135
+ );
3136
+ if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
3137
+ return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
3138
+ }
3139
+ function retainedReceipt(error) {
3140
+ if (!(error instanceof import_apps6.AppsError) || !record3(error.details)) return null;
3141
+ return record3(error.details.operation) ? error.details.operation : null;
3142
+ }
3143
+ function normalizeRequestError(error) {
3144
+ if (!(error instanceof import_apps6.AppsError)) return error instanceof Error ? error : new Error(String(error));
3145
+ if (error.status === 401 || error.status === 403) {
3146
+ return new ConfigOperationCommandError(error.message, "auth_failed");
3147
+ }
3148
+ if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
3149
+ if (error.status === 429 || error.status >= 500) {
3150
+ return new ConfigOperationCommandError(error.message, "remote_unavailable");
3151
+ }
3152
+ return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
3153
+ }
3154
+ function record3(value2) {
3155
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
3156
+ }
3157
+
3158
+ // src/config-reconcile-command.ts
3159
+ var import_apps8 = require("@odla-ai/apps");
3160
+ var import_node_path8 = require("path");
3161
+
2804
3162
  // src/config-reconcile.ts
2805
- var import_apps5 = require("@odla-ai/apps");
3163
+ var import_apps7 = require("@odla-ai/apps");
2806
3164
 
2807
3165
  // src/config-reconcile-values.ts
2808
3166
  function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
@@ -2903,6 +3261,7 @@ function reconcileConfig(input) {
2903
3261
  sources: { desired: desiredSource, observed: observedSource },
2904
3262
  desiredRevision,
2905
3263
  observedRevision,
3264
+ registryRevision: input.observed?.configRevision ?? null,
2906
3265
  status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
2907
3266
  summary: {
2908
3267
  differences: different,
@@ -2957,7 +3316,7 @@ function compareEnvironments(desired, observed, desiredSource, observedSource, a
2957
3316
  function compareServices(env, desired, observed, desiredSource, observedSource, add) {
2958
3317
  const wanted = desired.environments[env]?.services ?? {};
2959
3318
  const live = observed?.environments[env] ?? {};
2960
- const knownOrder = (0, import_apps5.orderAppServices)((0, import_apps5.appServiceIds)());
3319
+ const knownOrder = (0, import_apps7.orderAppServices)((0, import_apps7.appServiceIds)());
2961
3320
  const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
2962
3321
  for (const service of services) {
2963
3322
  const next = wanted[service];
@@ -3068,20 +3427,19 @@ async function configDiff(options) {
3068
3427
  }
3069
3428
  async function configPlan(options) {
3070
3429
  const reconciliation = await inspectConfig(options);
3430
+ const apply = configApplySupport(reconciliation);
3071
3431
  const planDigest = configDigest({
3072
- schemaVersion: "odla.config-plan/v1",
3432
+ schemaVersion: "odla.config-plan/v2",
3073
3433
  desiredRevision: reconciliation.desiredRevision,
3074
3434
  observedRevision: reconciliation.observedRevision,
3435
+ registryRevision: reconciliation.registryRevision,
3075
3436
  actions: reconciliation.actions
3076
3437
  });
3077
3438
  const document2 = {
3078
- schemaVersion: "odla.config-plan/v1",
3439
+ schemaVersion: "odla.config-plan/v2",
3079
3440
  ...reconciliation,
3080
3441
  planDigest,
3081
- apply: {
3082
- supported: false,
3083
- reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
3084
- },
3442
+ apply,
3085
3443
  nextActions: planNextActions(reconciliation, options.configPath)
3086
3444
  };
3087
3445
  printPlan2(document2, options);
@@ -3095,7 +3453,7 @@ async function inspectConfig(options) {
3095
3453
  platform: cfg.platformUrl,
3096
3454
  scope: "app:config:read",
3097
3455
  token: options.token,
3098
- tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3456
+ tokenFile: (0, import_node_path8.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3099
3457
  rootDir: cfg.rootDir,
3100
3458
  email: options.email,
3101
3459
  open: options.open,
@@ -3104,7 +3462,7 @@ async function inspectConfig(options) {
3104
3462
  openApprovalUrl: options.openApprovalUrl,
3105
3463
  label: `odla CLI (${cfg.app.id} config read)`
3106
3464
  });
3107
- const client = (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
3465
+ const client = (0, import_apps8.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
3108
3466
  const observed = await client.resolveApp(cfg.app.id);
3109
3467
  return reconcileConfig({
3110
3468
  desired: desiredRegistryState(cfg),
@@ -3140,7 +3498,7 @@ function printPlan2(document2, options) {
3140
3498
  }
3141
3499
  if (!document2.actions.length) out.log(" no managed changes");
3142
3500
  out.log(`plan digest: ${document2.planDigest}`);
3143
- out.log(`apply: unsupported \u2014 ${document2.apply.reason}`);
3501
+ out.log(`apply: ${document2.apply.supported ? "supported" : "blocked"} \u2014 ${document2.apply.reason}`);
3144
3502
  printNext(out, document2.nextActions);
3145
3503
  }
3146
3504
  function printHeader(out, kind, document2) {
@@ -3148,6 +3506,7 @@ function printHeader(out, kind, document2) {
3148
3506
  out.log(`platform: ${document2.scope.platformUrl}`);
3149
3507
  out.log(`desired: ${document2.desiredRevision}`);
3150
3508
  out.log(`observed: ${document2.observedRevision ?? "absent"}`);
3509
+ out.log(`registry: ${document2.registryRevision ?? "absent"}`);
3151
3510
  out.log(
3152
3511
  `summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
3153
3512
  );
@@ -3180,6 +3539,13 @@ function diffNextActions(reconciliation, configPath) {
3180
3539
  }
3181
3540
  function planNextActions(reconciliation, configPath) {
3182
3541
  const next = [];
3542
+ if (configApplySupport(reconciliation).supported) {
3543
+ next.push({
3544
+ code: "apply_frozen_plan",
3545
+ command: "odla-ai config apply --plan <saved-plan.json> --json",
3546
+ description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
3547
+ });
3548
+ }
3183
3549
  if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
3184
3550
  next.push({
3185
3551
  code: "review_provision",
@@ -3215,13 +3581,13 @@ function quoteArg2(value2) {
3215
3581
 
3216
3582
  // src/doctor-checks.ts
3217
3583
  var import_node_child_process3 = require("child_process");
3218
- var import_node_fs10 = require("fs");
3219
- var import_node_path9 = require("path");
3584
+ var import_node_fs12 = require("fs");
3585
+ var import_node_path10 = require("path");
3220
3586
 
3221
3587
  // src/wrangler.ts
3222
3588
  var import_node_child_process2 = require("child_process");
3223
- var import_node_fs9 = require("fs");
3224
- var import_node_path8 = require("path");
3589
+ var import_node_fs11 = require("fs");
3590
+ var import_node_path9 = require("path");
3225
3591
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
3226
3592
  const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
3227
3593
  let stdout = "";
@@ -3235,15 +3601,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
3235
3601
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
3236
3602
  function findWranglerConfig(rootDir) {
3237
3603
  for (const name of WRANGLER_CONFIG_FILES) {
3238
- const path = (0, import_node_path8.join)(rootDir, name);
3239
- if ((0, import_node_fs9.existsSync)(path)) return path;
3604
+ const path = (0, import_node_path9.join)(rootDir, name);
3605
+ if ((0, import_node_fs11.existsSync)(path)) return path;
3240
3606
  }
3241
3607
  return null;
3242
3608
  }
3243
3609
  function readWranglerConfig(path) {
3244
3610
  if (path.endsWith(".toml")) return null;
3245
3611
  try {
3246
- return JSON.parse(stripJsonComments((0, import_node_fs9.readFileSync)(path, "utf8")));
3612
+ return JSON.parse(stripJsonComments((0, import_node_fs11.readFileSync)(path, "utf8")));
3247
3613
  } catch {
3248
3614
  return null;
3249
3615
  }
@@ -3341,10 +3707,10 @@ function wranglerWarnings(rootDir) {
3341
3707
  for (const { label, block } of blocks) {
3342
3708
  const assets = block.assets;
3343
3709
  if (assets?.directory) {
3344
- const dir = (0, import_node_path9.resolve)(rootDir, assets.directory);
3345
- if (dir === (0, import_node_path9.resolve)(rootDir)) {
3710
+ const dir = (0, import_node_path10.resolve)(rootDir, assets.directory);
3711
+ if (dir === (0, import_node_path10.resolve)(rootDir)) {
3346
3712
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
3347
- } else if ((0, import_node_fs10.existsSync)((0, import_node_path9.join)(dir, "node_modules"))) {
3713
+ } else if ((0, import_node_fs12.existsSync)((0, import_node_path10.join)(dir, "node_modules"))) {
3348
3714
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
3349
3715
  }
3350
3716
  }
@@ -3379,13 +3745,13 @@ function o11yProjectWarnings(rootDir) {
3379
3745
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
3380
3746
  return warnings;
3381
3747
  }
3382
- const main = typeof config.main === "string" ? (0, import_node_path9.resolve)(rootDir, config.main) : null;
3383
- if (!main || !(0, import_node_fs10.existsSync)(main)) {
3748
+ const main = typeof config.main === "string" ? (0, import_node_path10.resolve)(rootDir, config.main) : null;
3749
+ if (!main || !(0, import_node_fs12.existsSync)(main)) {
3384
3750
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
3385
3751
  } else {
3386
3752
  let source = "";
3387
3753
  try {
3388
- source = (0, import_node_fs10.readFileSync)(main, "utf8");
3754
+ source = (0, import_node_fs12.readFileSync)(main, "utf8");
3389
3755
  } catch {
3390
3756
  }
3391
3757
  if (!/\bwithObservability\b/.test(source)) {
@@ -3409,7 +3775,7 @@ function calendarProjectWarnings(rootDir) {
3409
3775
  }
3410
3776
  function readPackageJson(rootDir) {
3411
3777
  try {
3412
- return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path9.join)(rootDir, "package.json"), "utf8"));
3778
+ return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(rootDir, "package.json"), "utf8"));
3413
3779
  } catch {
3414
3780
  return null;
3415
3781
  }
@@ -3636,14 +4002,14 @@ function harnessOption(value2, flag) {
3636
4002
  }
3637
4003
 
3638
4004
  // src/init.ts
3639
- var import_node_fs11 = require("fs");
3640
- var import_node_path10 = require("path");
3641
- var import_apps7 = require("@odla-ai/apps");
4005
+ var import_node_fs13 = require("fs");
4006
+ var import_node_path11 = require("path");
4007
+ var import_apps9 = require("@odla-ai/apps");
3642
4008
  function initProject(options) {
3643
4009
  const out = options.stdout ?? console;
3644
- const rootDir = (0, import_node_path10.resolve)(options.rootDir ?? process.cwd());
3645
- const configPath = (0, import_node_path10.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
3646
- if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
4010
+ const rootDir = (0, import_node_path11.resolve)(options.rootDir ?? process.cwd());
4011
+ const configPath = (0, import_node_path11.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
4012
+ if ((0, import_node_fs13.existsSync)(configPath) && !options.force) {
3647
4013
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
3648
4014
  }
3649
4015
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -3652,27 +4018,27 @@ function initProject(options) {
3652
4018
  const envs = options.envs?.length ? options.envs : ["dev"];
3653
4019
  const services = options.services?.length ? options.services : ["db", "ai"];
3654
4020
  for (const service of services) {
3655
- const definition = (0, import_apps7.appServiceDefinition)(service);
3656
- if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps7.appServiceIds)().join(", ")})`);
4021
+ const definition = (0, import_apps9.appServiceDefinition)(service);
4022
+ if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps9.appServiceIds)().join(", ")})`);
3657
4023
  for (const dependency of definition.requires) {
3658
4024
  if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
3659
4025
  }
3660
4026
  }
3661
4027
  const aiProvider = options.aiProvider ?? "anthropic";
3662
- (0, import_node_fs11.mkdirSync)((0, import_node_path10.dirname)(configPath), { recursive: true });
3663
- (0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, "src/odla"), { recursive: true });
3664
- (0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, ".odla"), { recursive: true });
3665
- (0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
3666
- writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
3667
- writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
4028
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(configPath), { recursive: true });
4029
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, "src/odla"), { recursive: true });
4030
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, ".odla"), { recursive: true });
4031
+ (0, import_node_fs13.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
4032
+ writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
4033
+ writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
3668
4034
  ensureGitignore(rootDir);
3669
4035
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
3670
4036
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
3671
4037
  out.log("updated .gitignore for local odla credentials");
3672
4038
  }
3673
4039
  function writeIfMissing(path, text) {
3674
- if ((0, import_node_fs11.existsSync)(path)) return;
3675
- (0, import_node_fs11.writeFileSync)(path, text);
4040
+ if ((0, import_node_fs13.existsSync)(path)) return;
4041
+ (0, import_node_fs13.writeFileSync)(path, text);
3676
4042
  }
3677
4043
  function configTemplate(input) {
3678
4044
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -3847,7 +4213,7 @@ function assertWranglerConfig(cfg) {
3847
4213
 
3848
4214
  // src/secrets-set.ts
3849
4215
  var import_ai2 = require("@odla-ai/ai");
3850
- var import_apps8 = require("@odla-ai/apps");
4216
+ var import_apps10 = require("@odla-ai/apps");
3851
4217
  var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
3852
4218
  async function secretsSet(options) {
3853
4219
  const name = (options.name ?? "").trim();
@@ -3899,13 +4265,13 @@ async function resolveVaultWrite(options) {
3899
4265
  throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
3900
4266
  }
3901
4267
  const value2 = await secretInputValue(options, "secret");
3902
- return { cfg, tenantId: (0, import_apps8.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
4268
+ return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
3903
4269
  }
3904
4270
 
3905
4271
  // src/skill.ts
3906
- var import_node_fs12 = require("fs");
4272
+ var import_node_fs14 = require("fs");
3907
4273
  var import_node_os2 = require("os");
3908
- var import_node_path11 = require("path");
4274
+ var import_node_path12 = require("path");
3909
4275
  var import_node_url2 = require("url");
3910
4276
 
3911
4277
  // src/skill-adapters.ts
@@ -3984,8 +4350,8 @@ function installSkill(options = {}) {
3984
4350
  const files = listFiles(sourceDir);
3985
4351
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
3986
4352
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
3987
- const root = (0, import_node_path11.resolve)(options.dir ?? process.cwd());
3988
- const home = (0, import_node_path11.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
4353
+ const root = (0, import_node_path12.resolve)(options.dir ?? process.cwd());
4354
+ const home = (0, import_node_path12.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
3989
4355
  const plans = /* @__PURE__ */ new Map();
3990
4356
  const targets = /* @__PURE__ */ new Map();
3991
4357
  const rememberTarget = (harness, target) => {
@@ -3999,48 +4365,48 @@ function installSkill(options = {}) {
3999
4365
  plans.set(target, { target, content: content2, boundary, managedMerge });
4000
4366
  };
4001
4367
  const planSkillTree = (targetDir2, boundary = root) => {
4002
- for (const rel of files) plan((0, import_node_path11.join)(targetDir2, rel), (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(sourceDir, rel), "utf8"), false, boundary);
4368
+ for (const rel of files) plan((0, import_node_path12.join)(targetDir2, rel), (0, import_node_fs14.readFileSync)((0, import_node_path12.join)(sourceDir, rel), "utf8"), false, boundary);
4003
4369
  };
4004
4370
  let targetDir;
4005
4371
  if (options.global) {
4006
- const claudeRoot = (0, import_node_path11.join)(home, ".claude", "skills");
4007
- const codexRoot = (0, import_node_path11.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path11.join)(home, ".codex"), "skills");
4372
+ const claudeRoot = (0, import_node_path12.join)(home, ".claude", "skills");
4373
+ const codexRoot = (0, import_node_path12.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path12.join)(home, ".codex"), "skills");
4008
4374
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
4009
4375
  for (const harness of harnesses) {
4010
4376
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
4011
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path11.dirname)((0, import_node_path11.dirname)(codexRoot)));
4377
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path12.dirname)((0, import_node_path12.dirname)(codexRoot)));
4012
4378
  rememberTarget(harness, skillRoot);
4013
4379
  }
4014
4380
  } else {
4015
- const sharedRoot = (0, import_node_path11.join)(root, ".agents", "skills");
4381
+ const sharedRoot = (0, import_node_path12.join)(root, ".agents", "skills");
4016
4382
  planSkillTree(sharedRoot);
4017
- const claudeRoot = (0, import_node_path11.join)(root, ".claude", "skills");
4383
+ const claudeRoot = (0, import_node_path12.join)(root, ".claude", "skills");
4018
4384
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
4019
4385
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
4020
4386
  if (harnesses.includes("claude")) {
4021
4387
  for (const skill of skillNames(files)) {
4022
- const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(sourceDir, skill, "SKILL.md"), "utf8");
4023
- plan((0, import_node_path11.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
4388
+ const canonical = (0, import_node_fs14.readFileSync)((0, import_node_path12.join)(sourceDir, skill, "SKILL.md"), "utf8");
4389
+ plan((0, import_node_path12.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
4024
4390
  }
4025
4391
  rememberTarget("claude", claudeRoot);
4026
4392
  }
4027
4393
  if (harnesses.includes("cursor")) {
4028
- const cursorRule = (0, import_node_path11.join)(root, ".cursor", "rules", "odla.mdc");
4394
+ const cursorRule = (0, import_node_path12.join)(root, ".cursor", "rules", "odla.mdc");
4029
4395
  plan(cursorRule, CURSOR_RULE);
4030
4396
  rememberTarget("cursor", cursorRule);
4031
4397
  }
4032
4398
  if (harnesses.includes("agents")) {
4033
- const agentsFile = (0, import_node_path11.join)(root, "AGENTS.md");
4399
+ const agentsFile = (0, import_node_path12.join)(root, "AGENTS.md");
4034
4400
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
4035
4401
  rememberTarget("agents", agentsFile);
4036
4402
  }
4037
4403
  if (harnesses.includes("copilot")) {
4038
- const copilotFile = (0, import_node_path11.join)(root, ".github", "copilot-instructions.md");
4404
+ const copilotFile = (0, import_node_path12.join)(root, ".github", "copilot-instructions.md");
4039
4405
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
4040
4406
  rememberTarget("copilot", copilotFile);
4041
4407
  }
4042
4408
  if (harnesses.includes("gemini")) {
4043
- const geminiFile = (0, import_node_path11.join)(root, "GEMINI.md");
4409
+ const geminiFile = (0, import_node_path12.join)(root, "GEMINI.md");
4044
4410
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
4045
4411
  rememberTarget("gemini", geminiFile);
4046
4412
  }
@@ -4054,11 +4420,11 @@ function installSkill(options = {}) {
4054
4420
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
4055
4421
  continue;
4056
4422
  }
4057
- if (!(0, import_node_fs12.existsSync)(file.target)) {
4423
+ if (!(0, import_node_fs14.existsSync)(file.target)) {
4058
4424
  writtenPaths.add(file.target);
4059
4425
  continue;
4060
4426
  }
4061
- const current = (0, import_node_fs12.readFileSync)(file.target, "utf8");
4427
+ const current = (0, import_node_fs14.readFileSync)(file.target, "utf8");
4062
4428
  if (current === file.content) {
4063
4429
  unchangedPaths.add(file.target);
4064
4430
  } else if (file.managedMerge || options.force) {
@@ -4075,9 +4441,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
4075
4441
  );
4076
4442
  }
4077
4443
  for (const file of plans.values()) {
4078
- if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
4079
- (0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(file.target), { recursive: true });
4080
- (0, import_node_fs12.writeFileSync)(file.target, file.content);
4444
+ if (!(0, import_node_fs14.existsSync)(file.target) || (0, import_node_fs14.readFileSync)(file.target, "utf8") !== file.content) {
4445
+ (0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(file.target), { recursive: true });
4446
+ (0, import_node_fs14.writeFileSync)(file.target, file.content);
4081
4447
  }
4082
4448
  }
4083
4449
  const skills = skillNames(files);
@@ -4096,7 +4462,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
4096
4462
  };
4097
4463
  }
4098
4464
  function pathsUnder(root, paths) {
4099
- return [...paths].map((path) => (0, import_node_path11.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path11.sep}`) && !(0, import_node_path11.isAbsolute)(path)).sort();
4465
+ return [...paths].map((path) => (0, import_node_path12.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path12.sep}`) && !(0, import_node_path12.isAbsolute)(path)).sort();
4100
4466
  }
4101
4467
  function normalizeHarnesses(values, global) {
4102
4468
  const requested = values?.length ? values : ["claude"];
@@ -4118,9 +4484,9 @@ function normalizeHarnesses(values, global) {
4118
4484
  function managedFileContent(path, block, force, boundary) {
4119
4485
  const symlink = symlinkedComponent(boundary, path);
4120
4486
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
4121
- if (!(0, import_node_fs12.existsSync)(path)) return `${block}
4487
+ if (!(0, import_node_fs14.existsSync)(path)) return `${block}
4122
4488
  `;
4123
- const current = (0, import_node_fs12.readFileSync)(path, "utf8");
4489
+ const current = (0, import_node_fs14.readFileSync)(path, "utf8");
4124
4490
  const start = "<!-- odla-ai agent setup:start -->";
4125
4491
  const end = "<!-- odla-ai agent setup:end -->";
4126
4492
  const startAt = current.indexOf(start);
@@ -4141,15 +4507,15 @@ function managedFileContent(path, block, force, boundary) {
4141
4507
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
4142
4508
  }
4143
4509
  function symlinkedComponent(boundary, target) {
4144
- const rel = (0, import_node_path11.relative)(boundary, target);
4145
- if (rel === ".." || rel.startsWith(`..${import_node_path11.sep}`) || (0, import_node_path11.isAbsolute)(rel)) {
4510
+ const rel = (0, import_node_path12.relative)(boundary, target);
4511
+ if (rel === ".." || rel.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(rel)) {
4146
4512
  throw new Error(`agent setup target escapes its install root: ${target}`);
4147
4513
  }
4148
4514
  let current = boundary;
4149
- for (const part of rel.split(import_node_path11.sep).filter(Boolean)) {
4150
- current = (0, import_node_path11.join)(current, part);
4515
+ for (const part of rel.split(import_node_path12.sep).filter(Boolean)) {
4516
+ current = (0, import_node_path12.join)(current, part);
4151
4517
  try {
4152
- if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
4518
+ if ((0, import_node_fs14.lstatSync)(current).isSymbolicLink()) return current;
4153
4519
  } catch (error) {
4154
4520
  if (error.code !== "ENOENT") throw error;
4155
4521
  }
@@ -4160,13 +4526,13 @@ function skillNames(files) {
4160
4526
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
4161
4527
  }
4162
4528
  function listFiles(dir) {
4163
- if (!(0, import_node_fs12.existsSync)(dir)) return [];
4529
+ if (!(0, import_node_fs14.existsSync)(dir)) return [];
4164
4530
  const results = [];
4165
4531
  const walk = (current) => {
4166
- for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
4167
- const path = (0, import_node_path11.join)(current, entry.name);
4532
+ for (const entry of (0, import_node_fs14.readdirSync)(current, { withFileTypes: true })) {
4533
+ const path = (0, import_node_path12.join)(current, entry.name);
4168
4534
  if (entry.isDirectory()) walk(path);
4169
- else results.push((0, import_node_path11.relative)(dir, path));
4535
+ else results.push((0, import_node_path12.relative)(dir, path));
4170
4536
  }
4171
4537
  };
4172
4538
  walk(dir);
@@ -4361,10 +4727,14 @@ async function secretsCommand(parsed, deps) {
4361
4727
  async function projectCommand(command, parsed, deps) {
4362
4728
  if (command === "config") {
4363
4729
  const sub = parsed.positionals[1];
4364
- if (sub !== "diff" && sub !== "plan") {
4730
+ if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
4365
4731
  throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
4366
4732
  }
4367
- assertArgs(parsed, ["config", "token", "email", "open", "json"], 2);
4733
+ assertArgs(
4734
+ parsed,
4735
+ sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
4736
+ 2
4737
+ );
4368
4738
  const options = {
4369
4739
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4370
4740
  token: stringOpt(parsed.options.token),
@@ -4376,7 +4746,42 @@ async function projectCommand(command, parsed, deps) {
4376
4746
  stdout: deps.stdout
4377
4747
  };
4378
4748
  if (sub === "diff") await configDiff(options);
4379
- else await configPlan(options);
4749
+ else if (sub === "plan") await configPlan(options);
4750
+ else await configApply({
4751
+ ...options,
4752
+ planPath: requiredString(parsed.options.plan, "--plan"),
4753
+ idempotencyKey: stringOpt(parsed.options["idempotency-key"])
4754
+ });
4755
+ return true;
4756
+ }
4757
+ if (command === "operations") {
4758
+ const sub = parsed.positionals[1];
4759
+ if (sub !== "get" && sub !== "wait") {
4760
+ throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
4761
+ }
4762
+ assertArgs(
4763
+ parsed,
4764
+ sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
4765
+ 3
4766
+ );
4767
+ const operationId = parsed.positionals[2];
4768
+ if (!operationId) throw new Error(`operation id is required`);
4769
+ const options = {
4770
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4771
+ operationId,
4772
+ token: stringOpt(parsed.options.token),
4773
+ email: stringOpt(parsed.options.email),
4774
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
4775
+ json: parsed.options.json === true,
4776
+ fetch: deps.fetch,
4777
+ openApprovalUrl: deps.openUrl,
4778
+ stdout: deps.stdout,
4779
+ pollWait: deps.pollWait,
4780
+ intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
4781
+ timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
4782
+ };
4783
+ if (sub === "get") await configOperationGet(options);
4784
+ else await configOperationWait(options);
4380
4785
  return true;
4381
4786
  }
4382
4787
  if (command === "init") {
@@ -4437,9 +4842,9 @@ async function projectCommand(command, parsed, deps) {
4437
4842
  }
4438
4843
 
4439
4844
  // src/code-connect.ts
4440
- var import_node_fs14 = require("fs");
4845
+ var import_node_fs15 = require("fs");
4441
4846
  var import_node_os4 = require("os");
4442
- var import_node_path13 = require("path");
4847
+ var import_node_path14 = require("path");
4443
4848
 
4444
4849
  // ../harness/dist/chunk-QTUEF2HZ.js
4445
4850
  var HARNESS_PROTOCOL_VERSION = 1;
@@ -4449,7 +4854,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
4449
4854
  var HarnessProtocolError = class extends Error {
4450
4855
  name = "HarnessProtocolError";
4451
4856
  };
4452
- function record2(value2) {
4857
+ function record4(value2) {
4453
4858
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
4454
4859
  }
4455
4860
  function boundedText(value2, label, max) {
@@ -4466,7 +4871,7 @@ function parseAgentOutput(line) {
4466
4871
  } catch {
4467
4872
  throw new HarnessProtocolError("agent emitted invalid JSON");
4468
4873
  }
4469
- const message2 = record2(value2);
4874
+ const message2 = record4(value2);
4470
4875
  if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
4471
4876
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
4472
4877
  }
@@ -4479,7 +4884,7 @@ function parseAgentOutput(line) {
4479
4884
  };
4480
4885
  }
4481
4886
  if (message2.type === "inference.request") {
4482
- const call2 = record2(message2.call);
4887
+ const call2 = record4(message2.call);
4483
4888
  if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
4484
4889
  throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
4485
4890
  }
@@ -4491,7 +4896,7 @@ function parseAgentOutput(line) {
4491
4896
  };
4492
4897
  }
4493
4898
  if (message2.type === "tool.request") {
4494
- const input = record2(message2.input);
4899
+ const input = record4(message2.input);
4495
4900
  const tool = String(message2.tool);
4496
4901
  if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
4497
4902
  throw new HarnessProtocolError("tool.request requires a registered tool and object input");
@@ -4834,8 +5239,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
4834
5239
  const maxFiles = options.maxFiles ?? 2e4;
4835
5240
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
4836
5241
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
4837
- const entries = inventory.flatMap((record8) => {
4838
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record8);
5242
+ const entries = inventory.flatMap((record10) => {
5243
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record10);
4839
5244
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
4840
5245
  });
4841
5246
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -5083,8 +5488,8 @@ function normalize(value2) {
5083
5488
  if (Array.isArray(value2)) return value2.map(normalize);
5084
5489
  if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
5085
5490
  if (typeof value2 === "object") {
5086
- const record8 = value2;
5087
- return Object.fromEntries(Object.keys(record8).filter((key) => record8[key] !== void 0).sort().map((key) => [key, normalize(record8[key])]));
5491
+ const record10 = value2;
5492
+ return Object.fromEntries(Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, normalize(record10[key])]));
5088
5493
  }
5089
5494
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
5090
5495
  }
@@ -5163,7 +5568,7 @@ function normalizeReaders(readers) {
5163
5568
  }
5164
5569
 
5165
5570
  // ../camel/dist/code.js
5166
- var DIGEST = /^sha256:[0-9a-f]{64}$/;
5571
+ var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
5167
5572
  var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
5168
5573
  var ID = /^[A-Za-z0-9._:-]{1,160}$/;
5169
5574
  async function digestCodeVerificationReceipt(fields) {
@@ -5198,18 +5603,18 @@ async function digestCodeVerificationReceipt(fields) {
5198
5603
  return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
5199
5604
  }
5200
5605
  function validate(fields) {
5201
- if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST.test(fields.trustedBaseDigest) || !DIGEST.test(fields.patchDigest) || !DIGEST.test(fields.candidateDigest) || !DIGEST.test(fields.sourceDigest) || !DIGEST.test(fields.policyDigest) || !DIGEST.test(fields.changedTestSetDigest) || !Number.isSafeInteger(fields.changedTestCount) || fields.changedTestCount < 0 || fields.changedTestCount > 1e4 || fields.changedTestsRequireReview !== fields.changedTestCount > 0 || fields.recipes.length < 1 || fields.recipes.length > 64) {
5606
+ if (fields.schemaVersion !== 1 || typeof fields.verificationId !== "string" || !ID.test(fields.verificationId) || typeof fields.trustedBaseCommitSha !== "string" || !SHA.test(fields.trustedBaseCommitSha) || !DIGEST2.test(fields.trustedBaseDigest) || !DIGEST2.test(fields.patchDigest) || !DIGEST2.test(fields.candidateDigest) || !DIGEST2.test(fields.sourceDigest) || !DIGEST2.test(fields.policyDigest) || !DIGEST2.test(fields.changedTestSetDigest) || !Number.isSafeInteger(fields.changedTestCount) || fields.changedTestCount < 0 || fields.changedTestCount > 1e4 || fields.changedTestsRequireReview !== fields.changedTestCount > 0 || fields.recipes.length < 1 || fields.recipes.length > 64) {
5202
5607
  throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
5203
5608
  }
5204
5609
  const ids = /* @__PURE__ */ new Set();
5205
5610
  for (const recipe2 of fields.recipes) {
5206
- if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !DIGEST.test(recipe2.recipeDigest) || !["passed", "failed", "timed_out", "output_limited"].includes(recipe2.status) || !Number.isSafeInteger(recipe2.exitCode) || recipe2.exitCode < 0 || recipe2.exitCode > 255 || !Number.isSafeInteger(recipe2.durationMs) || recipe2.durationMs < 0 || recipe2.durationMs > 30 * 6e4) {
5611
+ if (typeof recipe2.recipeId !== "string" || !ID.test(recipe2.recipeId) || ids.has(recipe2.recipeId) || typeof recipe2.recipeDigest !== "string" || !DIGEST2.test(recipe2.recipeDigest) || !["passed", "failed", "timed_out", "output_limited"].includes(recipe2.status) || !Number.isSafeInteger(recipe2.exitCode) || recipe2.exitCode < 0 || recipe2.exitCode > 255 || !Number.isSafeInteger(recipe2.durationMs) || recipe2.durationMs < 0 || recipe2.durationMs > 30 * 6e4) {
5207
5612
  throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
5208
5613
  }
5209
5614
  const artifactIds = /* @__PURE__ */ new Set();
5210
5615
  if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
5211
5616
  for (const artifact of recipe2.artifacts) {
5212
- if (typeof artifact.artifactId !== "string" || !ID.test(artifact.artifactId) || artifactIds.has(artifact.artifactId) || !["verified", "missing", "invalid", "too_large"].includes(artifact.status) || artifact.bytes !== null && (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0) || artifact.digest !== null && !DIGEST.test(artifact.digest) || artifact.status === "verified" !== (artifact.bytes !== null && artifact.digest !== null) || ["missing", "invalid"].includes(artifact.status) && (artifact.bytes !== null || artifact.digest !== null) || artifact.status === "too_large" && (artifact.bytes === null || artifact.digest !== null)) {
5617
+ if (typeof artifact.artifactId !== "string" || !ID.test(artifact.artifactId) || artifactIds.has(artifact.artifactId) || !["verified", "missing", "invalid", "too_large"].includes(artifact.status) || artifact.bytes !== null && (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0) || artifact.digest !== null && !DIGEST2.test(artifact.digest) || artifact.status === "verified" !== (artifact.bytes !== null && artifact.digest !== null) || ["missing", "invalid"].includes(artifact.status) && (artifact.bytes !== null || artifact.digest !== null) || artifact.status === "too_large" && (artifact.bytes === null || artifact.digest !== null)) {
5213
5618
  throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
5214
5619
  }
5215
5620
  artifactIds.add(artifact.artifactId);
@@ -5225,7 +5630,7 @@ function validate(fields) {
5225
5630
  }
5226
5631
  }
5227
5632
  var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
5228
- var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
5633
+ var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
5229
5634
  var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
5230
5635
  var MAX_PATCH_BYTES = 256 * 1024;
5231
5636
  var MAX_STATE_BYTES = 64 * 1024;
@@ -5270,14 +5675,14 @@ function normalizeState(value2) {
5270
5675
  ]);
5271
5676
  const planCursor = state2.planCursor;
5272
5677
  const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
5273
- const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST2, 256, true);
5274
- if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !DIGEST2.test(state2.planningInputDigest) || typeof state2.buildPolicyDigest !== "string" || !DIGEST2.test(state2.buildPolicyDigest) || state2.dependencyLayerDigest !== null && (typeof state2.dependencyLayerDigest !== "string" || !DIGEST2.test(state2.dependencyLayerDigest)) || state2.verificationDigest !== null && (typeof state2.verificationDigest !== "string" || !DIGEST2.test(state2.verificationDigest)) || state2.reviewDigest !== null && (typeof state2.reviewDigest !== "string" || !DIGEST2.test(state2.reviewDigest)) || !["candidate_untrusted", "verified", "reviewed"].includes(String(state2.trustStatus)) || !Array.isArray(state2.completedEffects) || state2.completedEffects.length > 256) {
5678
+ const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
5679
+ if (planCursor !== null && (typeof planCursor !== "string" || !ID2.test(planCursor)) || typeof state2.planningInputDigest !== "string" || !DIGEST22.test(state2.planningInputDigest) || typeof state2.buildPolicyDigest !== "string" || !DIGEST22.test(state2.buildPolicyDigest) || state2.dependencyLayerDigest !== null && (typeof state2.dependencyLayerDigest !== "string" || !DIGEST22.test(state2.dependencyLayerDigest)) || state2.verificationDigest !== null && (typeof state2.verificationDigest !== "string" || !DIGEST22.test(state2.verificationDigest)) || state2.reviewDigest !== null && (typeof state2.reviewDigest !== "string" || !DIGEST22.test(state2.reviewDigest)) || !["candidate_untrusted", "verified", "reviewed"].includes(String(state2.trustStatus)) || !Array.isArray(state2.completedEffects) || state2.completedEffects.length > 256) {
5275
5680
  throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
5276
5681
  }
5277
5682
  const effects = state2.completedEffects.map((item) => {
5278
5683
  const effect = object(item, "completed effect");
5279
5684
  exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
5280
- if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !DIGEST2.test(effect.actionDigest) || typeof effect.receiptDigest !== "string" || !DIGEST2.test(effect.receiptDigest)) {
5685
+ if (typeof effect.effectId !== "string" || !ID2.test(effect.effectId) || typeof effect.actionDigest !== "string" || !DIGEST22.test(effect.actionDigest) || typeof effect.receiptDigest !== "string" || !DIGEST22.test(effect.receiptDigest)) {
5281
5686
  throw invalid2("Portable checkpoint effect receipt is malformed.");
5282
5687
  }
5283
5688
  return {
@@ -5807,7 +6212,7 @@ function createCodeRuntimeControlClient(options) {
5807
6212
  }
5808
6213
  const value2 = await response2.json().catch(() => null);
5809
6214
  if (!response2.ok) {
5810
- const problem = record3(record3(value2)?.error);
6215
+ const problem = record5(record5(value2)?.error);
5811
6216
  throw new CodeRuntimeControlError(
5812
6217
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
5813
6218
  response2.status,
@@ -5829,12 +6234,12 @@ function createCodeRuntimeControlClient(options) {
5829
6234
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
5830
6235
  ),
5831
6236
  infer: async (sessionId, inference) => {
5832
- const value2 = record3(await call2(
6237
+ const value2 = record5(await call2(
5833
6238
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
5834
6239
  inference,
5835
6240
  modelRequestTimeoutMs
5836
6241
  ));
5837
- if (!value2 || value2.requestId !== inference.requestId || !record3(value2.response) || !record3(value2.receipt)) {
6242
+ if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
5838
6243
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
5839
6244
  }
5840
6245
  return value2;
@@ -5892,12 +6297,12 @@ function validateHeartbeat(version, capabilities) {
5892
6297
  }
5893
6298
  }
5894
6299
  function parseSnapshot(value2) {
5895
- const root = record3(value2);
5896
- const host = record3(root?.host);
6300
+ const root = record5(value2);
6301
+ const host = record5(root?.host);
5897
6302
  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");
5898
6303
  const bindingIds = /* @__PURE__ */ new Set();
5899
6304
  const bindings = root.bindings.map((item) => {
5900
- const binding = record3(item);
6305
+ const binding = record5(item);
5901
6306
  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)) {
5902
6307
  throw invalid("binding");
5903
6308
  }
@@ -5907,10 +6312,10 @@ function parseSnapshot(value2) {
5907
6312
  const commandIds = /* @__PURE__ */ new Set();
5908
6313
  const commandSequences = /* @__PURE__ */ new Set();
5909
6314
  const commands = root.commands.map((item) => {
5910
- const command = record3(item);
6315
+ const command = record5(item);
5911
6316
  const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
5912
6317
  const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
5913
- if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !record3(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
6318
+ 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");
5914
6319
  commandIds.add(command.commandId);
5915
6320
  commandSequences.add(sequenceKey);
5916
6321
  return command;
@@ -5918,10 +6323,10 @@ function parseSnapshot(value2) {
5918
6323
  return { host, bindings, commands };
5919
6324
  }
5920
6325
  async function parseSource(value2) {
5921
- const snapshot = record3(record3(value2)?.snapshot);
6326
+ const snapshot = record5(record5(value2)?.snapshot);
5922
6327
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
5923
6328
  const files = snapshot.files.map((value22) => {
5924
- const file = record3(value22);
6329
+ const file = record5(value22);
5925
6330
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
5926
6331
  return { path: file.path, content: file.content };
5927
6332
  });
@@ -5930,11 +6335,11 @@ async function parseSource(value2) {
5930
6335
  const aliases = /* @__PURE__ */ new Set();
5931
6336
  const references = [];
5932
6337
  for (const item of referencesValue) {
5933
- const reference = record3(item);
6338
+ const reference = record5(item);
5934
6339
  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");
5935
6340
  aliases.add(reference.alias);
5936
6341
  const referenceFiles = reference.files.map((entry) => {
5937
- const file = record3(entry);
6342
+ const file = record5(entry);
5938
6343
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
5939
6344
  return { path: file.path, content: file.content };
5940
6345
  });
@@ -5949,18 +6354,18 @@ async function parseSource(value2) {
5949
6354
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
5950
6355
  }
5951
6356
  function parseReview(value2) {
5952
- const review = record3(record3(value2)?.review);
6357
+ const review = record5(record5(value2)?.review);
5953
6358
  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");
5954
6359
  return review;
5955
6360
  }
5956
6361
  function parseCandidate(value2) {
5957
- const candidate = record3(record3(value2)?.candidate);
6362
+ const candidate = record5(record5(value2)?.candidate);
5958
6363
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
5959
6364
  throw invalid("candidate");
5960
6365
  }
5961
6366
  return { candidateId: candidate.candidateId, status: candidate.status };
5962
6367
  }
5963
- var record3 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
6368
+ var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
5964
6369
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
5965
6370
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
5966
6371
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
@@ -7323,13 +7728,6 @@ var CodePiRuntimeEngine = class {
7323
7728
  }
7324
7729
  };
7325
7730
 
7326
- // src/version.ts
7327
- var import_node_fs13 = require("fs");
7328
- function cliVersion() {
7329
- const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
7330
- return pkg.version ?? "unknown";
7331
- }
7332
-
7333
7731
  // src/security-hosted-github.ts
7334
7732
  var import_node_child_process4 = require("child_process");
7335
7733
  var import_node_util2 = require("util");
@@ -7605,7 +8003,7 @@ var import_node_child_process6 = require("child_process");
7605
8003
  var import_node_crypto3 = require("crypto");
7606
8004
  var import_promises10 = require("fs/promises");
7607
8005
  var import_node_os3 = require("os");
7608
- var import_node_path12 = require("path");
8006
+ var import_node_path13 = require("path");
7609
8007
  var import_node_url3 = require("url");
7610
8008
 
7611
8009
  // src/code-runtime-config.ts
@@ -7691,10 +8089,10 @@ async function embeddedPiImageName() {
7691
8089
  return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
7692
8090
  }
7693
8091
  async function buildEmbeddedPiImage(engine, image, run) {
7694
- const context = await (0, import_promises10.mkdtemp)((0, import_node_path12.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
8092
+ const context = await (0, import_promises10.mkdtemp)((0, import_node_path13.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
7695
8093
  try {
7696
- await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path12.join)(context, "pi-agent.js"));
7697
- await (0, import_promises10.writeFile)((0, import_node_path12.join)(context, "Dockerfile"), [
8094
+ await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path13.join)(context, "pi-agent.js"));
8095
+ await (0, import_promises10.writeFile)((0, import_node_path13.join)(context, "Dockerfile"), [
7698
8096
  `FROM ${CODE_NODE_IMAGE}`,
7699
8097
  "COPY pi-agent.js /opt/odla/pi-agent.js",
7700
8098
  "WORKDIR /workspace",
@@ -7710,8 +8108,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
7710
8108
  // src/code-connect.ts
7711
8109
  async function codeConnect(options) {
7712
8110
  const cwd = options.cwd ?? process.cwd();
7713
- const configPath = (0, import_node_path13.resolve)(cwd, options.configPath);
7714
- const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
8111
+ const configPath = (0, import_node_path14.resolve)(cwd, options.configPath);
8112
+ const cfg = (0, import_node_fs15.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7715
8113
  const requestedAppId = options.appId?.trim();
7716
8114
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
7717
8115
  throw new Error("--app-id must be a valid odla app id");
@@ -7869,20 +8267,20 @@ async function runCodeRuntime(input) {
7869
8267
  }
7870
8268
  }
7871
8269
  function parseConnection(value2, appId, appEnv) {
7872
- const root = record4(value2);
7873
- const host = record4(root?.host);
7874
- const offer = record4(root?.offer);
7875
- const binding = record4(root?.binding);
8270
+ const root = record6(value2);
8271
+ const host = record6(root?.host);
8272
+ const offer = record6(root?.offer);
8273
+ const binding = record6(root?.binding);
7876
8274
  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)) {
7877
8275
  throw new Error("connect Code host returned an invalid response");
7878
8276
  }
7879
8277
  return root;
7880
8278
  }
7881
8279
  function apiFailure(action2, status, value2) {
7882
- const message2 = record4(record4(value2)?.error)?.message;
8280
+ const message2 = record6(record6(value2)?.error)?.message;
7883
8281
  return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
7884
8282
  }
7885
- function record4(value2) {
8283
+ function record6(value2) {
7886
8284
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7887
8285
  }
7888
8286
 
@@ -8097,8 +8495,9 @@ Usage:
8097
8495
  odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
8098
8496
  odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
8099
8497
  odla-ai doctor [--config odla.config.mjs]
8100
- odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
8101
- odla-ai config plan [--config odla.config.mjs] [--email <odla-account>] [--json]
8498
+ odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
8499
+ odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
8500
+ odla-ai operations <get|wait> <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
8102
8501
  odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
8103
8502
  odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
8104
8503
  odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
@@ -8223,8 +8622,8 @@ Commands:
8223
8622
  setup Install offline odla runbooks for common coding-agent harnesses.
8224
8623
  init Create a generic odla.config.mjs plus starter schema/rules files.
8225
8624
  doctor Validate and summarize the project config without network calls.
8226
- config Read-only diff and revision-bound plan for checked-in Registry
8227
- intent; runtime-owned fields and excluded coverage stay explicit.
8625
+ config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
8626
+ operations Inspect or wait on one exact, durable config-operation receipt.
8228
8627
  calendar Inspect, connect, or disconnect the live Google booking connection.
8229
8628
  app Archive (suspend, data retained), restore, export, import, or
8230
8629
  manage the co-owners of the app. Archiving takes every
@@ -8931,8 +9330,8 @@ async function pmAdd(ctx, entity, parsed) {
8931
9330
  emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
8932
9331
  }
8933
9332
  async function pmGet(ctx, entity, id) {
8934
- const { record: record8 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
8935
- emit2(ctx, record8, () => printRecord(ctx, entity, record8));
9333
+ const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
9334
+ emit2(ctx, record10, () => printRecord(ctx, entity, record10));
8936
9335
  }
8937
9336
  async function pmSet(ctx, entity, id, parsed) {
8938
9337
  const patch2 = collectEntityFields(entity, parsed, true);
@@ -8977,9 +9376,9 @@ async function pmHandoff(ctx, parsed) {
8977
9376
  ]);
8978
9377
  const handoff = {
8979
9378
  appId,
8980
- unmetGoals: goals.filter((record8) => record8.status !== "met"),
8981
- activeTasks: tasks.filter((record8) => record8.column !== "done"),
8982
- openBugs: bugs.filter((record8) => record8.status !== "fixed" && record8.status !== "wontfix")
9379
+ unmetGoals: goals.filter((record10) => record10.status !== "met"),
9380
+ activeTasks: tasks.filter((record10) => record10.column !== "done"),
9381
+ openBugs: bugs.filter((record10) => record10.status !== "fixed" && record10.status !== "wontfix")
8983
9382
  };
8984
9383
  const result = {
8985
9384
  ...handoff,
@@ -8998,10 +9397,10 @@ async function pmHandoff(ctx, parsed) {
8998
9397
  ]) {
8999
9398
  ctx.out.log(`${label}:`);
9000
9399
  if (!records.length) ctx.out.log("- (none)");
9001
- else for (const record8 of records) printRecord(
9400
+ else for (const record10 of records) printRecord(
9002
9401
  ctx,
9003
9402
  label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
9004
- record8
9403
+ record10
9005
9404
  );
9006
9405
  }
9007
9406
  });
@@ -9242,17 +9641,17 @@ async function platformCommand(parsed, deps = {}) {
9242
9641
  }
9243
9642
  }
9244
9643
  function isPlatformStatus(value2) {
9245
- if (!record5(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
9246
- if (!record5(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
9247
- if (!record5(value2.catalog) || !record5(value2.summary)) return false;
9644
+ if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
9645
+ if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
9646
+ if (!record7(value2.catalog) || !record7(value2.summary)) return false;
9248
9647
  return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
9249
9648
  }
9250
9649
  function apiMessage(value2) {
9251
- if (!record5(value2)) return "request failed";
9252
- const error = record5(value2.error) ? value2.error : value2;
9650
+ if (!record7(value2)) return "request failed";
9651
+ const error = record7(value2.error) ? value2.error : value2;
9253
9652
  return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
9254
9653
  }
9255
- function record5(value2) {
9654
+ function record7(value2) {
9256
9655
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
9257
9656
  }
9258
9657
 
@@ -9293,7 +9692,7 @@ function statusVerdict(reads) {
9293
9692
  severity: "degraded"
9294
9693
  });
9295
9694
  }
9296
- const performance = record6(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
9695
+ const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
9297
9696
  if (performance?.status === "unavailable") {
9298
9697
  reasons.push({
9299
9698
  source: "liveSync",
@@ -9374,7 +9773,7 @@ function statusVerdict(reads) {
9374
9773
  reasons
9375
9774
  };
9376
9775
  }
9377
- function record6(value2) {
9776
+ function record8(value2) {
9378
9777
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
9379
9778
  }
9380
9779
  function numeric2(value2) {
@@ -9402,7 +9801,7 @@ function printO11yStatus(status, out) {
9402
9801
  out.log(
9403
9802
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
9404
9803
  );
9405
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record7) : [];
9804
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
9406
9805
  const requests = routes.reduce(
9407
9806
  (total, row) => total + numeric3(row.requests),
9408
9807
  0
@@ -9414,39 +9813,39 @@ function printO11yStatus(status, out) {
9414
9813
  out.log(
9415
9814
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
9416
9815
  );
9417
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record7) : [];
9816
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
9418
9817
  out.log(
9419
9818
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
9420
9819
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
9421
9820
  ).join(", ") : "none observed"}`
9422
9821
  );
9423
9822
  out.log(liveSyncLine(status.liveSync));
9424
- const canaryDurations = record7(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
9823
+ const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
9425
9824
  out.log(
9426
9825
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
9427
9826
  );
9428
- const collectorIngest = record7(status.collector.body.ingest) ? status.collector.body.ingest : {};
9429
- const collectorStorage = record7(collectorIngest.storage) ? collectorIngest.storage : {};
9827
+ const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
9828
+ const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
9430
9829
  out.log(
9431
9830
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
9432
9831
  );
9433
- const providerMetrics = record7(status.provider.body.metrics) ? status.provider.body.metrics : {};
9434
- const providerCapacity = record7(status.provider.body.capacity) ? status.provider.body.capacity : {};
9435
- const workerMemory = record7(providerCapacity.memory) ? providerCapacity.memory : {};
9832
+ const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
9833
+ const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
9834
+ const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
9436
9835
  out.log(
9437
9836
  `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`
9438
9837
  );
9439
9838
  for (const line of providerCapacityLines(status.providerCapacity)) {
9440
9839
  out.log(line);
9441
9840
  }
9442
- const coverage = record7(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
9443
- const coverageCounts = record7(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
9444
- const coverageBudget = record7(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
9841
+ const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
9842
+ const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
9843
+ const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
9445
9844
  out.log(
9446
9845
  `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`
9447
9846
  );
9448
9847
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
9449
- const providerFreshness = record7(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
9848
+ const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
9450
9849
  out.log(
9451
9850
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
9452
9851
  );
@@ -9455,17 +9854,17 @@ function printO11yStatus(status, out) {
9455
9854
  );
9456
9855
  }
9457
9856
  function providerCapacityLines(read3) {
9458
- const resources = record7(read3.body.resources) ? read3.body.resources : {};
9459
- const durableObjects = record7(resources.durableObjects) ? resources.durableObjects : {};
9460
- const periodic = record7(durableObjects.periodic) ? durableObjects.periodic : {};
9461
- const storage = record7(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
9462
- const d1 = record7(resources.d1) ? resources.d1 : {};
9463
- const d1Activity = record7(d1.activity) ? d1.activity : {};
9464
- const d1Storage = record7(d1.storage) ? d1.storage : {};
9465
- const d1Latency = record7(d1Activity.latency) ? d1Activity.latency : {};
9466
- const r2 = record7(resources.r2) ? resources.r2 : {};
9467
- const r2Operations = record7(r2.operations) ? r2.operations : {};
9468
- const r2Storage = record7(r2.storage) ? r2.storage : {};
9857
+ const resources = record9(read3.body.resources) ? read3.body.resources : {};
9858
+ const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
9859
+ const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
9860
+ const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
9861
+ const d1 = record9(resources.d1) ? resources.d1 : {};
9862
+ const d1Activity = record9(d1.activity) ? d1.activity : {};
9863
+ const d1Storage = record9(d1.storage) ? d1.storage : {};
9864
+ const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
9865
+ const r2 = record9(resources.r2) ? resources.r2 : {};
9866
+ const r2Operations = record9(r2.operations) ? r2.operations : {};
9867
+ const r2Storage = record9(r2.storage) ? r2.storage : {};
9469
9868
  const status = String(
9470
9869
  read3.body.status ?? read3.body.error ?? "unavailable"
9471
9870
  );
@@ -9476,11 +9875,11 @@ function providerCapacityLines(read3) {
9476
9875
  ];
9477
9876
  }
9478
9877
  function liveSyncLine(read3) {
9479
- const performance = record7(read3.body.performance) ? read3.body.performance : {};
9480
- const commitToSend = record7(performance.commitToSend) ? performance.commitToSend : {};
9878
+ const performance = record9(read3.body.performance) ? read3.body.performance : {};
9879
+ const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
9481
9880
  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`;
9482
9881
  }
9483
- function record7(value2) {
9882
+ function record9(value2) {
9484
9883
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
9485
9884
  }
9486
9885
  function numeric3(value2) {
@@ -9663,7 +10062,7 @@ async function read2(url, headers, doFetch) {
9663
10062
  }
9664
10063
 
9665
10064
  // src/provision.ts
9666
- var import_apps10 = require("@odla-ai/apps");
10065
+ var import_apps12 = require("@odla-ai/apps");
9667
10066
  var import_ai3 = require("@odla-ai/ai");
9668
10067
  var import_node_process10 = __toESM(require("process"), 1);
9669
10068
 
@@ -9720,9 +10119,9 @@ async function responseText(res) {
9720
10119
  }
9721
10120
 
9722
10121
  // src/provision-credentials.ts
9723
- var import_apps9 = require("@odla-ai/apps");
10122
+ var import_apps11 = require("@odla-ai/apps");
9724
10123
  async function provisionEnvCredentials(opts) {
9725
- const tenantId = (0, import_apps9.tenantIdFor)(opts.cfg.app.id, opts.env);
10124
+ const tenantId = (0, import_apps11.tenantIdFor)(opts.cfg.app.id, opts.env);
9726
10125
  const prior = opts.credentials?.envs[opts.env];
9727
10126
  let credentials = opts.credentials;
9728
10127
  let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
@@ -9884,7 +10283,7 @@ async function provision(options) {
9884
10283
  }
9885
10284
  const doFetch = options.fetch ?? fetch;
9886
10285
  const token = await getDeveloperToken(cfg, options, doFetch, out);
9887
- const apps = (0, import_apps10.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
10286
+ const apps = (0, import_apps12.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
9888
10287
  const existing = await apps.resolveApp(cfg.app.id);
9889
10288
  if (existing) {
9890
10289
  out.log(`app: ${cfg.app.id} already exists`);
@@ -9895,7 +10294,7 @@ async function provision(options) {
9895
10294
  for (const env of cfg.envs) {
9896
10295
  await assertTenantAdminAccess(doFetch, cfg, env, token);
9897
10296
  }
9898
- const serviceOrder = (0, import_apps10.orderAppServices)(cfg.services);
10297
+ const serviceOrder = (0, import_apps12.orderAppServices)(cfg.services);
9899
10298
  for (const env of cfg.envs) {
9900
10299
  for (const service of serviceOrder) {
9901
10300
  if (service === "ai") {
@@ -9928,7 +10327,7 @@ async function provision(options) {
9928
10327
  }
9929
10328
  }
9930
10329
  for (const env of cfg.envs) {
9931
- const tenantId = (0, import_apps10.tenantIdFor)(cfg.app.id, env);
10330
+ const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
9932
10331
  credentials = await provisionEnvCredentials({
9933
10332
  cfg,
9934
10333
  env,
@@ -10013,7 +10412,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
10013
10412
  }
10014
10413
 
10015
10414
  // src/record.ts
10016
- var import_node_fs15 = require("fs");
10415
+ var import_node_fs16 = require("fs");
10017
10416
  var import_node_process11 = __toESM(require("process"), 1);
10018
10417
 
10019
10418
  // src/surface.ts
@@ -10062,7 +10461,7 @@ var COMMAND_SURFACE = {
10062
10461
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
10063
10462
  capabilities: {},
10064
10463
  code: { connect: {} },
10065
- config: { diff: {}, plan: {} },
10464
+ config: { diff: {}, plan: {}, apply: {} },
10066
10465
  context: { show: {}, list: {}, save: {}, remove: {} },
10067
10466
  // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
10068
10467
  discuss: {
@@ -10080,6 +10479,7 @@ var COMMAND_SURFACE = {
10080
10479
  help: {},
10081
10480
  init: {},
10082
10481
  o11y: { status: {} },
10482
+ operations: { get: {}, wait: {} },
10083
10483
  platform: { status: {} },
10084
10484
  pm: {
10085
10485
  ...PM_ENTITIES,
@@ -10167,14 +10567,14 @@ function recordInvocation(parsed) {
10167
10567
  options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
10168
10568
  };
10169
10569
  if (!entry.path.length) return;
10170
- (0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
10570
+ (0, import_node_fs16.appendFileSync)(file, `${JSON.stringify(entry)}
10171
10571
  `);
10172
10572
  } catch {
10173
10573
  }
10174
10574
  }
10175
10575
 
10176
10576
  // src/runbook-actions.ts
10177
- var import_node_fs16 = require("fs");
10577
+ var import_node_fs17 = require("fs");
10178
10578
 
10179
10579
  // src/runbook-requires.ts
10180
10580
  var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
@@ -10259,7 +10659,7 @@ async function bySlug(ctx, slug) {
10259
10659
  function readBody(file, inline) {
10260
10660
  if (inline !== void 0) return inline;
10261
10661
  if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
10262
- return (0, import_node_fs16.readFileSync)(file === "-" ? 0 : file, "utf8");
10662
+ return (0, import_node_fs17.readFileSync)(file === "-" ? 0 : file, "utf8");
10263
10663
  }
10264
10664
  var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
10265
10665
  async function runbookList(ctx, all, query) {
@@ -10346,8 +10746,8 @@ async function runbookRemove(ctx, slug) {
10346
10746
  }
10347
10747
 
10348
10748
  // src/runbook-import.ts
10349
- var import_node_fs17 = require("fs");
10350
- var import_node_path14 = require("path");
10749
+ var import_node_fs18 = require("fs");
10750
+ var import_node_path15 = require("path");
10351
10751
  function parseRunbook(text, slug) {
10352
10752
  let rest = text;
10353
10753
  const meta = {};
@@ -10372,12 +10772,12 @@ function parseRunbook(text, slug) {
10372
10772
  };
10373
10773
  }
10374
10774
  function readRunbookDir(dir) {
10375
- if (!(0, import_node_fs17.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
10376
- const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
10775
+ if (!(0, import_node_fs18.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
10776
+ const files = (0, import_node_fs18.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
10377
10777
  if (!files.length) throw new Error(`no .md files in ${dir}`);
10378
10778
  return files.map((file) => {
10379
- const slug = (0, import_node_path14.basename)(file, ".md");
10380
- const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(dir, file), "utf8"), slug);
10779
+ const slug = (0, import_node_path15.basename)(file, ".md");
10780
+ const parsed = parseRunbook((0, import_node_fs18.readFileSync)((0, import_node_path15.join)(dir, file), "utf8"), slug);
10381
10781
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
10382
10782
  });
10383
10783
  }
@@ -10449,8 +10849,8 @@ async function upsert(ctx, r, visibility) {
10449
10849
 
10450
10850
  // src/runbook-impact.ts
10451
10851
  var import_node_child_process7 = require("child_process");
10452
- var import_node_fs18 = require("fs");
10453
- var import_node_path15 = require("path");
10852
+ var import_node_fs19 = require("fs");
10853
+ var import_node_path16 = require("path");
10454
10854
 
10455
10855
  // src/runbook-impact-scan.ts
10456
10856
  var DECL = /^[+-]\s*export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/;
@@ -10619,10 +11019,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
10619
11019
  }
10620
11020
  function manifestLabeller(root) {
10621
11021
  return (workspace) => {
10622
- const manifest = (0, import_node_path15.join)(root, workspace, "package.json");
10623
- if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
11022
+ const manifest = (0, import_node_path16.join)(root, workspace, "package.json");
11023
+ if (!(0, import_node_fs19.existsSync)(manifest)) return void 0;
10624
11024
  try {
10625
- const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
11025
+ const name = JSON.parse((0, import_node_fs19.readFileSync)(manifest, "utf8")).name;
10626
11026
  return typeof name === "string" ? name : void 0;
10627
11027
  } catch {
10628
11028
  return void 0;
@@ -10689,7 +11089,7 @@ function report3(ctx, impacts) {
10689
11089
  async function runbookImpact(ctx, options, deps = {}) {
10690
11090
  const cwd = deps.cwd ?? process.cwd();
10691
11091
  const runGit = deps.runGit ?? gitRunner(cwd);
10692
- const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path15.join)(cwd, path), "utf8"));
11092
+ const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs19.readFileSync)((0, import_node_path16.join)(cwd, path), "utf8"));
10693
11093
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
10694
11094
  if (!surfaces.length) {
10695
11095
  return ctx.out.log(
@@ -10822,9 +11222,9 @@ async function runbookComment(ctx, slug, body) {
10822
11222
 
10823
11223
  // src/runbook-editor.ts
10824
11224
  var import_node_child_process8 = require("child_process");
10825
- var import_node_fs19 = require("fs");
11225
+ var import_node_fs20 = require("fs");
10826
11226
  var import_node_os5 = require("os");
10827
- var import_node_path16 = require("path");
11227
+ var import_node_path17 = require("path");
10828
11228
  var import_node_process12 = __toESM(require("process"), 1);
10829
11229
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
10830
11230
  function resolveEditor(env = import_node_process12.default.env) {
@@ -10850,16 +11250,16 @@ function editText(initial, slug, deps = {}) {
10850
11250
  );
10851
11251
  if (!interactive())
10852
11252
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
10853
- const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path16.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
10854
- const file = (0, import_node_path16.join)(dir, `${slug}.md`);
11253
+ const dir = (0, import_node_fs20.mkdtempSync)((0, import_node_path17.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
11254
+ const file = (0, import_node_path17.join)(dir, `${slug}.md`);
10855
11255
  try {
10856
- (0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
11256
+ (0, import_node_fs20.writeFileSync)(file, initial, { mode: 384 });
10857
11257
  const code = defaultRunOrInjected(deps)(editor, file);
10858
11258
  if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
10859
- const edited = (0, import_node_fs19.readFileSync)(file, "utf8");
11259
+ const edited = (0, import_node_fs20.readFileSync)(file, "utf8");
10860
11260
  return edited === initial ? null : edited;
10861
11261
  } finally {
10862
- (0, import_node_fs19.rmSync)(dir, { recursive: true, force: true });
11262
+ (0, import_node_fs20.rmSync)(dir, { recursive: true, force: true });
10863
11263
  }
10864
11264
  }
10865
11265
  var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
@@ -11271,7 +11671,7 @@ function hostedSeverity(value2, flag) {
11271
11671
  var import_security2 = require("@odla-ai/security");
11272
11672
 
11273
11673
  // src/security.ts
11274
- var import_node_path17 = require("path");
11674
+ var import_node_path18 = require("path");
11275
11675
  var import_security = require("@odla-ai/security");
11276
11676
  var import_node3 = require("@odla-ai/security/node");
11277
11677
  async function runHostedSecurity(options) {
@@ -11283,9 +11683,9 @@ async function runHostedSecurity(options) {
11283
11683
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
11284
11684
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
11285
11685
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
11286
- const target = (0, import_node_path17.resolve)(options.target ?? cfg?.rootDir ?? ".");
11287
- const output = (0, import_node_path17.resolve)(options.out ?? (0, import_node_path17.resolve)(target, ".odla/security/hosted"));
11288
- const outputRelative = (0, import_node_path17.relative)(target, output).split(import_node_path17.sep).join("/");
11686
+ const target = (0, import_node_path18.resolve)(options.target ?? cfg?.rootDir ?? ".");
11687
+ const output = (0, import_node_path18.resolve)(options.out ?? (0, import_node_path18.resolve)(target, ".odla/security/hosted"));
11688
+ const outputRelative = (0, import_node_path18.relative)(target, output).split(import_node_path18.sep).join("/");
11289
11689
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
11290
11690
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
11291
11691
  const tokenRequest = {
@@ -11297,7 +11697,7 @@ async function runHostedSecurity(options) {
11297
11697
  };
11298
11698
  const token = await injectedToken(options, tokenRequest);
11299
11699
  const snapshot = await (0, import_node3.snapshotDirectory)(target, {
11300
- exclude: !outputRelative.startsWith("../") && !(0, import_node_path17.isAbsolute)(outputRelative) ? [outputRelative] : []
11700
+ exclude: !outputRelative.startsWith("../") && !(0, import_node_path18.isAbsolute)(outputRelative) ? [outputRelative] : []
11301
11701
  });
11302
11702
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
11303
11703
  platform,
@@ -11315,7 +11715,7 @@ async function runHostedSecurity(options) {
11315
11715
  });
11316
11716
  const harness = (0, import_security.createSecurityHarness)({
11317
11717
  profile,
11318
- store: new import_node3.FileRunStore((0, import_node_path17.resolve)(output, "state")),
11718
+ store: new import_node3.FileRunStore((0, import_node_path18.resolve)(output, "state")),
11319
11719
  discoveryReasoner: hosted.discoveryReasoner,
11320
11720
  validationReasoner: hosted.validationReasoner,
11321
11721
  policy: {
@@ -11339,7 +11739,7 @@ async function runHostedSecurity(options) {
11339
11739
  function selectEnv(requested, declared, configPath, rootDir) {
11340
11740
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
11341
11741
  if (!env || !declared.includes(env)) {
11342
- const shown = (0, import_node_path17.relative)(rootDir, configPath) || configPath;
11742
+ const shown = (0, import_node_path18.relative)(rootDir, configPath) || configPath;
11343
11743
  throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
11344
11744
  }
11345
11745
  return env;
@@ -11368,7 +11768,7 @@ function printSummary(out, appId, env, run, report4, output) {
11368
11768
  out.log(` coverage: ${report4.coverageStatus} ${complete}/${report4.coverage.length} blocked=${report4.metrics.blockedCells} shallow=${report4.metrics.shallowCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
11369
11769
  if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
11370
11770
  out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
11371
- out.log(` report: ${(0, import_node_path17.resolve)(output, "REPORT.md")}`);
11771
+ out.log(` report: ${(0, import_node_path18.resolve)(output, "REPORT.md")}`);
11372
11772
  }
11373
11773
  function formatBudget(usage) {
11374
11774
  return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
@@ -11808,11 +12208,11 @@ async function securityStatus(parsed, dependencies) {
11808
12208
  // src/cli.ts
11809
12209
  function exitCodeFor(err) {
11810
12210
  const code = err?.code;
11811
- if (code === "handshake_pending" || code === "watch_timeout") return 75;
12211
+ if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
11812
12212
  if (code === "checkpoint_required") return 3;
11813
12213
  if (code === "remote_unavailable") return 6;
11814
12214
  if (code === "auth_failed") return 5;
11815
- if (code === "invalid_cursor") return 2;
12215
+ if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
11816
12216
  return 1;
11817
12217
  }
11818
12218
  async function runCli(argv = process.argv.slice(2), dependencies = {}) {