@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/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  CODE_BUILD_RECIPES: () => CODE_BUILD_RECIPES,
37
37
  CODE_PI_IMAGE: () => CODE_PI_IMAGE,
38
38
  COMMAND_SURFACE: () => COMMAND_SURFACE,
39
+ ConfigOperationCommandError: () => ConfigOperationCommandError,
39
40
  GOOGLE_CALENDAR_EVENTS_SCOPE: () => GOOGLE_CALENDAR_EVENTS_SCOPE,
40
41
  SYSTEM_AI_PURPOSES: () => SYSTEM_AI_PURPOSES,
41
42
  acceptedAfter: () => acceptedAfter,
@@ -47,7 +48,10 @@ __export(index_exports, {
47
48
  calendarServiceConfig: () => calendarServiceConfig,
48
49
  calendarStatus: () => calendarStatus,
49
50
  codeConnect: () => codeConnect,
51
+ configApply: () => configApply,
50
52
  configDiff: () => configDiff,
53
+ configOperationGet: () => configOperationGet,
54
+ configOperationWait: () => configOperationWait,
51
55
  configPlan: () => configPlan,
52
56
  connectGitHubSecuritySource: () => connectGitHubSecuritySource,
53
57
  describeProblem: () => describeProblem,
@@ -557,6 +561,7 @@ function audienceBoundEnvToken(token, platform) {
557
561
  var SCOPE_PURPOSE = {
558
562
  "platform:status:read": "read the platform fleet health and deployment snapshot",
559
563
  "app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
564
+ "app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
560
565
  "platform:runbook:write": "add or edit odla's operational runbooks",
561
566
  "platform:ai:policy:write": "change System AI model routing",
562
567
  "platform:ai:policy:read": "read System AI model routing",
@@ -1112,6 +1117,7 @@ function unique(values) {
1112
1117
  var DEFAULT_PLATFORM = "https://odla.ai";
1113
1118
  var DEFAULT_ENVS = ["dev"];
1114
1119
  var DEFAULT_SERVICES = ["db", "ai"];
1120
+ var configImportSerial = 0;
1115
1121
  var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
1116
1122
  async function loadProjectConfig(configPath = "odla.config.mjs") {
1117
1123
  const resolved = (0, import_node_path4.resolve)(configPath);
@@ -1319,7 +1325,8 @@ function validId2(value2) {
1319
1325
  }
1320
1326
  async function loadConfigModule(path) {
1321
1327
  if (path.endsWith(".json")) return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
1322
- const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?t=${Date.now()}`);
1328
+ const nonce = `${Date.now()}-${configImportSerial++}`;
1329
+ const mod = await import(`${(0, import_node_url.pathToFileURL)(path).href}?reload=${nonce}`);
1323
1330
  const value2 = mod.default ?? mod.config;
1324
1331
  if (typeof value2 === "function") return await value2();
1325
1332
  return value2;
@@ -2703,7 +2710,8 @@ var CAPABILITIES = {
2703
2710
  "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",
2704
2711
  "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",
2705
2712
  "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",
2706
- "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",
2713
+ "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",
2714
+ "inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
2707
2715
  "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
2708
2716
  "run app-attributed hosted security discovery and independent validation without provider keys",
2709
2717
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
@@ -2750,10 +2758,31 @@ function printGroup(out, heading, items) {
2750
2758
  out.log("");
2751
2759
  }
2752
2760
 
2753
- // src/config-reconcile-command.ts
2761
+ // src/config-operation-command.ts
2754
2762
  var import_apps6 = require("@odla-ai/apps");
2755
2763
  var import_node_path7 = require("path");
2756
2764
 
2765
+ // src/version.ts
2766
+ var import_node_fs9 = require("fs");
2767
+ function cliVersion() {
2768
+ const pkg = JSON.parse((0, import_node_fs9.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
2769
+ return pkg.version ?? "unknown";
2770
+ }
2771
+
2772
+ // src/config-operation-error.ts
2773
+ var ConfigOperationCommandError = class extends Error {
2774
+ constructor(message2, code) {
2775
+ super(message2);
2776
+ this.code = code;
2777
+ this.name = "ConfigOperationCommandError";
2778
+ }
2779
+ code;
2780
+ };
2781
+
2782
+ // src/config-operation-validate.ts
2783
+ var import_apps3 = require("@odla-ai/apps");
2784
+ var import_node_fs10 = require("fs");
2785
+
2757
2786
  // src/config-reconcile-digest.ts
2758
2787
  var import_node_crypto = require("crypto");
2759
2788
  function canonicalJson(value2) {
@@ -2763,27 +2792,160 @@ function configDigest(value2) {
2763
2792
  return `sha256:${(0, import_node_crypto.createHash)("sha256").update(canonicalJson(value2)).digest("hex")}`;
2764
2793
  }
2765
2794
  function canonicalValue(value2) {
2795
+ if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
2796
+ if (typeof value2 === "number") {
2797
+ if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
2798
+ return value2;
2799
+ }
2766
2800
  if (Array.isArray(value2)) return value2.map(canonicalValue);
2767
2801
  if (value2 && typeof value2 === "object") {
2802
+ const record10 = value2;
2768
2803
  return Object.fromEntries(
2769
- Object.entries(value2).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalValue(entry)])
2804
+ Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, canonicalValue(record10[key])])
2770
2805
  );
2771
2806
  }
2772
- return value2;
2807
+ throw new TypeError("canonical JSON rejects unsupported values");
2808
+ }
2809
+
2810
+ // src/config-operation-validate.ts
2811
+ var DIGEST = /^sha256:[0-9a-f]{64}$/;
2812
+ var REVISION = /^registry:[1-9][0-9]*$/;
2813
+ 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;
2814
+ var ACTION_ID = /^action-[0-9a-f]{16}$/;
2815
+ var ENV = /^[a-z0-9]{2,12}$/;
2816
+ var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
2817
+ function readPlan(path) {
2818
+ let value2;
2819
+ try {
2820
+ const raw = (0, import_node_fs10.readFileSync)(path, "utf8");
2821
+ if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
2822
+ value2 = JSON.parse(raw);
2823
+ } catch (error) {
2824
+ throw new ConfigOperationCommandError(
2825
+ `cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
2826
+ "invalid_plan"
2827
+ );
2828
+ }
2829
+ if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
2830
+ if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
2831
+ invalidPlan("plan scope is invalid");
2832
+ }
2833
+ if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
2834
+ invalidPlan("plan content revisions are invalid");
2835
+ }
2836
+ if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
2837
+ invalidPlan("plan Registry revision or digest is invalid");
2838
+ }
2839
+ if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
2840
+ invalidPlan("plan actions must contain 1-32 entries");
2841
+ }
2842
+ assertActions(value2.actions);
2843
+ const plan = value2;
2844
+ const digest = configDigest({
2845
+ schemaVersion: plan.schemaVersion,
2846
+ desiredRevision: plan.desiredRevision,
2847
+ observedRevision: plan.observedRevision,
2848
+ registryRevision: plan.registryRevision,
2849
+ actions: plan.actions
2850
+ });
2851
+ if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
2852
+ return plan;
2853
+ }
2854
+ function assertPlanContext(plan, appId, platformUrl) {
2855
+ if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
2856
+ throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
2857
+ }
2858
+ }
2859
+ function verifyReceipt(receipt, appId, operationId) {
2860
+ if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
2861
+ throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
2862
+ }
2863
+ if (receipt.receiptDigest) {
2864
+ const { receiptDigest, ...fields } = receipt;
2865
+ if (configDigest(fields) !== receiptDigest) {
2866
+ throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
2867
+ }
2868
+ } else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
2869
+ throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
2870
+ }
2871
+ }
2872
+ function assertOperationId(value2) {
2873
+ if (!OPERATION_ID.test(value2)) {
2874
+ throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
2875
+ }
2876
+ }
2877
+ function assertActions(actions) {
2878
+ const ids = /* @__PURE__ */ new Set();
2879
+ for (const action2 of actions) {
2880
+ if (!record2(action2)) invalidPlan("every plan action must be an object");
2881
+ const id = String(action2.id ?? "");
2882
+ if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
2883
+ ids.add(id);
2884
+ 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))) {
2885
+ invalidPlan("plan action metadata is invalid");
2886
+ }
2887
+ if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
2888
+ assertConditionalAction(action2);
2889
+ }
2890
+ }
2891
+ }
2892
+ function assertConditionalAction(action2) {
2893
+ if (action2.kind === "rename_app") {
2894
+ 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");
2895
+ return;
2896
+ }
2897
+ if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
2898
+ if (action2.kind === "set_link") {
2899
+ if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
2900
+ return;
2901
+ }
2902
+ if (!action2.service || !SERVICE.test(action2.service) || !(0, import_apps3.appServiceDefinition)(action2.service)) {
2903
+ invalidPlan("service action names an unknown service");
2904
+ }
2905
+ const base = `environments.${action2.env}.services.${action2.service}`;
2906
+ if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
2907
+ invalidPlan("service action path is invalid");
2908
+ }
2909
+ if (action2.applySupport !== "provision" || !record2(action2.after)) {
2910
+ invalidPlan("service action payload is invalid");
2911
+ }
2912
+ if (action2.kind === "enable_service") {
2913
+ if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
2914
+ invalidPlan("service enable action is invalid");
2915
+ }
2916
+ } else if (!record2(action2.before)) {
2917
+ invalidPlan("service configure action is invalid");
2918
+ }
2919
+ }
2920
+ function linkState(value2) {
2921
+ if (value2 === null) return true;
2922
+ if (typeof value2 !== "string") return false;
2923
+ try {
2924
+ const url = new URL(value2.trim());
2925
+ return url.protocol === "http:" || url.protocol === "https:";
2926
+ } catch {
2927
+ return false;
2928
+ }
2929
+ }
2930
+ function invalidPlan(message2) {
2931
+ throw new ConfigOperationCommandError(message2, "invalid_plan");
2932
+ }
2933
+ function record2(value2) {
2934
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
2773
2935
  }
2774
2936
 
2775
2937
  // src/config-reconcile-desired.ts
2776
- var import_apps4 = require("@odla-ai/apps");
2938
+ var import_apps5 = require("@odla-ai/apps");
2777
2939
 
2778
2940
  // src/provision-helpers.ts
2779
2941
  var import_ai = require("@odla-ai/ai");
2780
- var import_apps3 = require("@odla-ai/apps");
2942
+ var import_apps4 = require("@odla-ai/apps");
2781
2943
  function defaultSecretName(provider) {
2782
2944
  const names = import_ai.DEFAULT_SECRET_NAMES;
2783
2945
  return names[provider] ?? `${provider}_api_key`;
2784
2946
  }
2785
2947
  async function assertTenantAdminAccess(doFetch, cfg, env, token) {
2786
- const tenantId = (0, import_apps3.tenantIdFor)(cfg.app.id, env);
2948
+ const tenantId = (0, import_apps4.tenantIdFor)(cfg.app.id, env);
2787
2949
  const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/entitlements`, {
2788
2950
  headers: { authorization: `Bearer ${token}` }
2789
2951
  });
@@ -2825,7 +2987,7 @@ async function safeText3(res) {
2825
2987
  // src/config-reconcile-desired.ts
2826
2988
  function desiredRegistryState(cfg) {
2827
2989
  const environments = {};
2828
- const services = (0, import_apps4.orderAppServices)(cfg.services);
2990
+ const services = (0, import_apps5.orderAppServices)(cfg.services);
2829
2991
  for (const env of cfg.envs) {
2830
2992
  const desiredServices = {};
2831
2993
  for (const service of services) {
@@ -2865,8 +3027,208 @@ function managedServiceConfig(cfg, env, service) {
2865
3027
  return {};
2866
3028
  }
2867
3029
 
3030
+ // src/config-reconcile-support.ts
3031
+ var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
3032
+ "rename_app",
3033
+ "enable_service",
3034
+ "configure_service",
3035
+ "set_link"
3036
+ ]);
3037
+ function configApplySupport(reconciliation) {
3038
+ if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
3039
+ return {
3040
+ supported: false,
3041
+ checkpointRequired: false,
3042
+ reason: "the app must already exist in a revision-aware Registry"
3043
+ };
3044
+ }
3045
+ if (!reconciliation.actions.length) {
3046
+ return {
3047
+ supported: false,
3048
+ checkpointRequired: false,
3049
+ reason: "there are no managed changes to apply"
3050
+ };
3051
+ }
3052
+ const blocked = reconciliation.actions.filter(
3053
+ (action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
3054
+ );
3055
+ if (blocked.length) {
3056
+ return {
3057
+ supported: false,
3058
+ checkpointRequired: blocked.some(
3059
+ (action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
3060
+ ),
3061
+ reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
3062
+ };
3063
+ }
3064
+ return {
3065
+ supported: true,
3066
+ checkpointRequired: false,
3067
+ reason: "all actions are low-risk, checkpoint-free Registry changes"
3068
+ };
3069
+ }
3070
+
3071
+ // src/config-operation-command.ts
3072
+ var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
3073
+ var DEFAULT_WAIT_SECONDS = 60;
3074
+ var DEFAULT_INTERVAL_SECONDS = 2;
3075
+ async function configApply(options) {
3076
+ const plan = readPlan(options.planPath);
3077
+ const cfg = await loadProjectConfig(options.configPath);
3078
+ assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
3079
+ const support = configApplySupport(plan);
3080
+ if (!support.supported) {
3081
+ throw new ConfigOperationCommandError(
3082
+ support.reason,
3083
+ support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
3084
+ );
3085
+ }
3086
+ if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
3087
+ throw new ConfigOperationCommandError(
3088
+ "project config changed after this plan was frozen; generate and review a fresh plan",
3089
+ "checkpoint_required"
3090
+ );
3091
+ }
3092
+ const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
3093
+ if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
3094
+ throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
3095
+ }
3096
+ const client = await operationClient(cfg, options, "apply");
3097
+ const request2 = {
3098
+ schemaVersion: "odla.config-operation-request/v1",
3099
+ expectedRevision: plan.registryRevision,
3100
+ desiredRevision: plan.desiredRevision,
3101
+ observedRevision: plan.observedRevision,
3102
+ planDigest: plan.planDigest,
3103
+ idempotencyKey,
3104
+ source: { kind: "cli", version: cliVersion() },
3105
+ actions: plan.actions
3106
+ };
3107
+ let receipt;
3108
+ try {
3109
+ receipt = await client.applyConfigOperation(cfg.app.id, request2);
3110
+ } catch (error) {
3111
+ const retained = retainedReceipt(error);
3112
+ if (retained) {
3113
+ verifyReceipt(retained, cfg.app.id);
3114
+ printReceipt(retained, options);
3115
+ throw failureForReceipt(retained);
3116
+ }
3117
+ throw normalizeRequestError(error);
3118
+ }
3119
+ verifyReceipt(receipt, cfg.app.id);
3120
+ printReceipt(receipt, options);
3121
+ assertApplyCompleted(receipt);
3122
+ return receipt;
3123
+ }
3124
+ async function configOperationGet(options) {
3125
+ assertOperationId(options.operationId);
3126
+ const cfg = await loadProjectConfig(options.configPath);
3127
+ const client = await operationClient(cfg, options, "read");
3128
+ const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
3129
+ throw normalizeRequestError(error);
3130
+ });
3131
+ if (!receipt) {
3132
+ throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
3133
+ }
3134
+ verifyReceipt(receipt, cfg.app.id, options.operationId);
3135
+ printReceipt(receipt, options);
3136
+ return receipt;
3137
+ }
3138
+ async function configOperationWait(options) {
3139
+ assertOperationId(options.operationId);
3140
+ const cfg = await loadProjectConfig(options.configPath);
3141
+ const client = await operationClient(cfg, options, "wait");
3142
+ const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
3143
+ const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
3144
+ const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
3145
+ const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
3146
+ let receipt = null;
3147
+ for (; ; ) {
3148
+ receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
3149
+ throw normalizeRequestError(error);
3150
+ });
3151
+ if (!receipt) {
3152
+ throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
3153
+ }
3154
+ verifyReceipt(receipt, cfg.app.id, options.operationId);
3155
+ if (receipt.state !== "running") break;
3156
+ if (now() >= deadline) {
3157
+ printReceipt(receipt, options);
3158
+ throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
3159
+ }
3160
+ await wait2(Math.min(interval, Math.max(0, deadline - now())));
3161
+ }
3162
+ printReceipt(receipt, options);
3163
+ if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
3164
+ return receipt;
3165
+ }
3166
+ async function operationClient(cfg, options, purpose) {
3167
+ const doFetch = options.fetch ?? fetch;
3168
+ const out = options.stdout ?? console;
3169
+ const token = await resolveAdminPlatformToken({
3170
+ platform: cfg.platformUrl,
3171
+ scope: "app:config:write",
3172
+ token: options.token,
3173
+ tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3174
+ rootDir: cfg.rootDir,
3175
+ email: options.email,
3176
+ open: options.open,
3177
+ fetch: doFetch,
3178
+ stdout: out,
3179
+ openApprovalUrl: options.openApprovalUrl,
3180
+ label: `odla CLI (${cfg.app.id} config operation ${purpose})`
3181
+ });
3182
+ return (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
3183
+ }
3184
+ function printReceipt(receipt, options) {
3185
+ const out = options.stdout ?? console;
3186
+ if (options.json) out.log(JSON.stringify(receipt, null, 2));
3187
+ else {
3188
+ out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
3189
+ out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
3190
+ for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
3191
+ out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
3192
+ out.log(`studio: ${receipt.studioUrl}`);
3193
+ }
3194
+ }
3195
+ function assertApplyCompleted(receipt) {
3196
+ if (receipt.state === "succeeded") return;
3197
+ throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
3198
+ }
3199
+ function failureForReceipt(receipt) {
3200
+ if (receipt.state === "conflict") return new ConfigOperationCommandError(
3201
+ receipt.error?.message ?? "config operation conflicted with current Registry state",
3202
+ "checkpoint_required"
3203
+ );
3204
+ if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
3205
+ return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
3206
+ }
3207
+ function retainedReceipt(error) {
3208
+ if (!(error instanceof import_apps6.AppsError) || !record3(error.details)) return null;
3209
+ return record3(error.details.operation) ? error.details.operation : null;
3210
+ }
3211
+ function normalizeRequestError(error) {
3212
+ if (!(error instanceof import_apps6.AppsError)) return error instanceof Error ? error : new Error(String(error));
3213
+ if (error.status === 401 || error.status === 403) {
3214
+ return new ConfigOperationCommandError(error.message, "auth_failed");
3215
+ }
3216
+ if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
3217
+ if (error.status === 429 || error.status >= 500) {
3218
+ return new ConfigOperationCommandError(error.message, "remote_unavailable");
3219
+ }
3220
+ return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
3221
+ }
3222
+ function record3(value2) {
3223
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
3224
+ }
3225
+
3226
+ // src/config-reconcile-command.ts
3227
+ var import_apps8 = require("@odla-ai/apps");
3228
+ var import_node_path8 = require("path");
3229
+
2868
3230
  // src/config-reconcile.ts
2869
- var import_apps5 = require("@odla-ai/apps");
3231
+ var import_apps7 = require("@odla-ai/apps");
2870
3232
 
2871
3233
  // src/config-reconcile-values.ts
2872
3234
  function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
@@ -2967,6 +3329,7 @@ function reconcileConfig(input) {
2967
3329
  sources: { desired: desiredSource, observed: observedSource },
2968
3330
  desiredRevision,
2969
3331
  observedRevision,
3332
+ registryRevision: input.observed?.configRevision ?? null,
2970
3333
  status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
2971
3334
  summary: {
2972
3335
  differences: different,
@@ -3021,7 +3384,7 @@ function compareEnvironments(desired, observed, desiredSource, observedSource, a
3021
3384
  function compareServices(env, desired, observed, desiredSource, observedSource, add) {
3022
3385
  const wanted = desired.environments[env]?.services ?? {};
3023
3386
  const live = observed?.environments[env] ?? {};
3024
- const knownOrder = (0, import_apps5.orderAppServices)((0, import_apps5.appServiceIds)());
3387
+ const knownOrder = (0, import_apps7.orderAppServices)((0, import_apps7.appServiceIds)());
3025
3388
  const services = [.../* @__PURE__ */ new Set([...knownOrder, ...Object.keys(wanted), ...Object.keys(live)])];
3026
3389
  for (const service of services) {
3027
3390
  const next = wanted[service];
@@ -3132,20 +3495,19 @@ async function configDiff(options) {
3132
3495
  }
3133
3496
  async function configPlan(options) {
3134
3497
  const reconciliation = await inspectConfig(options);
3498
+ const apply = configApplySupport(reconciliation);
3135
3499
  const planDigest = configDigest({
3136
- schemaVersion: "odla.config-plan/v1",
3500
+ schemaVersion: "odla.config-plan/v2",
3137
3501
  desiredRevision: reconciliation.desiredRevision,
3138
3502
  observedRevision: reconciliation.observedRevision,
3503
+ registryRevision: reconciliation.registryRevision,
3139
3504
  actions: reconciliation.actions
3140
3505
  });
3141
3506
  const document2 = {
3142
- schemaVersion: "odla.config-plan/v1",
3507
+ schemaVersion: "odla.config-plan/v2",
3143
3508
  ...reconciliation,
3144
3509
  planDigest,
3145
- apply: {
3146
- supported: false,
3147
- reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
3148
- },
3510
+ apply,
3149
3511
  nextActions: planNextActions(reconciliation, options.configPath)
3150
3512
  };
3151
3513
  printPlan2(document2, options);
@@ -3159,7 +3521,7 @@ async function inspectConfig(options) {
3159
3521
  platform: cfg.platformUrl,
3160
3522
  scope: "app:config:read",
3161
3523
  token: options.token,
3162
- tokenFile: (0, import_node_path7.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3524
+ tokenFile: (0, import_node_path8.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
3163
3525
  rootDir: cfg.rootDir,
3164
3526
  email: options.email,
3165
3527
  open: options.open,
@@ -3168,7 +3530,7 @@ async function inspectConfig(options) {
3168
3530
  openApprovalUrl: options.openApprovalUrl,
3169
3531
  label: `odla CLI (${cfg.app.id} config read)`
3170
3532
  });
3171
- const client = (0, import_apps6.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
3533
+ const client = (0, import_apps8.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
3172
3534
  const observed = await client.resolveApp(cfg.app.id);
3173
3535
  return reconcileConfig({
3174
3536
  desired: desiredRegistryState(cfg),
@@ -3204,7 +3566,7 @@ function printPlan2(document2, options) {
3204
3566
  }
3205
3567
  if (!document2.actions.length) out.log(" no managed changes");
3206
3568
  out.log(`plan digest: ${document2.planDigest}`);
3207
- out.log(`apply: unsupported \u2014 ${document2.apply.reason}`);
3569
+ out.log(`apply: ${document2.apply.supported ? "supported" : "blocked"} \u2014 ${document2.apply.reason}`);
3208
3570
  printNext(out, document2.nextActions);
3209
3571
  }
3210
3572
  function printHeader(out, kind, document2) {
@@ -3212,6 +3574,7 @@ function printHeader(out, kind, document2) {
3212
3574
  out.log(`platform: ${document2.scope.platformUrl}`);
3213
3575
  out.log(`desired: ${document2.desiredRevision}`);
3214
3576
  out.log(`observed: ${document2.observedRevision ?? "absent"}`);
3577
+ out.log(`registry: ${document2.registryRevision ?? "absent"}`);
3215
3578
  out.log(
3216
3579
  `summary: ${document2.summary.differences} different, ${document2.summary.unmanaged} unmanaged, ${document2.summary.actions} planned actions`
3217
3580
  );
@@ -3244,6 +3607,13 @@ function diffNextActions(reconciliation, configPath) {
3244
3607
  }
3245
3608
  function planNextActions(reconciliation, configPath) {
3246
3609
  const next = [];
3610
+ if (configApplySupport(reconciliation).supported) {
3611
+ next.push({
3612
+ code: "apply_frozen_plan",
3613
+ command: "odla-ai config apply --plan <saved-plan.json> --json",
3614
+ description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
3615
+ });
3616
+ }
3247
3617
  if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
3248
3618
  next.push({
3249
3619
  code: "review_provision",
@@ -3279,13 +3649,13 @@ function quoteArg2(value2) {
3279
3649
 
3280
3650
  // src/doctor-checks.ts
3281
3651
  var import_node_child_process3 = require("child_process");
3282
- var import_node_fs10 = require("fs");
3283
- var import_node_path9 = require("path");
3652
+ var import_node_fs12 = require("fs");
3653
+ var import_node_path10 = require("path");
3284
3654
 
3285
3655
  // src/wrangler.ts
3286
3656
  var import_node_child_process2 = require("child_process");
3287
- var import_node_fs9 = require("fs");
3288
- var import_node_path8 = require("path");
3657
+ var import_node_fs11 = require("fs");
3658
+ var import_node_path9 = require("path");
3289
3659
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
3290
3660
  const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
3291
3661
  let stdout = "";
@@ -3299,15 +3669,15 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
3299
3669
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
3300
3670
  function findWranglerConfig(rootDir) {
3301
3671
  for (const name of WRANGLER_CONFIG_FILES) {
3302
- const path = (0, import_node_path8.join)(rootDir, name);
3303
- if ((0, import_node_fs9.existsSync)(path)) return path;
3672
+ const path = (0, import_node_path9.join)(rootDir, name);
3673
+ if ((0, import_node_fs11.existsSync)(path)) return path;
3304
3674
  }
3305
3675
  return null;
3306
3676
  }
3307
3677
  function readWranglerConfig(path) {
3308
3678
  if (path.endsWith(".toml")) return null;
3309
3679
  try {
3310
- return JSON.parse(stripJsonComments((0, import_node_fs9.readFileSync)(path, "utf8")));
3680
+ return JSON.parse(stripJsonComments((0, import_node_fs11.readFileSync)(path, "utf8")));
3311
3681
  } catch {
3312
3682
  return null;
3313
3683
  }
@@ -3405,10 +3775,10 @@ function wranglerWarnings(rootDir) {
3405
3775
  for (const { label, block } of blocks) {
3406
3776
  const assets = block.assets;
3407
3777
  if (assets?.directory) {
3408
- const dir = (0, import_node_path9.resolve)(rootDir, assets.directory);
3409
- if (dir === (0, import_node_path9.resolve)(rootDir)) {
3778
+ const dir = (0, import_node_path10.resolve)(rootDir, assets.directory);
3779
+ if (dir === (0, import_node_path10.resolve)(rootDir)) {
3410
3780
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
3411
- } else if ((0, import_node_fs10.existsSync)((0, import_node_path9.join)(dir, "node_modules"))) {
3781
+ } else if ((0, import_node_fs12.existsSync)((0, import_node_path10.join)(dir, "node_modules"))) {
3412
3782
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
3413
3783
  }
3414
3784
  }
@@ -3443,13 +3813,13 @@ function o11yProjectWarnings(rootDir) {
3443
3813
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
3444
3814
  return warnings;
3445
3815
  }
3446
- const main = typeof config.main === "string" ? (0, import_node_path9.resolve)(rootDir, config.main) : null;
3447
- if (!main || !(0, import_node_fs10.existsSync)(main)) {
3816
+ const main = typeof config.main === "string" ? (0, import_node_path10.resolve)(rootDir, config.main) : null;
3817
+ if (!main || !(0, import_node_fs12.existsSync)(main)) {
3448
3818
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
3449
3819
  } else {
3450
3820
  let source = "";
3451
3821
  try {
3452
- source = (0, import_node_fs10.readFileSync)(main, "utf8");
3822
+ source = (0, import_node_fs12.readFileSync)(main, "utf8");
3453
3823
  } catch {
3454
3824
  }
3455
3825
  if (!/\bwithObservability\b/.test(source)) {
@@ -3473,7 +3843,7 @@ function calendarProjectWarnings(rootDir) {
3473
3843
  }
3474
3844
  function readPackageJson(rootDir) {
3475
3845
  try {
3476
- return JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path9.join)(rootDir, "package.json"), "utf8"));
3846
+ return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(rootDir, "package.json"), "utf8"));
3477
3847
  } catch {
3478
3848
  return null;
3479
3849
  }
@@ -3700,14 +4070,14 @@ function harnessOption(value2, flag) {
3700
4070
  }
3701
4071
 
3702
4072
  // src/init.ts
3703
- var import_node_fs11 = require("fs");
3704
- var import_node_path10 = require("path");
3705
- var import_apps7 = require("@odla-ai/apps");
4073
+ var import_node_fs13 = require("fs");
4074
+ var import_node_path11 = require("path");
4075
+ var import_apps9 = require("@odla-ai/apps");
3706
4076
  function initProject(options) {
3707
4077
  const out = options.stdout ?? console;
3708
- const rootDir = (0, import_node_path10.resolve)(options.rootDir ?? process.cwd());
3709
- const configPath = (0, import_node_path10.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
3710
- if ((0, import_node_fs11.existsSync)(configPath) && !options.force) {
4078
+ const rootDir = (0, import_node_path11.resolve)(options.rootDir ?? process.cwd());
4079
+ const configPath = (0, import_node_path11.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
4080
+ if ((0, import_node_fs13.existsSync)(configPath) && !options.force) {
3711
4081
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
3712
4082
  }
3713
4083
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -3716,27 +4086,27 @@ function initProject(options) {
3716
4086
  const envs = options.envs?.length ? options.envs : ["dev"];
3717
4087
  const services = options.services?.length ? options.services : ["db", "ai"];
3718
4088
  for (const service of services) {
3719
- const definition = (0, import_apps7.appServiceDefinition)(service);
3720
- if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps7.appServiceIds)().join(", ")})`);
4089
+ const definition = (0, import_apps9.appServiceDefinition)(service);
4090
+ if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${(0, import_apps9.appServiceIds)().join(", ")})`);
3721
4091
  for (const dependency of definition.requires) {
3722
4092
  if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
3723
4093
  }
3724
4094
  }
3725
4095
  const aiProvider = options.aiProvider ?? "anthropic";
3726
- (0, import_node_fs11.mkdirSync)((0, import_node_path10.dirname)(configPath), { recursive: true });
3727
- (0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, "src/odla"), { recursive: true });
3728
- (0, import_node_fs11.mkdirSync)((0, import_node_path10.resolve)(rootDir, ".odla"), { recursive: true });
3729
- (0, import_node_fs11.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
3730
- writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
3731
- writeIfMissing((0, import_node_path10.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
4096
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.dirname)(configPath), { recursive: true });
4097
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, "src/odla"), { recursive: true });
4098
+ (0, import_node_fs13.mkdirSync)((0, import_node_path11.resolve)(rootDir, ".odla"), { recursive: true });
4099
+ (0, import_node_fs13.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
4100
+ writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
4101
+ writeIfMissing((0, import_node_path11.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
3732
4102
  ensureGitignore(rootDir);
3733
4103
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
3734
4104
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
3735
4105
  out.log("updated .gitignore for local odla credentials");
3736
4106
  }
3737
4107
  function writeIfMissing(path, text) {
3738
- if ((0, import_node_fs11.existsSync)(path)) return;
3739
- (0, import_node_fs11.writeFileSync)(path, text);
4108
+ if ((0, import_node_fs13.existsSync)(path)) return;
4109
+ (0, import_node_fs13.writeFileSync)(path, text);
3740
4110
  }
3741
4111
  function configTemplate(input) {
3742
4112
  const calendar = input.services.includes("calendar") ? ` calendar: {
@@ -3911,7 +4281,7 @@ function assertWranglerConfig(cfg) {
3911
4281
 
3912
4282
  // src/secrets-set.ts
3913
4283
  var import_ai2 = require("@odla-ai/ai");
3914
- var import_apps8 = require("@odla-ai/apps");
4284
+ var import_apps10 = require("@odla-ai/apps");
3915
4285
  var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
3916
4286
  async function secretsSet(options) {
3917
4287
  const name = (options.name ?? "").trim();
@@ -3963,13 +4333,13 @@ async function resolveVaultWrite(options) {
3963
4333
  throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
3964
4334
  }
3965
4335
  const value2 = await secretInputValue(options, "secret");
3966
- return { cfg, tenantId: (0, import_apps8.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
4336
+ return { cfg, tenantId: (0, import_apps10.tenantIdFor)(cfg.app.id, options.env), value: value2, doFetch, out };
3967
4337
  }
3968
4338
 
3969
4339
  // src/skill.ts
3970
- var import_node_fs12 = require("fs");
4340
+ var import_node_fs14 = require("fs");
3971
4341
  var import_node_os2 = require("os");
3972
- var import_node_path11 = require("path");
4342
+ var import_node_path12 = require("path");
3973
4343
  var import_node_url2 = require("url");
3974
4344
 
3975
4345
  // src/skill-adapters.ts
@@ -4048,8 +4418,8 @@ function installSkill(options = {}) {
4048
4418
  const files = listFiles(sourceDir);
4049
4419
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
4050
4420
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
4051
- const root = (0, import_node_path11.resolve)(options.dir ?? process.cwd());
4052
- const home = (0, import_node_path11.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
4421
+ const root = (0, import_node_path12.resolve)(options.dir ?? process.cwd());
4422
+ const home = (0, import_node_path12.resolve)(options.homeDir ?? (0, import_node_os2.homedir)());
4053
4423
  const plans = /* @__PURE__ */ new Map();
4054
4424
  const targets = /* @__PURE__ */ new Map();
4055
4425
  const rememberTarget = (harness, target) => {
@@ -4063,48 +4433,48 @@ function installSkill(options = {}) {
4063
4433
  plans.set(target, { target, content: content2, boundary, managedMerge });
4064
4434
  };
4065
4435
  const planSkillTree = (targetDir2, boundary = root) => {
4066
- 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);
4436
+ 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);
4067
4437
  };
4068
4438
  let targetDir;
4069
4439
  if (options.global) {
4070
- const claudeRoot = (0, import_node_path11.join)(home, ".claude", "skills");
4071
- const codexRoot = (0, import_node_path11.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path11.join)(home, ".codex"), "skills");
4440
+ const claudeRoot = (0, import_node_path12.join)(home, ".claude", "skills");
4441
+ const codexRoot = (0, import_node_path12.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path12.join)(home, ".codex"), "skills");
4072
4442
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
4073
4443
  for (const harness of harnesses) {
4074
4444
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
4075
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path11.dirname)((0, import_node_path11.dirname)(codexRoot)));
4445
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path12.dirname)((0, import_node_path12.dirname)(codexRoot)));
4076
4446
  rememberTarget(harness, skillRoot);
4077
4447
  }
4078
4448
  } else {
4079
- const sharedRoot = (0, import_node_path11.join)(root, ".agents", "skills");
4449
+ const sharedRoot = (0, import_node_path12.join)(root, ".agents", "skills");
4080
4450
  planSkillTree(sharedRoot);
4081
- const claudeRoot = (0, import_node_path11.join)(root, ".claude", "skills");
4451
+ const claudeRoot = (0, import_node_path12.join)(root, ".claude", "skills");
4082
4452
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
4083
4453
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
4084
4454
  if (harnesses.includes("claude")) {
4085
4455
  for (const skill of skillNames(files)) {
4086
- const canonical = (0, import_node_fs12.readFileSync)((0, import_node_path11.join)(sourceDir, skill, "SKILL.md"), "utf8");
4087
- plan((0, import_node_path11.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
4456
+ const canonical = (0, import_node_fs14.readFileSync)((0, import_node_path12.join)(sourceDir, skill, "SKILL.md"), "utf8");
4457
+ plan((0, import_node_path12.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
4088
4458
  }
4089
4459
  rememberTarget("claude", claudeRoot);
4090
4460
  }
4091
4461
  if (harnesses.includes("cursor")) {
4092
- const cursorRule = (0, import_node_path11.join)(root, ".cursor", "rules", "odla.mdc");
4462
+ const cursorRule = (0, import_node_path12.join)(root, ".cursor", "rules", "odla.mdc");
4093
4463
  plan(cursorRule, CURSOR_RULE);
4094
4464
  rememberTarget("cursor", cursorRule);
4095
4465
  }
4096
4466
  if (harnesses.includes("agents")) {
4097
- const agentsFile = (0, import_node_path11.join)(root, "AGENTS.md");
4467
+ const agentsFile = (0, import_node_path12.join)(root, "AGENTS.md");
4098
4468
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
4099
4469
  rememberTarget("agents", agentsFile);
4100
4470
  }
4101
4471
  if (harnesses.includes("copilot")) {
4102
- const copilotFile = (0, import_node_path11.join)(root, ".github", "copilot-instructions.md");
4472
+ const copilotFile = (0, import_node_path12.join)(root, ".github", "copilot-instructions.md");
4103
4473
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
4104
4474
  rememberTarget("copilot", copilotFile);
4105
4475
  }
4106
4476
  if (harnesses.includes("gemini")) {
4107
- const geminiFile = (0, import_node_path11.join)(root, "GEMINI.md");
4477
+ const geminiFile = (0, import_node_path12.join)(root, "GEMINI.md");
4108
4478
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
4109
4479
  rememberTarget("gemini", geminiFile);
4110
4480
  }
@@ -4118,11 +4488,11 @@ function installSkill(options = {}) {
4118
4488
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
4119
4489
  continue;
4120
4490
  }
4121
- if (!(0, import_node_fs12.existsSync)(file.target)) {
4491
+ if (!(0, import_node_fs14.existsSync)(file.target)) {
4122
4492
  writtenPaths.add(file.target);
4123
4493
  continue;
4124
4494
  }
4125
- const current = (0, import_node_fs12.readFileSync)(file.target, "utf8");
4495
+ const current = (0, import_node_fs14.readFileSync)(file.target, "utf8");
4126
4496
  if (current === file.content) {
4127
4497
  unchangedPaths.add(file.target);
4128
4498
  } else if (file.managedMerge || options.force) {
@@ -4139,9 +4509,9 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
4139
4509
  );
4140
4510
  }
4141
4511
  for (const file of plans.values()) {
4142
- if (!(0, import_node_fs12.existsSync)(file.target) || (0, import_node_fs12.readFileSync)(file.target, "utf8") !== file.content) {
4143
- (0, import_node_fs12.mkdirSync)((0, import_node_path11.dirname)(file.target), { recursive: true });
4144
- (0, import_node_fs12.writeFileSync)(file.target, file.content);
4512
+ if (!(0, import_node_fs14.existsSync)(file.target) || (0, import_node_fs14.readFileSync)(file.target, "utf8") !== file.content) {
4513
+ (0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(file.target), { recursive: true });
4514
+ (0, import_node_fs14.writeFileSync)(file.target, file.content);
4145
4515
  }
4146
4516
  }
4147
4517
  const skills = skillNames(files);
@@ -4160,7 +4530,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
4160
4530
  };
4161
4531
  }
4162
4532
  function pathsUnder(root, paths) {
4163
- 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();
4533
+ 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();
4164
4534
  }
4165
4535
  function normalizeHarnesses(values, global) {
4166
4536
  const requested = values?.length ? values : ["claude"];
@@ -4182,9 +4552,9 @@ function normalizeHarnesses(values, global) {
4182
4552
  function managedFileContent(path, block, force, boundary) {
4183
4553
  const symlink = symlinkedComponent(boundary, path);
4184
4554
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
4185
- if (!(0, import_node_fs12.existsSync)(path)) return `${block}
4555
+ if (!(0, import_node_fs14.existsSync)(path)) return `${block}
4186
4556
  `;
4187
- const current = (0, import_node_fs12.readFileSync)(path, "utf8");
4557
+ const current = (0, import_node_fs14.readFileSync)(path, "utf8");
4188
4558
  const start = "<!-- odla-ai agent setup:start -->";
4189
4559
  const end = "<!-- odla-ai agent setup:end -->";
4190
4560
  const startAt = current.indexOf(start);
@@ -4205,15 +4575,15 @@ function managedFileContent(path, block, force, boundary) {
4205
4575
  return `${current.slice(0, startAt)}${block}${current.slice(afterEnd)}`;
4206
4576
  }
4207
4577
  function symlinkedComponent(boundary, target) {
4208
- const rel = (0, import_node_path11.relative)(boundary, target);
4209
- if (rel === ".." || rel.startsWith(`..${import_node_path11.sep}`) || (0, import_node_path11.isAbsolute)(rel)) {
4578
+ const rel = (0, import_node_path12.relative)(boundary, target);
4579
+ if (rel === ".." || rel.startsWith(`..${import_node_path12.sep}`) || (0, import_node_path12.isAbsolute)(rel)) {
4210
4580
  throw new Error(`agent setup target escapes its install root: ${target}`);
4211
4581
  }
4212
4582
  let current = boundary;
4213
- for (const part of rel.split(import_node_path11.sep).filter(Boolean)) {
4214
- current = (0, import_node_path11.join)(current, part);
4583
+ for (const part of rel.split(import_node_path12.sep).filter(Boolean)) {
4584
+ current = (0, import_node_path12.join)(current, part);
4215
4585
  try {
4216
- if ((0, import_node_fs12.lstatSync)(current).isSymbolicLink()) return current;
4586
+ if ((0, import_node_fs14.lstatSync)(current).isSymbolicLink()) return current;
4217
4587
  } catch (error) {
4218
4588
  if (error.code !== "ENOENT") throw error;
4219
4589
  }
@@ -4224,13 +4594,13 @@ function skillNames(files) {
4224
4594
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
4225
4595
  }
4226
4596
  function listFiles(dir) {
4227
- if (!(0, import_node_fs12.existsSync)(dir)) return [];
4597
+ if (!(0, import_node_fs14.existsSync)(dir)) return [];
4228
4598
  const results = [];
4229
4599
  const walk = (current) => {
4230
- for (const entry of (0, import_node_fs12.readdirSync)(current, { withFileTypes: true })) {
4231
- const path = (0, import_node_path11.join)(current, entry.name);
4600
+ for (const entry of (0, import_node_fs14.readdirSync)(current, { withFileTypes: true })) {
4601
+ const path = (0, import_node_path12.join)(current, entry.name);
4232
4602
  if (entry.isDirectory()) walk(path);
4233
- else results.push((0, import_node_path11.relative)(dir, path));
4603
+ else results.push((0, import_node_path12.relative)(dir, path));
4234
4604
  }
4235
4605
  };
4236
4606
  walk(dir);
@@ -4425,10 +4795,14 @@ async function secretsCommand(parsed, deps) {
4425
4795
  async function projectCommand(command, parsed, deps) {
4426
4796
  if (command === "config") {
4427
4797
  const sub = parsed.positionals[1];
4428
- if (sub !== "diff" && sub !== "plan") {
4798
+ if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
4429
4799
  throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
4430
4800
  }
4431
- assertArgs(parsed, ["config", "token", "email", "open", "json"], 2);
4801
+ assertArgs(
4802
+ parsed,
4803
+ sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
4804
+ 2
4805
+ );
4432
4806
  const options = {
4433
4807
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4434
4808
  token: stringOpt(parsed.options.token),
@@ -4440,7 +4814,42 @@ async function projectCommand(command, parsed, deps) {
4440
4814
  stdout: deps.stdout
4441
4815
  };
4442
4816
  if (sub === "diff") await configDiff(options);
4443
- else await configPlan(options);
4817
+ else if (sub === "plan") await configPlan(options);
4818
+ else await configApply({
4819
+ ...options,
4820
+ planPath: requiredString(parsed.options.plan, "--plan"),
4821
+ idempotencyKey: stringOpt(parsed.options["idempotency-key"])
4822
+ });
4823
+ return true;
4824
+ }
4825
+ if (command === "operations") {
4826
+ const sub = parsed.positionals[1];
4827
+ if (sub !== "get" && sub !== "wait") {
4828
+ throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
4829
+ }
4830
+ assertArgs(
4831
+ parsed,
4832
+ sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
4833
+ 3
4834
+ );
4835
+ const operationId = parsed.positionals[2];
4836
+ if (!operationId) throw new Error(`operation id is required`);
4837
+ const options = {
4838
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
4839
+ operationId,
4840
+ token: stringOpt(parsed.options.token),
4841
+ email: stringOpt(parsed.options.email),
4842
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
4843
+ json: parsed.options.json === true,
4844
+ fetch: deps.fetch,
4845
+ openApprovalUrl: deps.openUrl,
4846
+ stdout: deps.stdout,
4847
+ pollWait: deps.pollWait,
4848
+ intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
4849
+ timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
4850
+ };
4851
+ if (sub === "get") await configOperationGet(options);
4852
+ else await configOperationWait(options);
4444
4853
  return true;
4445
4854
  }
4446
4855
  if (command === "init") {
@@ -4501,9 +4910,9 @@ async function projectCommand(command, parsed, deps) {
4501
4910
  }
4502
4911
 
4503
4912
  // src/code-connect.ts
4504
- var import_node_fs14 = require("fs");
4913
+ var import_node_fs15 = require("fs");
4505
4914
  var import_node_os4 = require("os");
4506
- var import_node_path13 = require("path");
4915
+ var import_node_path14 = require("path");
4507
4916
 
4508
4917
  // ../harness/dist/chunk-QTUEF2HZ.js
4509
4918
  var HARNESS_PROTOCOL_VERSION = 1;
@@ -4513,7 +4922,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
4513
4922
  var HarnessProtocolError = class extends Error {
4514
4923
  name = "HarnessProtocolError";
4515
4924
  };
4516
- function record2(value2) {
4925
+ function record4(value2) {
4517
4926
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
4518
4927
  }
4519
4928
  function boundedText(value2, label, max) {
@@ -4530,7 +4939,7 @@ function parseAgentOutput(line) {
4530
4939
  } catch {
4531
4940
  throw new HarnessProtocolError("agent emitted invalid JSON");
4532
4941
  }
4533
- const message2 = record2(value2);
4942
+ const message2 = record4(value2);
4534
4943
  if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
4535
4944
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
4536
4945
  }
@@ -4543,7 +4952,7 @@ function parseAgentOutput(line) {
4543
4952
  };
4544
4953
  }
4545
4954
  if (message2.type === "inference.request") {
4546
- const call2 = record2(message2.call);
4955
+ const call2 = record4(message2.call);
4547
4956
  if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
4548
4957
  throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
4549
4958
  }
@@ -4555,7 +4964,7 @@ function parseAgentOutput(line) {
4555
4964
  };
4556
4965
  }
4557
4966
  if (message2.type === "tool.request") {
4558
- const input = record2(message2.input);
4967
+ const input = record4(message2.input);
4559
4968
  const tool = String(message2.tool);
4560
4969
  if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
4561
4970
  throw new HarnessProtocolError("tool.request requires a registered tool and object input");
@@ -4898,8 +5307,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
4898
5307
  const maxFiles = options.maxFiles ?? 2e4;
4899
5308
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
4900
5309
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
4901
- const entries = inventory.flatMap((record8) => {
4902
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record8);
5310
+ const entries = inventory.flatMap((record10) => {
5311
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record10);
4903
5312
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
4904
5313
  });
4905
5314
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -5147,8 +5556,8 @@ function normalize(value2) {
5147
5556
  if (Array.isArray(value2)) return value2.map(normalize);
5148
5557
  if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
5149
5558
  if (typeof value2 === "object") {
5150
- const record8 = value2;
5151
- return Object.fromEntries(Object.keys(record8).filter((key) => record8[key] !== void 0).sort().map((key) => [key, normalize(record8[key])]));
5559
+ const record10 = value2;
5560
+ return Object.fromEntries(Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, normalize(record10[key])]));
5152
5561
  }
5153
5562
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
5154
5563
  }
@@ -5227,7 +5636,7 @@ function normalizeReaders(readers) {
5227
5636
  }
5228
5637
 
5229
5638
  // ../camel/dist/code.js
5230
- var DIGEST = /^sha256:[0-9a-f]{64}$/;
5639
+ var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
5231
5640
  var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
5232
5641
  var ID = /^[A-Za-z0-9._:-]{1,160}$/;
5233
5642
  async function digestCodeVerificationReceipt(fields) {
@@ -5262,18 +5671,18 @@ async function digestCodeVerificationReceipt(fields) {
5262
5671
  return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
5263
5672
  }
5264
5673
  function validate(fields) {
5265
- 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) {
5674
+ 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) {
5266
5675
  throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
5267
5676
  }
5268
5677
  const ids = /* @__PURE__ */ new Set();
5269
5678
  for (const recipe2 of fields.recipes) {
5270
- 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) {
5679
+ 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) {
5271
5680
  throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
5272
5681
  }
5273
5682
  const artifactIds = /* @__PURE__ */ new Set();
5274
5683
  if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
5275
5684
  for (const artifact of recipe2.artifacts) {
5276
- 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)) {
5685
+ 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)) {
5277
5686
  throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
5278
5687
  }
5279
5688
  artifactIds.add(artifact.artifactId);
@@ -5289,7 +5698,7 @@ function validate(fields) {
5289
5698
  }
5290
5699
  }
5291
5700
  var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
5292
- var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
5701
+ var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
5293
5702
  var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
5294
5703
  var MAX_PATCH_BYTES = 256 * 1024;
5295
5704
  var MAX_STATE_BYTES = 64 * 1024;
@@ -5334,14 +5743,14 @@ function normalizeState(value2) {
5334
5743
  ]);
5335
5744
  const planCursor = state2.planCursor;
5336
5745
  const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
5337
- const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST2, 256, true);
5338
- 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) {
5746
+ const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
5747
+ 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) {
5339
5748
  throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
5340
5749
  }
5341
5750
  const effects = state2.completedEffects.map((item) => {
5342
5751
  const effect = object(item, "completed effect");
5343
5752
  exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
5344
- 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)) {
5753
+ 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)) {
5345
5754
  throw invalid2("Portable checkpoint effect receipt is malformed.");
5346
5755
  }
5347
5756
  return {
@@ -5871,7 +6280,7 @@ function createCodeRuntimeControlClient(options) {
5871
6280
  }
5872
6281
  const value2 = await response2.json().catch(() => null);
5873
6282
  if (!response2.ok) {
5874
- const problem = record3(record3(value2)?.error);
6283
+ const problem = record5(record5(value2)?.error);
5875
6284
  throw new CodeRuntimeControlError(
5876
6285
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
5877
6286
  response2.status,
@@ -5893,12 +6302,12 @@ function createCodeRuntimeControlClient(options) {
5893
6302
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
5894
6303
  ),
5895
6304
  infer: async (sessionId, inference) => {
5896
- const value2 = record3(await call2(
6305
+ const value2 = record5(await call2(
5897
6306
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
5898
6307
  inference,
5899
6308
  modelRequestTimeoutMs
5900
6309
  ));
5901
- if (!value2 || value2.requestId !== inference.requestId || !record3(value2.response) || !record3(value2.receipt)) {
6310
+ if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
5902
6311
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
5903
6312
  }
5904
6313
  return value2;
@@ -5956,12 +6365,12 @@ function validateHeartbeat(version, capabilities) {
5956
6365
  }
5957
6366
  }
5958
6367
  function parseSnapshot(value2) {
5959
- const root = record3(value2);
5960
- const host = record3(root?.host);
6368
+ const root = record5(value2);
6369
+ const host = record5(root?.host);
5961
6370
  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");
5962
6371
  const bindingIds = /* @__PURE__ */ new Set();
5963
6372
  const bindings = root.bindings.map((item) => {
5964
- const binding = record3(item);
6373
+ const binding = record5(item);
5965
6374
  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)) {
5966
6375
  throw invalid("binding");
5967
6376
  }
@@ -5971,10 +6380,10 @@ function parseSnapshot(value2) {
5971
6380
  const commandIds = /* @__PURE__ */ new Set();
5972
6381
  const commandSequences = /* @__PURE__ */ new Set();
5973
6382
  const commands = root.commands.map((item) => {
5974
- const command = record3(item);
6383
+ const command = record5(item);
5975
6384
  const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
5976
6385
  const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
5977
- 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");
6386
+ 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");
5978
6387
  commandIds.add(command.commandId);
5979
6388
  commandSequences.add(sequenceKey);
5980
6389
  return command;
@@ -5982,10 +6391,10 @@ function parseSnapshot(value2) {
5982
6391
  return { host, bindings, commands };
5983
6392
  }
5984
6393
  async function parseSource(value2) {
5985
- const snapshot = record3(record3(value2)?.snapshot);
6394
+ const snapshot = record5(record5(value2)?.snapshot);
5986
6395
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
5987
6396
  const files = snapshot.files.map((value22) => {
5988
- const file = record3(value22);
6397
+ const file = record5(value22);
5989
6398
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
5990
6399
  return { path: file.path, content: file.content };
5991
6400
  });
@@ -5994,11 +6403,11 @@ async function parseSource(value2) {
5994
6403
  const aliases = /* @__PURE__ */ new Set();
5995
6404
  const references = [];
5996
6405
  for (const item of referencesValue) {
5997
- const reference = record3(item);
6406
+ const reference = record5(item);
5998
6407
  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");
5999
6408
  aliases.add(reference.alias);
6000
6409
  const referenceFiles = reference.files.map((entry) => {
6001
- const file = record3(entry);
6410
+ const file = record5(entry);
6002
6411
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
6003
6412
  return { path: file.path, content: file.content };
6004
6413
  });
@@ -6013,18 +6422,18 @@ async function parseSource(value2) {
6013
6422
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
6014
6423
  }
6015
6424
  function parseReview(value2) {
6016
- const review = record3(record3(value2)?.review);
6425
+ const review = record5(record5(value2)?.review);
6017
6426
  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");
6018
6427
  return review;
6019
6428
  }
6020
6429
  function parseCandidate(value2) {
6021
- const candidate = record3(record3(value2)?.candidate);
6430
+ const candidate = record5(record5(value2)?.candidate);
6022
6431
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
6023
6432
  throw invalid("candidate");
6024
6433
  }
6025
6434
  return { candidateId: candidate.candidateId, status: candidate.status };
6026
6435
  }
6027
- var record3 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
6436
+ var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
6028
6437
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
6029
6438
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
6030
6439
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
@@ -7387,13 +7796,6 @@ var CodePiRuntimeEngine = class {
7387
7796
  }
7388
7797
  };
7389
7798
 
7390
- // src/version.ts
7391
- var import_node_fs13 = require("fs");
7392
- function cliVersion() {
7393
- const pkg = JSON.parse((0, import_node_fs13.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
7394
- return pkg.version ?? "unknown";
7395
- }
7396
-
7397
7799
  // src/security-hosted-github.ts
7398
7800
  var import_node_child_process4 = require("child_process");
7399
7801
  var import_node_util2 = require("util");
@@ -7669,7 +8071,7 @@ var import_node_child_process6 = require("child_process");
7669
8071
  var import_node_crypto3 = require("crypto");
7670
8072
  var import_promises10 = require("fs/promises");
7671
8073
  var import_node_os3 = require("os");
7672
- var import_node_path12 = require("path");
8074
+ var import_node_path13 = require("path");
7673
8075
  var import_node_url3 = require("url");
7674
8076
 
7675
8077
  // src/code-runtime-config.ts
@@ -7755,10 +8157,10 @@ async function embeddedPiImageName() {
7755
8157
  return `odla-ai/pi-agent:embedded-sha256-${(0, import_node_crypto3.createHash)("sha256").update(bundle).digest("hex")}`;
7756
8158
  }
7757
8159
  async function buildEmbeddedPiImage(engine, image, run) {
7758
- const context = await (0, import_promises10.mkdtemp)((0, import_node_path12.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
8160
+ const context = await (0, import_promises10.mkdtemp)((0, import_node_path13.join)((0, import_node_os3.tmpdir)(), "odla-code-pi-"));
7759
8161
  try {
7760
- await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path12.join)(context, "pi-agent.js"));
7761
- await (0, import_promises10.writeFile)((0, import_node_path12.join)(context, "Dockerfile"), [
8162
+ await (0, import_promises10.copyFile)(embeddedPiAssetPath(), (0, import_node_path13.join)(context, "pi-agent.js"));
8163
+ await (0, import_promises10.writeFile)((0, import_node_path13.join)(context, "Dockerfile"), [
7762
8164
  `FROM ${CODE_NODE_IMAGE}`,
7763
8165
  "COPY pi-agent.js /opt/odla/pi-agent.js",
7764
8166
  "WORKDIR /workspace",
@@ -7774,8 +8176,8 @@ async function buildEmbeddedPiImage(engine, image, run) {
7774
8176
  // src/code-connect.ts
7775
8177
  async function codeConnect(options) {
7776
8178
  const cwd = options.cwd ?? process.cwd();
7777
- const configPath = (0, import_node_path13.resolve)(cwd, options.configPath);
7778
- const cfg = (0, import_node_fs14.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
8179
+ const configPath = (0, import_node_path14.resolve)(cwd, options.configPath);
8180
+ const cfg = (0, import_node_fs15.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
7779
8181
  const requestedAppId = options.appId?.trim();
7780
8182
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
7781
8183
  throw new Error("--app-id must be a valid odla app id");
@@ -7933,20 +8335,20 @@ async function runCodeRuntime(input) {
7933
8335
  }
7934
8336
  }
7935
8337
  function parseConnection(value2, appId, appEnv) {
7936
- const root = record4(value2);
7937
- const host = record4(root?.host);
7938
- const offer = record4(root?.offer);
7939
- const binding = record4(root?.binding);
8338
+ const root = record6(value2);
8339
+ const host = record6(root?.host);
8340
+ const offer = record6(root?.offer);
8341
+ const binding = record6(root?.binding);
7940
8342
  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)) {
7941
8343
  throw new Error("connect Code host returned an invalid response");
7942
8344
  }
7943
8345
  return root;
7944
8346
  }
7945
8347
  function apiFailure(action2, status, value2) {
7946
- const message2 = record4(record4(value2)?.error)?.message;
8348
+ const message2 = record6(record6(value2)?.error)?.message;
7947
8349
  return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
7948
8350
  }
7949
- function record4(value2) {
8351
+ function record6(value2) {
7950
8352
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
7951
8353
  }
7952
8354
 
@@ -8161,8 +8563,9 @@ Usage:
8161
8563
  odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
8162
8564
  odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
8163
8565
  odla-ai doctor [--config odla.config.mjs]
8164
- odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
8165
- odla-ai config plan [--config odla.config.mjs] [--email <odla-account>] [--json]
8566
+ odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
8567
+ odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
8568
+ odla-ai operations <get|wait> <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
8166
8569
  odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
8167
8570
  odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
8168
8571
  odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
@@ -8287,8 +8690,8 @@ Commands:
8287
8690
  setup Install offline odla runbooks for common coding-agent harnesses.
8288
8691
  init Create a generic odla.config.mjs plus starter schema/rules files.
8289
8692
  doctor Validate and summarize the project config without network calls.
8290
- config Read-only diff and revision-bound plan for checked-in Registry
8291
- intent; runtime-owned fields and excluded coverage stay explicit.
8693
+ config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
8694
+ operations Inspect or wait on one exact, durable config-operation receipt.
8292
8695
  calendar Inspect, connect, or disconnect the live Google booking connection.
8293
8696
  app Archive (suspend, data retained), restore, export, import, or
8294
8697
  manage the co-owners of the app. Archiving takes every
@@ -8995,8 +9398,8 @@ async function pmAdd(ctx, entity, parsed) {
8995
9398
  emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
8996
9399
  }
8997
9400
  async function pmGet(ctx, entity, id) {
8998
- const { record: record8 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
8999
- emit2(ctx, record8, () => printRecord(ctx, entity, record8));
9401
+ const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
9402
+ emit2(ctx, record10, () => printRecord(ctx, entity, record10));
9000
9403
  }
9001
9404
  async function pmSet(ctx, entity, id, parsed) {
9002
9405
  const patch2 = collectEntityFields(entity, parsed, true);
@@ -9041,9 +9444,9 @@ async function pmHandoff(ctx, parsed) {
9041
9444
  ]);
9042
9445
  const handoff = {
9043
9446
  appId,
9044
- unmetGoals: goals.filter((record8) => record8.status !== "met"),
9045
- activeTasks: tasks.filter((record8) => record8.column !== "done"),
9046
- openBugs: bugs.filter((record8) => record8.status !== "fixed" && record8.status !== "wontfix")
9447
+ unmetGoals: goals.filter((record10) => record10.status !== "met"),
9448
+ activeTasks: tasks.filter((record10) => record10.column !== "done"),
9449
+ openBugs: bugs.filter((record10) => record10.status !== "fixed" && record10.status !== "wontfix")
9047
9450
  };
9048
9451
  const result = {
9049
9452
  ...handoff,
@@ -9062,10 +9465,10 @@ async function pmHandoff(ctx, parsed) {
9062
9465
  ]) {
9063
9466
  ctx.out.log(`${label}:`);
9064
9467
  if (!records.length) ctx.out.log("- (none)");
9065
- else for (const record8 of records) printRecord(
9468
+ else for (const record10 of records) printRecord(
9066
9469
  ctx,
9067
9470
  label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
9068
- record8
9471
+ record10
9069
9472
  );
9070
9473
  }
9071
9474
  });
@@ -9306,17 +9709,17 @@ async function platformCommand(parsed, deps = {}) {
9306
9709
  }
9307
9710
  }
9308
9711
  function isPlatformStatus(value2) {
9309
- if (!record5(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
9310
- if (!record5(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
9311
- if (!record5(value2.catalog) || !record5(value2.summary)) return false;
9712
+ if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
9713
+ if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
9714
+ if (!record7(value2.catalog) || !record7(value2.summary)) return false;
9312
9715
  return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
9313
9716
  }
9314
9717
  function apiMessage(value2) {
9315
- if (!record5(value2)) return "request failed";
9316
- const error = record5(value2.error) ? value2.error : value2;
9718
+ if (!record7(value2)) return "request failed";
9719
+ const error = record7(value2.error) ? value2.error : value2;
9317
9720
  return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
9318
9721
  }
9319
- function record5(value2) {
9722
+ function record7(value2) {
9320
9723
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
9321
9724
  }
9322
9725
 
@@ -9357,7 +9760,7 @@ function statusVerdict(reads) {
9357
9760
  severity: "degraded"
9358
9761
  });
9359
9762
  }
9360
- const performance = record6(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
9763
+ const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
9361
9764
  if (performance?.status === "unavailable") {
9362
9765
  reasons.push({
9363
9766
  source: "liveSync",
@@ -9438,7 +9841,7 @@ function statusVerdict(reads) {
9438
9841
  reasons
9439
9842
  };
9440
9843
  }
9441
- function record6(value2) {
9844
+ function record8(value2) {
9442
9845
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
9443
9846
  }
9444
9847
  function numeric2(value2) {
@@ -9466,7 +9869,7 @@ function printO11yStatus(status, out) {
9466
9869
  out.log(
9467
9870
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
9468
9871
  );
9469
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record7) : [];
9872
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
9470
9873
  const requests = routes.reduce(
9471
9874
  (total, row) => total + numeric3(row.requests),
9472
9875
  0
@@ -9478,39 +9881,39 @@ function printO11yStatus(status, out) {
9478
9881
  out.log(
9479
9882
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
9480
9883
  );
9481
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record7) : [];
9884
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
9482
9885
  out.log(
9483
9886
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
9484
9887
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
9485
9888
  ).join(", ") : "none observed"}`
9486
9889
  );
9487
9890
  out.log(liveSyncLine(status.liveSync));
9488
- const canaryDurations = record7(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
9891
+ const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
9489
9892
  out.log(
9490
9893
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
9491
9894
  );
9492
- const collectorIngest = record7(status.collector.body.ingest) ? status.collector.body.ingest : {};
9493
- const collectorStorage = record7(collectorIngest.storage) ? collectorIngest.storage : {};
9895
+ const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
9896
+ const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
9494
9897
  out.log(
9495
9898
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
9496
9899
  );
9497
- const providerMetrics = record7(status.provider.body.metrics) ? status.provider.body.metrics : {};
9498
- const providerCapacity = record7(status.provider.body.capacity) ? status.provider.body.capacity : {};
9499
- const workerMemory = record7(providerCapacity.memory) ? providerCapacity.memory : {};
9900
+ const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
9901
+ const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
9902
+ const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
9500
9903
  out.log(
9501
9904
  `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`
9502
9905
  );
9503
9906
  for (const line of providerCapacityLines(status.providerCapacity)) {
9504
9907
  out.log(line);
9505
9908
  }
9506
- const coverage = record7(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
9507
- const coverageCounts = record7(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
9508
- const coverageBudget = record7(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
9909
+ const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
9910
+ const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
9911
+ const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
9509
9912
  out.log(
9510
9913
  `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`
9511
9914
  );
9512
9915
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
9513
- const providerFreshness = record7(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
9916
+ const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
9514
9917
  out.log(
9515
9918
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
9516
9919
  );
@@ -9519,17 +9922,17 @@ function printO11yStatus(status, out) {
9519
9922
  );
9520
9923
  }
9521
9924
  function providerCapacityLines(read3) {
9522
- const resources = record7(read3.body.resources) ? read3.body.resources : {};
9523
- const durableObjects = record7(resources.durableObjects) ? resources.durableObjects : {};
9524
- const periodic = record7(durableObjects.periodic) ? durableObjects.periodic : {};
9525
- const storage = record7(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
9526
- const d1 = record7(resources.d1) ? resources.d1 : {};
9527
- const d1Activity = record7(d1.activity) ? d1.activity : {};
9528
- const d1Storage = record7(d1.storage) ? d1.storage : {};
9529
- const d1Latency = record7(d1Activity.latency) ? d1Activity.latency : {};
9530
- const r2 = record7(resources.r2) ? resources.r2 : {};
9531
- const r2Operations = record7(r2.operations) ? r2.operations : {};
9532
- const r2Storage = record7(r2.storage) ? r2.storage : {};
9925
+ const resources = record9(read3.body.resources) ? read3.body.resources : {};
9926
+ const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
9927
+ const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
9928
+ const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
9929
+ const d1 = record9(resources.d1) ? resources.d1 : {};
9930
+ const d1Activity = record9(d1.activity) ? d1.activity : {};
9931
+ const d1Storage = record9(d1.storage) ? d1.storage : {};
9932
+ const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
9933
+ const r2 = record9(resources.r2) ? resources.r2 : {};
9934
+ const r2Operations = record9(r2.operations) ? r2.operations : {};
9935
+ const r2Storage = record9(r2.storage) ? r2.storage : {};
9533
9936
  const status = String(
9534
9937
  read3.body.status ?? read3.body.error ?? "unavailable"
9535
9938
  );
@@ -9540,11 +9943,11 @@ function providerCapacityLines(read3) {
9540
9943
  ];
9541
9944
  }
9542
9945
  function liveSyncLine(read3) {
9543
- const performance = record7(read3.body.performance) ? read3.body.performance : {};
9544
- const commitToSend = record7(performance.commitToSend) ? performance.commitToSend : {};
9946
+ const performance = record9(read3.body.performance) ? read3.body.performance : {};
9947
+ const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
9545
9948
  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`;
9546
9949
  }
9547
- function record7(value2) {
9950
+ function record9(value2) {
9548
9951
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
9549
9952
  }
9550
9953
  function numeric3(value2) {
@@ -9727,7 +10130,7 @@ async function read2(url, headers, doFetch) {
9727
10130
  }
9728
10131
 
9729
10132
  // src/provision.ts
9730
- var import_apps10 = require("@odla-ai/apps");
10133
+ var import_apps12 = require("@odla-ai/apps");
9731
10134
  var import_ai3 = require("@odla-ai/ai");
9732
10135
  var import_node_process10 = __toESM(require("process"), 1);
9733
10136
 
@@ -9784,9 +10187,9 @@ async function responseText(res) {
9784
10187
  }
9785
10188
 
9786
10189
  // src/provision-credentials.ts
9787
- var import_apps9 = require("@odla-ai/apps");
10190
+ var import_apps11 = require("@odla-ai/apps");
9788
10191
  async function provisionEnvCredentials(opts) {
9789
- const tenantId = (0, import_apps9.tenantIdFor)(opts.cfg.app.id, opts.env);
10192
+ const tenantId = (0, import_apps11.tenantIdFor)(opts.cfg.app.id, opts.env);
9790
10193
  const prior = opts.credentials?.envs[opts.env];
9791
10194
  let credentials = opts.credentials;
9792
10195
  let dbKey = opts.cfg.services.includes("db") && !opts.rotateDb ? prior?.dbKey : void 0;
@@ -9948,7 +10351,7 @@ async function provision(options) {
9948
10351
  }
9949
10352
  const doFetch = options.fetch ?? fetch;
9950
10353
  const token = await getDeveloperToken(cfg, options, doFetch, out);
9951
- const apps = (0, import_apps10.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
10354
+ const apps = (0, import_apps12.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
9952
10355
  const existing = await apps.resolveApp(cfg.app.id);
9953
10356
  if (existing) {
9954
10357
  out.log(`app: ${cfg.app.id} already exists`);
@@ -9959,7 +10362,7 @@ async function provision(options) {
9959
10362
  for (const env of cfg.envs) {
9960
10363
  await assertTenantAdminAccess(doFetch, cfg, env, token);
9961
10364
  }
9962
- const serviceOrder = (0, import_apps10.orderAppServices)(cfg.services);
10365
+ const serviceOrder = (0, import_apps12.orderAppServices)(cfg.services);
9963
10366
  for (const env of cfg.envs) {
9964
10367
  for (const service of serviceOrder) {
9965
10368
  if (service === "ai") {
@@ -9992,7 +10395,7 @@ async function provision(options) {
9992
10395
  }
9993
10396
  }
9994
10397
  for (const env of cfg.envs) {
9995
- const tenantId = (0, import_apps10.tenantIdFor)(cfg.app.id, env);
10398
+ const tenantId = (0, import_apps12.tenantIdFor)(cfg.app.id, env);
9996
10399
  credentials = await provisionEnvCredentials({
9997
10400
  cfg,
9998
10401
  env,
@@ -10077,7 +10480,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
10077
10480
  }
10078
10481
 
10079
10482
  // src/record.ts
10080
- var import_node_fs15 = require("fs");
10483
+ var import_node_fs16 = require("fs");
10081
10484
  var import_node_process11 = __toESM(require("process"), 1);
10082
10485
 
10083
10486
  // src/surface.ts
@@ -10126,7 +10529,7 @@ var COMMAND_SURFACE = {
10126
10529
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
10127
10530
  capabilities: {},
10128
10531
  code: { connect: {} },
10129
- config: { diff: {}, plan: {} },
10532
+ config: { diff: {}, plan: {}, apply: {} },
10130
10533
  context: { show: {}, list: {}, save: {}, remove: {} },
10131
10534
  // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
10132
10535
  discuss: {
@@ -10144,6 +10547,7 @@ var COMMAND_SURFACE = {
10144
10547
  help: {},
10145
10548
  init: {},
10146
10549
  o11y: { status: {} },
10550
+ operations: { get: {}, wait: {} },
10147
10551
  platform: { status: {} },
10148
10552
  pm: {
10149
10553
  ...PM_ENTITIES,
@@ -10240,14 +10644,14 @@ function recordInvocation(parsed) {
10240
10644
  options: Object.entries(parsed.options).map(([name, value2]) => value2 === false ? `no-${name}` : name).sort()
10241
10645
  };
10242
10646
  if (!entry.path.length) return;
10243
- (0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
10647
+ (0, import_node_fs16.appendFileSync)(file, `${JSON.stringify(entry)}
10244
10648
  `);
10245
10649
  } catch {
10246
10650
  }
10247
10651
  }
10248
10652
 
10249
10653
  // src/runbook-actions.ts
10250
- var import_node_fs16 = require("fs");
10654
+ var import_node_fs17 = require("fs");
10251
10655
 
10252
10656
  // src/runbook-requires.ts
10253
10657
  var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
@@ -10332,7 +10736,7 @@ async function bySlug(ctx, slug) {
10332
10736
  function readBody(file, inline) {
10333
10737
  if (inline !== void 0) return inline;
10334
10738
  if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
10335
- return (0, import_node_fs16.readFileSync)(file === "-" ? 0 : file, "utf8");
10739
+ return (0, import_node_fs17.readFileSync)(file === "-" ? 0 : file, "utf8");
10336
10740
  }
10337
10741
  var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
10338
10742
  async function runbookList(ctx, all, query) {
@@ -10419,8 +10823,8 @@ async function runbookRemove(ctx, slug) {
10419
10823
  }
10420
10824
 
10421
10825
  // src/runbook-import.ts
10422
- var import_node_fs17 = require("fs");
10423
- var import_node_path14 = require("path");
10826
+ var import_node_fs18 = require("fs");
10827
+ var import_node_path15 = require("path");
10424
10828
  function parseRunbook(text, slug) {
10425
10829
  let rest = text;
10426
10830
  const meta = {};
@@ -10445,12 +10849,12 @@ function parseRunbook(text, slug) {
10445
10849
  };
10446
10850
  }
10447
10851
  function readRunbookDir(dir) {
10448
- if (!(0, import_node_fs17.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
10449
- const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
10852
+ if (!(0, import_node_fs18.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
10853
+ const files = (0, import_node_fs18.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
10450
10854
  if (!files.length) throw new Error(`no .md files in ${dir}`);
10451
10855
  return files.map((file) => {
10452
- const slug = (0, import_node_path14.basename)(file, ".md");
10453
- const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(dir, file), "utf8"), slug);
10856
+ const slug = (0, import_node_path15.basename)(file, ".md");
10857
+ const parsed = parseRunbook((0, import_node_fs18.readFileSync)((0, import_node_path15.join)(dir, file), "utf8"), slug);
10454
10858
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
10455
10859
  });
10456
10860
  }
@@ -10522,8 +10926,8 @@ async function upsert(ctx, r, visibility) {
10522
10926
 
10523
10927
  // src/runbook-impact.ts
10524
10928
  var import_node_child_process7 = require("child_process");
10525
- var import_node_fs18 = require("fs");
10526
- var import_node_path15 = require("path");
10929
+ var import_node_fs19 = require("fs");
10930
+ var import_node_path16 = require("path");
10527
10931
 
10528
10932
  // src/runbook-impact-scan.ts
10529
10933
  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$]*)/;
@@ -10692,10 +11096,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
10692
11096
  }
10693
11097
  function manifestLabeller(root) {
10694
11098
  return (workspace) => {
10695
- const manifest = (0, import_node_path15.join)(root, workspace, "package.json");
10696
- if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
11099
+ const manifest = (0, import_node_path16.join)(root, workspace, "package.json");
11100
+ if (!(0, import_node_fs19.existsSync)(manifest)) return void 0;
10697
11101
  try {
10698
- const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
11102
+ const name = JSON.parse((0, import_node_fs19.readFileSync)(manifest, "utf8")).name;
10699
11103
  return typeof name === "string" ? name : void 0;
10700
11104
  } catch {
10701
11105
  return void 0;
@@ -10762,7 +11166,7 @@ function report3(ctx, impacts) {
10762
11166
  async function runbookImpact(ctx, options, deps = {}) {
10763
11167
  const cwd = deps.cwd ?? process.cwd();
10764
11168
  const runGit = deps.runGit ?? gitRunner(cwd);
10765
- const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path15.join)(cwd, path), "utf8"));
11169
+ const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs19.readFileSync)((0, import_node_path16.join)(cwd, path), "utf8"));
10766
11170
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
10767
11171
  if (!surfaces.length) {
10768
11172
  return ctx.out.log(
@@ -10895,9 +11299,9 @@ async function runbookComment(ctx, slug, body) {
10895
11299
 
10896
11300
  // src/runbook-editor.ts
10897
11301
  var import_node_child_process8 = require("child_process");
10898
- var import_node_fs19 = require("fs");
11302
+ var import_node_fs20 = require("fs");
10899
11303
  var import_node_os5 = require("os");
10900
- var import_node_path16 = require("path");
11304
+ var import_node_path17 = require("path");
10901
11305
  var import_node_process12 = __toESM(require("process"), 1);
10902
11306
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
10903
11307
  function resolveEditor(env = import_node_process12.default.env) {
@@ -10923,16 +11327,16 @@ function editText(initial, slug, deps = {}) {
10923
11327
  );
10924
11328
  if (!interactive())
10925
11329
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
10926
- const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path16.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
10927
- const file = (0, import_node_path16.join)(dir, `${slug}.md`);
11330
+ const dir = (0, import_node_fs20.mkdtempSync)((0, import_node_path17.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
11331
+ const file = (0, import_node_path17.join)(dir, `${slug}.md`);
10928
11332
  try {
10929
- (0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
11333
+ (0, import_node_fs20.writeFileSync)(file, initial, { mode: 384 });
10930
11334
  const code = defaultRunOrInjected(deps)(editor, file);
10931
11335
  if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
10932
- const edited = (0, import_node_fs19.readFileSync)(file, "utf8");
11336
+ const edited = (0, import_node_fs20.readFileSync)(file, "utf8");
10933
11337
  return edited === initial ? null : edited;
10934
11338
  } finally {
10935
- (0, import_node_fs19.rmSync)(dir, { recursive: true, force: true });
11339
+ (0, import_node_fs20.rmSync)(dir, { recursive: true, force: true });
10936
11340
  }
10937
11341
  }
10938
11342
  var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
@@ -11344,7 +11748,7 @@ function hostedSeverity(value2, flag) {
11344
11748
  var import_security2 = require("@odla-ai/security");
11345
11749
 
11346
11750
  // src/security.ts
11347
- var import_node_path17 = require("path");
11751
+ var import_node_path18 = require("path");
11348
11752
  var import_security = require("@odla-ai/security");
11349
11753
  var import_node3 = require("@odla-ai/security/node");
11350
11754
  async function runHostedSecurity(options) {
@@ -11356,9 +11760,9 @@ async function runHostedSecurity(options) {
11356
11760
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
11357
11761
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
11358
11762
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
11359
- const target = (0, import_node_path17.resolve)(options.target ?? cfg?.rootDir ?? ".");
11360
- const output = (0, import_node_path17.resolve)(options.out ?? (0, import_node_path17.resolve)(target, ".odla/security/hosted"));
11361
- const outputRelative = (0, import_node_path17.relative)(target, output).split(import_node_path17.sep).join("/");
11763
+ const target = (0, import_node_path18.resolve)(options.target ?? cfg?.rootDir ?? ".");
11764
+ const output = (0, import_node_path18.resolve)(options.out ?? (0, import_node_path18.resolve)(target, ".odla/security/hosted"));
11765
+ const outputRelative = (0, import_node_path18.relative)(target, output).split(import_node_path18.sep).join("/");
11362
11766
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
11363
11767
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
11364
11768
  const tokenRequest = {
@@ -11370,7 +11774,7 @@ async function runHostedSecurity(options) {
11370
11774
  };
11371
11775
  const token = await injectedToken(options, tokenRequest);
11372
11776
  const snapshot = await (0, import_node3.snapshotDirectory)(target, {
11373
- exclude: !outputRelative.startsWith("../") && !(0, import_node_path17.isAbsolute)(outputRelative) ? [outputRelative] : []
11777
+ exclude: !outputRelative.startsWith("../") && !(0, import_node_path18.isAbsolute)(outputRelative) ? [outputRelative] : []
11374
11778
  });
11375
11779
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
11376
11780
  platform,
@@ -11388,7 +11792,7 @@ async function runHostedSecurity(options) {
11388
11792
  });
11389
11793
  const harness = (0, import_security.createSecurityHarness)({
11390
11794
  profile,
11391
- store: new import_node3.FileRunStore((0, import_node_path17.resolve)(output, "state")),
11795
+ store: new import_node3.FileRunStore((0, import_node_path18.resolve)(output, "state")),
11392
11796
  discoveryReasoner: hosted.discoveryReasoner,
11393
11797
  validationReasoner: hosted.validationReasoner,
11394
11798
  policy: {
@@ -11412,7 +11816,7 @@ async function runHostedSecurity(options) {
11412
11816
  function selectEnv(requested, declared, configPath, rootDir) {
11413
11817
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
11414
11818
  if (!env || !declared.includes(env)) {
11415
- const shown = (0, import_node_path17.relative)(rootDir, configPath) || configPath;
11819
+ const shown = (0, import_node_path18.relative)(rootDir, configPath) || configPath;
11416
11820
  throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
11417
11821
  }
11418
11822
  return env;
@@ -11441,7 +11845,7 @@ function printSummary(out, appId, env, run, report4, output) {
11441
11845
  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}`);
11442
11846
  if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
11443
11847
  out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
11444
- out.log(` report: ${(0, import_node_path17.resolve)(output, "REPORT.md")}`);
11848
+ out.log(` report: ${(0, import_node_path18.resolve)(output, "REPORT.md")}`);
11445
11849
  }
11446
11850
  function formatBudget(usage) {
11447
11851
  return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
@@ -11892,11 +12296,11 @@ async function securityStatus(parsed, dependencies) {
11892
12296
  // src/cli.ts
11893
12297
  function exitCodeFor(err) {
11894
12298
  const code = err?.code;
11895
- if (code === "handshake_pending" || code === "watch_timeout") return 75;
12299
+ if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
11896
12300
  if (code === "checkpoint_required") return 3;
11897
12301
  if (code === "remote_unavailable") return 6;
11898
12302
  if (code === "auth_failed") return 5;
11899
- if (code === "invalid_cursor") return 2;
12303
+ if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
11900
12304
  return 1;
11901
12305
  }
11902
12306
  async function runCli(argv = process.argv.slice(2), dependencies = {}) {
@@ -12043,6 +12447,7 @@ async function calendarCommand(parsed, dependencies) {
12043
12447
  CODE_BUILD_RECIPES,
12044
12448
  CODE_PI_IMAGE,
12045
12449
  COMMAND_SURFACE,
12450
+ ConfigOperationCommandError,
12046
12451
  GOOGLE_CALENDAR_EVENTS_SCOPE,
12047
12452
  SYSTEM_AI_PURPOSES,
12048
12453
  acceptedAfter,
@@ -12054,7 +12459,10 @@ async function calendarCommand(parsed, dependencies) {
12054
12459
  calendarServiceConfig,
12055
12460
  calendarStatus,
12056
12461
  codeConnect,
12462
+ configApply,
12057
12463
  configDiff,
12464
+ configOperationGet,
12465
+ configOperationWait,
12058
12466
  configPlan,
12059
12467
  connectGitHubSecuritySource,
12060
12468
  describeProblem,