@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.
@@ -440,6 +440,7 @@ function audienceBoundEnvToken(token, platform) {
440
440
  var SCOPE_PURPOSE = {
441
441
  "platform:status:read": "read the platform fleet health and deployment snapshot",
442
442
  "app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
443
+ "app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
443
444
  "platform:runbook:write": "add or edit odla's operational runbooks",
444
445
  "platform:ai:policy:write": "change System AI model routing",
445
446
  "platform:ai:policy:read": "read System AI model routing",
@@ -941,6 +942,7 @@ function unique(values) {
941
942
  var DEFAULT_PLATFORM = "https://odla.ai";
942
943
  var DEFAULT_ENVS = ["dev"];
943
944
  var DEFAULT_SERVICES = ["db", "ai"];
945
+ var configImportSerial = 0;
944
946
  var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
945
947
  async function loadProjectConfig(configPath = "odla.config.mjs") {
946
948
  const resolved = resolve2(configPath);
@@ -1148,7 +1150,8 @@ function validId2(value2) {
1148
1150
  }
1149
1151
  async function loadConfigModule(path) {
1150
1152
  if (path.endsWith(".json")) return JSON.parse(readFileSync2(path, "utf8"));
1151
- const mod = await import(`${pathToFileURL(path).href}?t=${Date.now()}`);
1153
+ const nonce = `${Date.now()}-${configImportSerial++}`;
1154
+ const mod = await import(`${pathToFileURL(path).href}?reload=${nonce}`);
1152
1155
  const value2 = mod.default ?? mod.config;
1153
1156
  if (typeof value2 === "function") return await value2();
1154
1157
  return value2;
@@ -1638,7 +1641,8 @@ var CAPABILITIES = {
1638
1641
  "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",
1639
1642
  "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",
1640
1643
  "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",
1641
- "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",
1644
+ "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",
1645
+ "inspect or bounded-wait one exact durable config-operation journal entry and verify every terminal receipt digest before returning it to remote automation",
1642
1646
  "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
1643
1647
  "run app-attributed hosted security discovery and independent validation without provider keys",
1644
1648
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
@@ -1685,6 +1689,16 @@ function printGroup(out, heading, items) {
1685
1689
  out.log("");
1686
1690
  }
1687
1691
 
1692
+ // src/config-operation-error.ts
1693
+ var ConfigOperationCommandError = class extends Error {
1694
+ constructor(message2, code) {
1695
+ super(message2);
1696
+ this.code = code;
1697
+ this.name = "ConfigOperationCommandError";
1698
+ }
1699
+ code;
1700
+ };
1701
+
1688
1702
  // src/config-reconcile-desired.ts
1689
1703
  import { orderAppServices } from "@odla-ai/apps";
1690
1704
 
@@ -1778,8 +1792,25 @@ function managedServiceConfig(cfg, env, service) {
1778
1792
  return {};
1779
1793
  }
1780
1794
 
1781
- // src/config-reconcile.ts
1782
- import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
1795
+ // src/config-operation-command.ts
1796
+ import {
1797
+ AppsError,
1798
+ createAppsClient
1799
+ } from "@odla-ai/apps";
1800
+ import { join as join3 } from "path";
1801
+
1802
+ // src/version.ts
1803
+ import { readFileSync as readFileSync3 } from "fs";
1804
+ function cliVersion() {
1805
+ const pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
1806
+ return pkg.version ?? "unknown";
1807
+ }
1808
+
1809
+ // src/config-operation-validate.ts
1810
+ import {
1811
+ appServiceDefinition as appServiceDefinition2
1812
+ } from "@odla-ai/apps";
1813
+ import { readFileSync as readFileSync4 } from "fs";
1783
1814
 
1784
1815
  // src/config-reconcile-digest.ts
1785
1816
  import { createHash } from "crypto";
@@ -1790,15 +1821,347 @@ function configDigest(value2) {
1790
1821
  return `sha256:${createHash("sha256").update(canonicalJson(value2)).digest("hex")}`;
1791
1822
  }
1792
1823
  function canonicalValue(value2) {
1824
+ if (value2 === null || typeof value2 === "string" || typeof value2 === "boolean") return value2;
1825
+ if (typeof value2 === "number") {
1826
+ if (!Number.isFinite(value2)) throw new TypeError("canonical JSON rejects non-finite numbers");
1827
+ return value2;
1828
+ }
1793
1829
  if (Array.isArray(value2)) return value2.map(canonicalValue);
1794
1830
  if (value2 && typeof value2 === "object") {
1831
+ const record10 = value2;
1795
1832
  return Object.fromEntries(
1796
- Object.entries(value2).filter(([, entry]) => entry !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, canonicalValue(entry)])
1833
+ Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, canonicalValue(record10[key])])
1797
1834
  );
1798
1835
  }
1799
- return value2;
1836
+ throw new TypeError("canonical JSON rejects unsupported values");
1800
1837
  }
1801
1838
 
1839
+ // src/config-operation-validate.ts
1840
+ var DIGEST = /^sha256:[0-9a-f]{64}$/;
1841
+ var REVISION = /^registry:[1-9][0-9]*$/;
1842
+ 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;
1843
+ var ACTION_ID = /^action-[0-9a-f]{16}$/;
1844
+ var ENV = /^[a-z0-9]{2,12}$/;
1845
+ var SERVICE = /^[a-z][a-z0-9-]{0,39}$/;
1846
+ function readPlan(path) {
1847
+ let value2;
1848
+ try {
1849
+ const raw = readFileSync4(path, "utf8");
1850
+ if (Buffer.byteLength(raw) > 128 * 1024) throw new Error("plan exceeds 128 KiB");
1851
+ value2 = JSON.parse(raw);
1852
+ } catch (error) {
1853
+ throw new ConfigOperationCommandError(
1854
+ `cannot read --plan ${path}: ${error instanceof Error ? error.message : String(error)}`,
1855
+ "invalid_plan"
1856
+ );
1857
+ }
1858
+ if (!record2(value2) || value2.schemaVersion !== "odla.config-plan/v2") invalidPlan("unsupported plan schema");
1859
+ if (!record2(value2.scope) || typeof value2.scope.appId !== "string" || typeof value2.scope.platformUrl !== "string") {
1860
+ invalidPlan("plan scope is invalid");
1861
+ }
1862
+ if (!DIGEST.test(String(value2.desiredRevision)) || !DIGEST.test(String(value2.observedRevision))) {
1863
+ invalidPlan("plan content revisions are invalid");
1864
+ }
1865
+ if (!REVISION.test(String(value2.registryRevision)) || !DIGEST.test(String(value2.planDigest))) {
1866
+ invalidPlan("plan Registry revision or digest is invalid");
1867
+ }
1868
+ if (!Array.isArray(value2.actions) || !value2.actions.length || value2.actions.length > 32) {
1869
+ invalidPlan("plan actions must contain 1-32 entries");
1870
+ }
1871
+ assertActions(value2.actions);
1872
+ const plan = value2;
1873
+ const digest = configDigest({
1874
+ schemaVersion: plan.schemaVersion,
1875
+ desiredRevision: plan.desiredRevision,
1876
+ observedRevision: plan.observedRevision,
1877
+ registryRevision: plan.registryRevision,
1878
+ actions: plan.actions
1879
+ });
1880
+ if (digest !== plan.planDigest) invalidPlan("plan digest does not bind these revisions and actions");
1881
+ return plan;
1882
+ }
1883
+ function assertPlanContext(plan, appId, platformUrl) {
1884
+ if (plan.scope.appId !== appId || plan.scope.platformUrl.replace(/\/$/, "") !== platformUrl.replace(/\/$/, "")) {
1885
+ throw new ConfigOperationCommandError("plan scope does not match the selected project config", "checkpoint_required");
1886
+ }
1887
+ }
1888
+ function verifyReceipt(receipt, appId, operationId) {
1889
+ if (receipt.appId !== appId || operationId && receipt.operationId !== operationId) {
1890
+ throw new ConfigOperationCommandError("Registry returned a receipt outside the requested scope", "invalid_receipt");
1891
+ }
1892
+ if (receipt.receiptDigest) {
1893
+ const { receiptDigest, ...fields } = receipt;
1894
+ if (configDigest(fields) !== receiptDigest) {
1895
+ throw new ConfigOperationCommandError("config operation receipt digest is invalid", "invalid_receipt");
1896
+ }
1897
+ } else if (receipt.state === "succeeded" || receipt.state === "conflict" || receipt.state === "failed" && !receipt.error?.retryable) {
1898
+ throw new ConfigOperationCommandError("terminal config operation receipt is not digest-authenticated", "invalid_receipt");
1899
+ }
1900
+ }
1901
+ function assertOperationId(value2) {
1902
+ if (!OPERATION_ID.test(value2)) {
1903
+ throw new ConfigOperationCommandError("operation id must be a UUID", "invalid_operation_id");
1904
+ }
1905
+ }
1906
+ function assertActions(actions) {
1907
+ const ids = /* @__PURE__ */ new Set();
1908
+ for (const action2 of actions) {
1909
+ if (!record2(action2)) invalidPlan("every plan action must be an object");
1910
+ const id = String(action2.id ?? "");
1911
+ if (!ACTION_ID.test(id) || ids.has(id)) invalidPlan("plan action ids must be unique frozen ids");
1912
+ ids.add(id);
1913
+ 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))) {
1914
+ invalidPlan("plan action metadata is invalid");
1915
+ }
1916
+ if (["rename_app", "enable_service", "configure_service", "set_link"].includes(String(action2.kind))) {
1917
+ assertConditionalAction(action2);
1918
+ }
1919
+ }
1920
+ }
1921
+ function assertConditionalAction(action2) {
1922
+ if (action2.kind === "rename_app") {
1923
+ 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");
1924
+ return;
1925
+ }
1926
+ if (!action2.env || !ENV.test(action2.env)) invalidPlan("conditional action env is invalid");
1927
+ if (action2.kind === "set_link") {
1928
+ if (action2.path !== `environments.${action2.env}.link` || action2.applySupport !== "provision" || !linkState(action2.before) || !linkState(action2.after)) invalidPlan("link action is invalid");
1929
+ return;
1930
+ }
1931
+ if (!action2.service || !SERVICE.test(action2.service) || !appServiceDefinition2(action2.service)) {
1932
+ invalidPlan("service action names an unknown service");
1933
+ }
1934
+ const base = `environments.${action2.env}.services.${action2.service}`;
1935
+ if (action2.path !== (action2.kind === "configure_service" ? `${base}.config` : base)) {
1936
+ invalidPlan("service action path is invalid");
1937
+ }
1938
+ if (action2.applySupport !== "provision" || !record2(action2.after)) {
1939
+ invalidPlan("service action payload is invalid");
1940
+ }
1941
+ if (action2.kind === "enable_service") {
1942
+ if (action2.after.enabled !== true || action2.before !== null && !record2(action2.before)) {
1943
+ invalidPlan("service enable action is invalid");
1944
+ }
1945
+ } else if (!record2(action2.before)) {
1946
+ invalidPlan("service configure action is invalid");
1947
+ }
1948
+ }
1949
+ function linkState(value2) {
1950
+ if (value2 === null) return true;
1951
+ if (typeof value2 !== "string") return false;
1952
+ try {
1953
+ const url = new URL(value2.trim());
1954
+ return url.protocol === "http:" || url.protocol === "https:";
1955
+ } catch {
1956
+ return false;
1957
+ }
1958
+ }
1959
+ function invalidPlan(message2) {
1960
+ throw new ConfigOperationCommandError(message2, "invalid_plan");
1961
+ }
1962
+ function record2(value2) {
1963
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
1964
+ }
1965
+
1966
+ // src/config-reconcile-support.ts
1967
+ var CONDITIONAL_KINDS = /* @__PURE__ */ new Set([
1968
+ "rename_app",
1969
+ "enable_service",
1970
+ "configure_service",
1971
+ "set_link"
1972
+ ]);
1973
+ function configApplySupport(reconciliation) {
1974
+ if (!reconciliation.observedRevision || !reconciliation.registryRevision) {
1975
+ return {
1976
+ supported: false,
1977
+ checkpointRequired: false,
1978
+ reason: "the app must already exist in a revision-aware Registry"
1979
+ };
1980
+ }
1981
+ if (!reconciliation.actions.length) {
1982
+ return {
1983
+ supported: false,
1984
+ checkpointRequired: false,
1985
+ reason: "there are no managed changes to apply"
1986
+ };
1987
+ }
1988
+ const blocked = reconciliation.actions.filter(
1989
+ (action2) => !CONDITIONAL_KINDS.has(action2.kind) || action2.risk !== "low" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
1990
+ );
1991
+ if (blocked.length) {
1992
+ return {
1993
+ supported: false,
1994
+ checkpointRequired: blocked.some(
1995
+ (action2) => action2.risk === "high" || action2.requiresApproval || action2.applySupport === "studio" || action2.env === "prod" || action2.env === "production"
1996
+ ),
1997
+ reason: `${blocked.length} action${blocked.length === 1 ? "" : "s"} remain outside conditional apply`
1998
+ };
1999
+ }
2000
+ return {
2001
+ supported: true,
2002
+ checkpointRequired: false,
2003
+ reason: "all actions are low-risk, checkpoint-free Registry changes"
2004
+ };
2005
+ }
2006
+
2007
+ // src/config-operation-command.ts
2008
+ var IDEMPOTENCY_KEY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/;
2009
+ var DEFAULT_WAIT_SECONDS = 60;
2010
+ var DEFAULT_INTERVAL_SECONDS = 2;
2011
+ async function configApply(options) {
2012
+ const plan = readPlan(options.planPath);
2013
+ const cfg = await loadProjectConfig(options.configPath);
2014
+ assertPlanContext(plan, cfg.app.id, cfg.platformUrl);
2015
+ const support = configApplySupport(plan);
2016
+ if (!support.supported) {
2017
+ throw new ConfigOperationCommandError(
2018
+ support.reason,
2019
+ support.checkpointRequired ? "checkpoint_required" : "invalid_plan"
2020
+ );
2021
+ }
2022
+ if (configDigest(desiredRegistryState(cfg)) !== plan.desiredRevision) {
2023
+ throw new ConfigOperationCommandError(
2024
+ "project config changed after this plan was frozen; generate and review a fresh plan",
2025
+ "checkpoint_required"
2026
+ );
2027
+ }
2028
+ const idempotencyKey = options.idempotencyKey ?? `cli:${plan.planDigest.slice(7)}`;
2029
+ if (!IDEMPOTENCY_KEY.test(idempotencyKey)) {
2030
+ throw new ConfigOperationCommandError("--idempotency-key is not a safe 1-120 character key", "invalid_plan");
2031
+ }
2032
+ const client = await operationClient(cfg, options, "apply");
2033
+ const request2 = {
2034
+ schemaVersion: "odla.config-operation-request/v1",
2035
+ expectedRevision: plan.registryRevision,
2036
+ desiredRevision: plan.desiredRevision,
2037
+ observedRevision: plan.observedRevision,
2038
+ planDigest: plan.planDigest,
2039
+ idempotencyKey,
2040
+ source: { kind: "cli", version: cliVersion() },
2041
+ actions: plan.actions
2042
+ };
2043
+ let receipt;
2044
+ try {
2045
+ receipt = await client.applyConfigOperation(cfg.app.id, request2);
2046
+ } catch (error) {
2047
+ const retained = retainedReceipt(error);
2048
+ if (retained) {
2049
+ verifyReceipt(retained, cfg.app.id);
2050
+ printReceipt(retained, options);
2051
+ throw failureForReceipt(retained);
2052
+ }
2053
+ throw normalizeRequestError(error);
2054
+ }
2055
+ verifyReceipt(receipt, cfg.app.id);
2056
+ printReceipt(receipt, options);
2057
+ assertApplyCompleted(receipt);
2058
+ return receipt;
2059
+ }
2060
+ async function configOperationGet(options) {
2061
+ assertOperationId(options.operationId);
2062
+ const cfg = await loadProjectConfig(options.configPath);
2063
+ const client = await operationClient(cfg, options, "read");
2064
+ const receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
2065
+ throw normalizeRequestError(error);
2066
+ });
2067
+ if (!receipt) {
2068
+ throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
2069
+ }
2070
+ verifyReceipt(receipt, cfg.app.id, options.operationId);
2071
+ printReceipt(receipt, options);
2072
+ return receipt;
2073
+ }
2074
+ async function configOperationWait(options) {
2075
+ assertOperationId(options.operationId);
2076
+ const cfg = await loadProjectConfig(options.configPath);
2077
+ const client = await operationClient(cfg, options, "wait");
2078
+ const wait2 = options.pollWait ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
2079
+ const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
2080
+ const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
2081
+ const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
2082
+ let receipt = null;
2083
+ for (; ; ) {
2084
+ receipt = await client.getConfigOperation(cfg.app.id, options.operationId).catch((error) => {
2085
+ throw normalizeRequestError(error);
2086
+ });
2087
+ if (!receipt) {
2088
+ throw new ConfigOperationCommandError("config operation not found for this app", "operation_not_found");
2089
+ }
2090
+ verifyReceipt(receipt, cfg.app.id, options.operationId);
2091
+ if (receipt.state !== "running") break;
2092
+ if (now() >= deadline) {
2093
+ printReceipt(receipt, options);
2094
+ throw new ConfigOperationCommandError("config operation is still running", "operation_pending");
2095
+ }
2096
+ await wait2(Math.min(interval, Math.max(0, deadline - now())));
2097
+ }
2098
+ printReceipt(receipt, options);
2099
+ if (receipt.state !== "succeeded") throw failureForReceipt(receipt);
2100
+ return receipt;
2101
+ }
2102
+ async function operationClient(cfg, options, purpose) {
2103
+ const doFetch = options.fetch ?? fetch;
2104
+ const out = options.stdout ?? console;
2105
+ const token = await resolveAdminPlatformToken({
2106
+ platform: cfg.platformUrl,
2107
+ scope: "app:config:write",
2108
+ token: options.token,
2109
+ tokenFile: join3(cfg.rootDir, ".odla", "admin-token.local.json"),
2110
+ rootDir: cfg.rootDir,
2111
+ email: options.email,
2112
+ open: options.open,
2113
+ fetch: doFetch,
2114
+ stdout: out,
2115
+ openApprovalUrl: options.openApprovalUrl,
2116
+ label: `odla CLI (${cfg.app.id} config operation ${purpose})`
2117
+ });
2118
+ return createAppsClient({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
2119
+ }
2120
+ function printReceipt(receipt, options) {
2121
+ const out = options.stdout ?? console;
2122
+ if (options.json) out.log(JSON.stringify(receipt, null, 2));
2123
+ else {
2124
+ out.log(`config operation ${receipt.operationId}: ${receipt.state}`);
2125
+ out.log(`revision: ${receipt.expectedRevision} \u2192 ${receipt.currentRevision}`);
2126
+ for (const step of receipt.progress) out.log(` ${step.state} ${step.actionId} attempts=${step.attempts}`);
2127
+ out.log(`receipt: ${receipt.receiptDigest ?? "pending"}`);
2128
+ out.log(`studio: ${receipt.studioUrl}`);
2129
+ }
2130
+ }
2131
+ function assertApplyCompleted(receipt) {
2132
+ if (receipt.state === "succeeded") return;
2133
+ throw receipt.state === "running" ? new ConfigOperationCommandError("config operation is still running", "operation_pending") : failureForReceipt(receipt);
2134
+ }
2135
+ function failureForReceipt(receipt) {
2136
+ if (receipt.state === "conflict") return new ConfigOperationCommandError(
2137
+ receipt.error?.message ?? "config operation conflicted with current Registry state",
2138
+ "checkpoint_required"
2139
+ );
2140
+ if (receipt.error?.retryable) return new ConfigOperationCommandError(receipt.error.message, "remote_unavailable");
2141
+ return new ConfigOperationCommandError(receipt.error?.message ?? `config operation ${receipt.state}`, "config_operation_failed");
2142
+ }
2143
+ function retainedReceipt(error) {
2144
+ if (!(error instanceof AppsError) || !record3(error.details)) return null;
2145
+ return record3(error.details.operation) ? error.details.operation : null;
2146
+ }
2147
+ function normalizeRequestError(error) {
2148
+ if (!(error instanceof AppsError)) return error instanceof Error ? error : new Error(String(error));
2149
+ if (error.status === 401 || error.status === 403) {
2150
+ return new ConfigOperationCommandError(error.message, "auth_failed");
2151
+ }
2152
+ if (error.status === 409) return new ConfigOperationCommandError(error.message, "checkpoint_required");
2153
+ if (error.status === 429 || error.status >= 500) {
2154
+ return new ConfigOperationCommandError(error.message, "remote_unavailable");
2155
+ }
2156
+ return new ConfigOperationCommandError(error.message, error.code || "config_operation_failed");
2157
+ }
2158
+ function record3(value2) {
2159
+ return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
2160
+ }
2161
+
2162
+ // src/config-reconcile.ts
2163
+ import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
2164
+
1802
2165
  // src/config-reconcile-values.ts
1803
2166
  function difference(path, desired, observed, status, reason, desiredSource, observedSource, env, service) {
1804
2167
  return { path, env, service, status, desired, observed, desiredSource, observedSource, reason };
@@ -1898,6 +2261,7 @@ function reconcileConfig(input) {
1898
2261
  sources: { desired: desiredSource, observed: observedSource },
1899
2262
  desiredRevision,
1900
2263
  observedRevision,
2264
+ registryRevision: input.observed?.configRevision ?? null,
1901
2265
  status: different ? "different" : unmanaged ? "unmanaged" : "in_sync",
1902
2266
  summary: {
1903
2267
  differences: different,
@@ -2050,8 +2414,8 @@ function compareOptionalEnvironmentState(env, desired, observed, desiredSource,
2050
2414
  }
2051
2415
 
2052
2416
  // src/config-reconcile-command.ts
2053
- import { createAppsClient } from "@odla-ai/apps";
2054
- import { join as join3 } from "path";
2417
+ import { createAppsClient as createAppsClient2 } from "@odla-ai/apps";
2418
+ import { join as join4 } from "path";
2055
2419
  async function configDiff(options) {
2056
2420
  const reconciliation = await inspectConfig(options);
2057
2421
  const { actions: _actions, ...read3 } = reconciliation;
@@ -2065,20 +2429,19 @@ async function configDiff(options) {
2065
2429
  }
2066
2430
  async function configPlan(options) {
2067
2431
  const reconciliation = await inspectConfig(options);
2432
+ const apply = configApplySupport(reconciliation);
2068
2433
  const planDigest = configDigest({
2069
- schemaVersion: "odla.config-plan/v1",
2434
+ schemaVersion: "odla.config-plan/v2",
2070
2435
  desiredRevision: reconciliation.desiredRevision,
2071
2436
  observedRevision: reconciliation.observedRevision,
2437
+ registryRevision: reconciliation.registryRevision,
2072
2438
  actions: reconciliation.actions
2073
2439
  });
2074
2440
  const document = {
2075
- schemaVersion: "odla.config-plan/v1",
2441
+ schemaVersion: "odla.config-plan/v2",
2076
2442
  ...reconciliation,
2077
2443
  planDigest,
2078
- apply: {
2079
- supported: false,
2080
- reason: "conditional, resumable config apply is not available yet; use the exact reviewed commands below"
2081
- },
2444
+ apply,
2082
2445
  nextActions: planNextActions(reconciliation, options.configPath)
2083
2446
  };
2084
2447
  printPlan(document, options);
@@ -2092,7 +2455,7 @@ async function inspectConfig(options) {
2092
2455
  platform: cfg.platformUrl,
2093
2456
  scope: "app:config:read",
2094
2457
  token: options.token,
2095
- tokenFile: join3(cfg.rootDir, ".odla", "admin-token.local.json"),
2458
+ tokenFile: join4(cfg.rootDir, ".odla", "admin-token.local.json"),
2096
2459
  rootDir: cfg.rootDir,
2097
2460
  email: options.email,
2098
2461
  open: options.open,
@@ -2101,7 +2464,7 @@ async function inspectConfig(options) {
2101
2464
  openApprovalUrl: options.openApprovalUrl,
2102
2465
  label: `odla CLI (${cfg.app.id} config read)`
2103
2466
  });
2104
- const client = createAppsClient({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
2467
+ const client = createAppsClient2({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
2105
2468
  const observed = await client.resolveApp(cfg.app.id);
2106
2469
  return reconcileConfig({
2107
2470
  desired: desiredRegistryState(cfg),
@@ -2137,7 +2500,7 @@ function printPlan(document, options) {
2137
2500
  }
2138
2501
  if (!document.actions.length) out.log(" no managed changes");
2139
2502
  out.log(`plan digest: ${document.planDigest}`);
2140
- out.log(`apply: unsupported \u2014 ${document.apply.reason}`);
2503
+ out.log(`apply: ${document.apply.supported ? "supported" : "blocked"} \u2014 ${document.apply.reason}`);
2141
2504
  printNext(out, document.nextActions);
2142
2505
  }
2143
2506
  function printHeader(out, kind, document) {
@@ -2145,6 +2508,7 @@ function printHeader(out, kind, document) {
2145
2508
  out.log(`platform: ${document.scope.platformUrl}`);
2146
2509
  out.log(`desired: ${document.desiredRevision}`);
2147
2510
  out.log(`observed: ${document.observedRevision ?? "absent"}`);
2511
+ out.log(`registry: ${document.registryRevision ?? "absent"}`);
2148
2512
  out.log(
2149
2513
  `summary: ${document.summary.differences} different, ${document.summary.unmanaged} unmanaged, ${document.summary.actions} planned actions`
2150
2514
  );
@@ -2177,6 +2541,13 @@ function diffNextActions(reconciliation, configPath) {
2177
2541
  }
2178
2542
  function planNextActions(reconciliation, configPath) {
2179
2543
  const next = [];
2544
+ if (configApplySupport(reconciliation).supported) {
2545
+ next.push({
2546
+ code: "apply_frozen_plan",
2547
+ command: "odla-ai config apply --plan <saved-plan.json> --json",
2548
+ description: "Save this JSON document, then conditionally apply these exact revision-bound actions."
2549
+ });
2550
+ }
2180
2551
  if (reconciliation.actions.some((action2) => action2.applySupport === "provision")) {
2181
2552
  next.push({
2182
2553
  code: "review_provision",
@@ -2212,13 +2583,13 @@ function quoteArg2(value2) {
2212
2583
 
2213
2584
  // src/doctor-checks.ts
2214
2585
  import { execFileSync } from "child_process";
2215
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2216
- import { join as join5, resolve as resolve3 } from "path";
2586
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
2587
+ import { join as join6, resolve as resolve3 } from "path";
2217
2588
 
2218
2589
  // src/wrangler.ts
2219
2590
  import { spawn as spawn2 } from "child_process";
2220
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
2221
- import { join as join4 } from "path";
2591
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
2592
+ import { join as join5 } from "path";
2222
2593
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
2223
2594
  const child = spawn2(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
2224
2595
  let stdout = "";
@@ -2232,7 +2603,7 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
2232
2603
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
2233
2604
  function findWranglerConfig(rootDir) {
2234
2605
  for (const name of WRANGLER_CONFIG_FILES) {
2235
- const path = join4(rootDir, name);
2606
+ const path = join5(rootDir, name);
2236
2607
  if (existsSync4(path)) return path;
2237
2608
  }
2238
2609
  return null;
@@ -2240,7 +2611,7 @@ function findWranglerConfig(rootDir) {
2240
2611
  function readWranglerConfig(path) {
2241
2612
  if (path.endsWith(".toml")) return null;
2242
2613
  try {
2243
- return JSON.parse(stripJsonComments(readFileSync3(path, "utf8")));
2614
+ return JSON.parse(stripJsonComments(readFileSync5(path, "utf8")));
2244
2615
  } catch {
2245
2616
  return null;
2246
2617
  }
@@ -2341,7 +2712,7 @@ function wranglerWarnings(rootDir) {
2341
2712
  const dir = resolve3(rootDir, assets.directory);
2342
2713
  if (dir === resolve3(rootDir)) {
2343
2714
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
2344
- } else if (existsSync5(join5(dir, "node_modules"))) {
2715
+ } else if (existsSync5(join6(dir, "node_modules"))) {
2345
2716
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
2346
2717
  }
2347
2718
  }
@@ -2382,7 +2753,7 @@ function o11yProjectWarnings(rootDir) {
2382
2753
  } else {
2383
2754
  let source = "";
2384
2755
  try {
2385
- source = readFileSync4(main, "utf8");
2756
+ source = readFileSync6(main, "utf8");
2386
2757
  } catch {
2387
2758
  }
2388
2759
  if (!/\bwithObservability\b/.test(source)) {
@@ -2406,7 +2777,7 @@ function calendarProjectWarnings(rootDir) {
2406
2777
  }
2407
2778
  function readPackageJson(rootDir) {
2408
2779
  try {
2409
- return JSON.parse(readFileSync4(join5(rootDir, "package.json"), "utf8"));
2780
+ return JSON.parse(readFileSync6(join6(rootDir, "package.json"), "utf8"));
2410
2781
  } catch {
2411
2782
  return null;
2412
2783
  }
@@ -2616,7 +2987,7 @@ async function doctor(options) {
2616
2987
  // src/init.ts
2617
2988
  import { existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2618
2989
  import { dirname as dirname4, resolve as resolve4 } from "path";
2619
- import { appServiceDefinition as appServiceDefinition2, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
2990
+ import { appServiceDefinition as appServiceDefinition3, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
2620
2991
  function initProject(options) {
2621
2992
  const out = options.stdout ?? console;
2622
2993
  const rootDir = resolve4(options.rootDir ?? process.cwd());
@@ -2630,7 +3001,7 @@ function initProject(options) {
2630
3001
  const envs = options.envs?.length ? options.envs : ["dev"];
2631
3002
  const services = options.services?.length ? options.services : ["db", "ai"];
2632
3003
  for (const service of services) {
2633
- const definition = appServiceDefinition2(service);
3004
+ const definition = appServiceDefinition3(service);
2634
3005
  if (!definition) throw new Error(`--services contains unknown service "${service}" (known: ${appServiceIds3().join(", ")})`);
2635
3006
  for (const dependency of definition.requires) {
2636
3007
  if (!services.includes(dependency)) throw new Error(`--services ${service} requires ${dependency}`);
@@ -2881,9 +3252,9 @@ async function resolveVaultWrite(options) {
2881
3252
  }
2882
3253
 
2883
3254
  // src/skill.ts
2884
- import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync, writeFileSync as writeFileSync3 } from "fs";
3255
+ import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync, writeFileSync as writeFileSync3 } from "fs";
2885
3256
  import { homedir } from "os";
2886
- import { dirname as dirname5, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve5, sep } from "path";
3257
+ import { dirname as dirname5, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve5, sep } from "path";
2887
3258
  import { fileURLToPath } from "url";
2888
3259
 
2889
3260
  // src/skill-adapters.ts
@@ -2977,12 +3348,12 @@ function installSkill(options = {}) {
2977
3348
  plans.set(target, { target, content: content2, boundary, managedMerge });
2978
3349
  };
2979
3350
  const planSkillTree = (targetDir2, boundary = root) => {
2980
- for (const rel of files) plan(join6(targetDir2, rel), readFileSync5(join6(sourceDir, rel), "utf8"), false, boundary);
3351
+ for (const rel of files) plan(join7(targetDir2, rel), readFileSync7(join7(sourceDir, rel), "utf8"), false, boundary);
2981
3352
  };
2982
3353
  let targetDir;
2983
3354
  if (options.global) {
2984
- const claudeRoot = join6(home, ".claude", "skills");
2985
- const codexRoot = resolve5(options.codexHomeDir ?? process.env.CODEX_HOME ?? join6(home, ".codex"), "skills");
3355
+ const claudeRoot = join7(home, ".claude", "skills");
3356
+ const codexRoot = resolve5(options.codexHomeDir ?? process.env.CODEX_HOME ?? join7(home, ".codex"), "skills");
2986
3357
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
2987
3358
  for (const harness of harnesses) {
2988
3359
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
@@ -2990,35 +3361,35 @@ function installSkill(options = {}) {
2990
3361
  rememberTarget(harness, skillRoot);
2991
3362
  }
2992
3363
  } else {
2993
- const sharedRoot = join6(root, ".agents", "skills");
3364
+ const sharedRoot = join7(root, ".agents", "skills");
2994
3365
  planSkillTree(sharedRoot);
2995
- const claudeRoot = join6(root, ".claude", "skills");
3366
+ const claudeRoot = join7(root, ".claude", "skills");
2996
3367
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
2997
3368
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
2998
3369
  if (harnesses.includes("claude")) {
2999
3370
  for (const skill of skillNames(files)) {
3000
- const canonical = readFileSync5(join6(sourceDir, skill, "SKILL.md"), "utf8");
3001
- plan(join6(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3371
+ const canonical = readFileSync7(join7(sourceDir, skill, "SKILL.md"), "utf8");
3372
+ plan(join7(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical));
3002
3373
  }
3003
3374
  rememberTarget("claude", claudeRoot);
3004
3375
  }
3005
3376
  if (harnesses.includes("cursor")) {
3006
- const cursorRule = join6(root, ".cursor", "rules", "odla.mdc");
3377
+ const cursorRule = join7(root, ".cursor", "rules", "odla.mdc");
3007
3378
  plan(cursorRule, CURSOR_RULE);
3008
3379
  rememberTarget("cursor", cursorRule);
3009
3380
  }
3010
3381
  if (harnesses.includes("agents")) {
3011
- const agentsFile = join6(root, "AGENTS.md");
3382
+ const agentsFile = join7(root, "AGENTS.md");
3012
3383
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3013
3384
  rememberTarget("agents", agentsFile);
3014
3385
  }
3015
3386
  if (harnesses.includes("copilot")) {
3016
- const copilotFile = join6(root, ".github", "copilot-instructions.md");
3387
+ const copilotFile = join7(root, ".github", "copilot-instructions.md");
3017
3388
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3018
3389
  rememberTarget("copilot", copilotFile);
3019
3390
  }
3020
3391
  if (harnesses.includes("gemini")) {
3021
- const geminiFile = join6(root, "GEMINI.md");
3392
+ const geminiFile = join7(root, "GEMINI.md");
3022
3393
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
3023
3394
  rememberTarget("gemini", geminiFile);
3024
3395
  }
@@ -3036,7 +3407,7 @@ function installSkill(options = {}) {
3036
3407
  writtenPaths.add(file.target);
3037
3408
  continue;
3038
3409
  }
3039
- const current = readFileSync5(file.target, "utf8");
3410
+ const current = readFileSync7(file.target, "utf8");
3040
3411
  if (current === file.content) {
3041
3412
  unchangedPaths.add(file.target);
3042
3413
  } else if (file.managedMerge || options.force) {
@@ -3053,7 +3424,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
3053
3424
  );
3054
3425
  }
3055
3426
  for (const file of plans.values()) {
3056
- if (!existsSync7(file.target) || readFileSync5(file.target, "utf8") !== file.content) {
3427
+ if (!existsSync7(file.target) || readFileSync7(file.target, "utf8") !== file.content) {
3057
3428
  mkdirSync3(dirname5(file.target), { recursive: true });
3058
3429
  writeFileSync3(file.target, file.content);
3059
3430
  }
@@ -3098,7 +3469,7 @@ function managedFileContent(path, block, force, boundary) {
3098
3469
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
3099
3470
  if (!existsSync7(path)) return `${block}
3100
3471
  `;
3101
- const current = readFileSync5(path, "utf8");
3472
+ const current = readFileSync7(path, "utf8");
3102
3473
  const start = "<!-- odla-ai agent setup:start -->";
3103
3474
  const end = "<!-- odla-ai agent setup:end -->";
3104
3475
  const startAt = current.indexOf(start);
@@ -3125,7 +3496,7 @@ function symlinkedComponent(boundary, target) {
3125
3496
  }
3126
3497
  let current = boundary;
3127
3498
  for (const part of rel.split(sep).filter(Boolean)) {
3128
- current = join6(current, part);
3499
+ current = join7(current, part);
3129
3500
  try {
3130
3501
  if (lstatSync(current).isSymbolicLink()) return current;
3131
3502
  } catch (error) {
@@ -3142,7 +3513,7 @@ function listFiles(dir) {
3142
3513
  const results = [];
3143
3514
  const walk = (current) => {
3144
3515
  for (const entry of readdirSync(current, { withFileTypes: true })) {
3145
- const path = join6(current, entry.name);
3516
+ const path = join7(current, entry.name);
3146
3517
  if (entry.isDirectory()) walk(path);
3147
3518
  else results.push(relative2(dir, path));
3148
3519
  }
@@ -3533,7 +3904,7 @@ import { spawn as spawn3 } from "child_process";
3533
3904
  import { createHash as createHash2 } from "crypto";
3534
3905
  import { copyFile, mkdtemp, readFile, rm, writeFile } from "fs/promises";
3535
3906
  import { tmpdir } from "os";
3536
- import { join as join7 } from "path";
3907
+ import { join as join8 } from "path";
3537
3908
  import { fileURLToPath as fileURLToPath2 } from "url";
3538
3909
  var runCodeImageCommand = (command, args, stdio) => new Promise((accept, reject) => {
3539
3910
  const child = spawn3(command, [...args], { shell: false, stdio });
@@ -3592,10 +3963,10 @@ async function embeddedPiImageName() {
3592
3963
  return `odla-ai/pi-agent:embedded-sha256-${createHash2("sha256").update(bundle).digest("hex")}`;
3593
3964
  }
3594
3965
  async function buildEmbeddedPiImage(engine, image, run) {
3595
- const context = await mkdtemp(join7(tmpdir(), "odla-code-pi-"));
3966
+ const context = await mkdtemp(join8(tmpdir(), "odla-code-pi-"));
3596
3967
  try {
3597
- await copyFile(embeddedPiAssetPath(), join7(context, "pi-agent.js"));
3598
- await writeFile(join7(context, "Dockerfile"), [
3968
+ await copyFile(embeddedPiAssetPath(), join8(context, "pi-agent.js"));
3969
+ await writeFile(join8(context, "Dockerfile"), [
3599
3970
  `FROM ${CODE_NODE_IMAGE}`,
3600
3971
  "COPY pi-agent.js /opt/odla/pi-agent.js",
3601
3972
  "WORKDIR /workspace",
@@ -3621,7 +3992,7 @@ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
3621
3992
  var HarnessProtocolError = class extends Error {
3622
3993
  name = "HarnessProtocolError";
3623
3994
  };
3624
- function record2(value2) {
3995
+ function record4(value2) {
3625
3996
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
3626
3997
  }
3627
3998
  function boundedText(value2, label, max) {
@@ -3638,7 +4009,7 @@ function parseAgentOutput(line) {
3638
4009
  } catch {
3639
4010
  throw new HarnessProtocolError("agent emitted invalid JSON");
3640
4011
  }
3641
- const message2 = record2(value2);
4012
+ const message2 = record4(value2);
3642
4013
  if (!message2 || message2.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
3643
4014
  throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
3644
4015
  }
@@ -3651,7 +4022,7 @@ function parseAgentOutput(line) {
3651
4022
  };
3652
4023
  }
3653
4024
  if (message2.type === "inference.request") {
3654
- const call2 = record2(message2.call);
4025
+ const call2 = record4(message2.call);
3655
4026
  if (!call2 || !Array.isArray(call2.messages) || !Number.isSafeInteger(call2.maxTokens)) {
3656
4027
  throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
3657
4028
  }
@@ -3663,7 +4034,7 @@ function parseAgentOutput(line) {
3663
4034
  };
3664
4035
  }
3665
4036
  if (message2.type === "tool.request") {
3666
- const input = record2(message2.input);
4037
+ const input = record4(message2.input);
3667
4038
  const tool = String(message2.tool);
3668
4039
  if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
3669
4040
  throw new HarnessProtocolError("tool.request requires a registered tool and object input");
@@ -3698,7 +4069,7 @@ function encodeAgentInput(message2) {
3698
4069
  import { execFile as execFile2, spawn as spawn4 } from "child_process";
3699
4070
  import { constants } from "fs";
3700
4071
  import { access } from "fs/promises";
3701
- import { delimiter, join as join8 } from "path";
4072
+ import { delimiter, join as join9 } from "path";
3702
4073
  import { getgid, getuid } from "process";
3703
4074
  import { mkdir, mkdtemp as mkdtemp2, realpath, rm as rm2, writeFile as writeFile2 } from "fs/promises";
3704
4075
  import { tmpdir as tmpdir2 } from "os";
@@ -3716,7 +4087,7 @@ function assertPinnedImage(image) {
3716
4087
  async function commandAvailable(engine) {
3717
4088
  for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
3718
4089
  try {
3719
- await access(join8(directory, engine), constants.X_OK);
4090
+ await access(join9(directory, engine), constants.X_OK);
3720
4091
  return true;
3721
4092
  } catch {
3722
4093
  }
@@ -4006,8 +4377,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
4006
4377
  const maxFiles = options.maxFiles ?? 2e4;
4007
4378
  const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
4008
4379
  const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
4009
- const entries = inventory.flatMap((record8) => {
4010
- const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record8);
4380
+ const entries = inventory.flatMap((record10) => {
4381
+ const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record10);
4011
4382
  return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
4012
4383
  });
4013
4384
  if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
@@ -4255,8 +4626,8 @@ function normalize(value2) {
4255
4626
  if (Array.isArray(value2)) return value2.map(normalize);
4256
4627
  if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
4257
4628
  if (typeof value2 === "object") {
4258
- const record8 = value2;
4259
- return Object.fromEntries(Object.keys(record8).filter((key) => record8[key] !== void 0).sort().map((key) => [key, normalize(record8[key])]));
4629
+ const record10 = value2;
4630
+ return Object.fromEntries(Object.keys(record10).filter((key) => record10[key] !== void 0).sort().map((key) => [key, normalize(record10[key])]));
4260
4631
  }
4261
4632
  throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
4262
4633
  }
@@ -4335,7 +4706,7 @@ function normalizeReaders(readers) {
4335
4706
  }
4336
4707
 
4337
4708
  // ../camel/dist/code.js
4338
- var DIGEST = /^sha256:[0-9a-f]{64}$/;
4709
+ var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
4339
4710
  var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
4340
4711
  var ID = /^[A-Za-z0-9._:-]{1,160}$/;
4341
4712
  async function digestCodeVerificationReceipt(fields) {
@@ -4370,18 +4741,18 @@ async function digestCodeVerificationReceipt(fields) {
4370
4741
  return `sha256:${await sha256Hex(canonicalJson2(canonical))}`;
4371
4742
  }
4372
4743
  function validate(fields) {
4373
- 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) {
4744
+ 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) {
4374
4745
  throw new CamelError("state_conflict", "Code verification receipt is malformed or outside its bounds.");
4375
4746
  }
4376
4747
  const ids = /* @__PURE__ */ new Set();
4377
4748
  for (const recipe2 of fields.recipes) {
4378
- 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) {
4749
+ 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) {
4379
4750
  throw new CamelError("state_conflict", "Code verification recipe receipt is malformed or outside its bounds.");
4380
4751
  }
4381
4752
  const artifactIds = /* @__PURE__ */ new Set();
4382
4753
  if (recipe2.artifacts.length > 64) throw new CamelError("state_conflict", "Code verification has too many artifacts.");
4383
4754
  for (const artifact of recipe2.artifacts) {
4384
- 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)) {
4755
+ 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)) {
4385
4756
  throw new CamelError("state_conflict", "Code verification artifact receipt is malformed.");
4386
4757
  }
4387
4758
  artifactIds.add(artifact.artifactId);
@@ -4397,7 +4768,7 @@ function validate(fields) {
4397
4768
  }
4398
4769
  }
4399
4770
  var SHA2 = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
4400
- var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
4771
+ var DIGEST22 = /^sha256:[0-9a-f]{64}$/;
4401
4772
  var ID2 = /^[A-Za-z0-9._:-]{1,180}$/;
4402
4773
  var MAX_PATCH_BYTES = 256 * 1024;
4403
4774
  var MAX_STATE_BYTES = 64 * 1024;
@@ -4442,14 +4813,14 @@ function normalizeState(value2) {
4442
4813
  ]);
4443
4814
  const planCursor = state2.planCursor;
4444
4815
  const conversations = strings(state2.conversationRefs, "conversationRefs", ID2, 256, false);
4445
- const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST2, 256, true);
4446
- 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) {
4816
+ const approvals = strings(state2.unresolvedApprovals, "unresolvedApprovals", DIGEST22, 256, true);
4817
+ 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) {
4447
4818
  throw invalid2("Portable checkpoint state is malformed or outside its bounds.");
4448
4819
  }
4449
4820
  const effects = state2.completedEffects.map((item) => {
4450
4821
  const effect = object(item, "completed effect");
4451
4822
  exact2(effect, ["effectId", "actionDigest", "receiptDigest"]);
4452
- 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)) {
4823
+ 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)) {
4453
4824
  throw invalid2("Portable checkpoint effect receipt is malformed.");
4454
4825
  }
4455
4826
  return {
@@ -4558,7 +4929,7 @@ import { randomUUID } from "crypto";
4558
4929
  import { createHash as createHash22, randomUUID as randomUUID2 } from "crypto";
4559
4930
  import { createReadStream } from "fs";
4560
4931
  import { lstat as lstat22 } from "fs/promises";
4561
- import { join as join9 } from "path";
4932
+ import { join as join10 } from "path";
4562
4933
  import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
4563
4934
  import { tmpdir as tmpdir3 } from "os";
4564
4935
  import { dirname as dirname6, join as join23, resolve as resolve32, sep as sep23 } from "path";
@@ -4979,7 +5350,7 @@ function createCodeRuntimeControlClient(options) {
4979
5350
  }
4980
5351
  const value2 = await response2.json().catch(() => null);
4981
5352
  if (!response2.ok) {
4982
- const problem = record3(record3(value2)?.error);
5353
+ const problem = record5(record5(value2)?.error);
4983
5354
  throw new CodeRuntimeControlError(
4984
5355
  typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
4985
5356
  response2.status,
@@ -5001,12 +5372,12 @@ function createCodeRuntimeControlClient(options) {
5001
5372
  await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
5002
5373
  ),
5003
5374
  infer: async (sessionId, inference) => {
5004
- const value2 = record3(await call2(
5375
+ const value2 = record5(await call2(
5005
5376
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
5006
5377
  inference,
5007
5378
  modelRequestTimeoutMs
5008
5379
  ));
5009
- if (!value2 || value2.requestId !== inference.requestId || !record3(value2.response) || !record3(value2.receipt)) {
5380
+ if (!value2 || value2.requestId !== inference.requestId || !record5(value2.response) || !record5(value2.receipt)) {
5010
5381
  throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
5011
5382
  }
5012
5383
  return value2;
@@ -5064,12 +5435,12 @@ function validateHeartbeat(version, capabilities) {
5064
5435
  }
5065
5436
  }
5066
5437
  function parseSnapshot(value2) {
5067
- const root = record3(value2);
5068
- const host = record3(root?.host);
5438
+ const root = record5(value2);
5439
+ const host = record5(root?.host);
5069
5440
  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");
5070
5441
  const bindingIds = /* @__PURE__ */ new Set();
5071
5442
  const bindings = root.bindings.map((item) => {
5072
- const binding = record3(item);
5443
+ const binding = record5(item);
5073
5444
  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)) {
5074
5445
  throw invalid("binding");
5075
5446
  }
@@ -5079,10 +5450,10 @@ function parseSnapshot(value2) {
5079
5450
  const commandIds = /* @__PURE__ */ new Set();
5080
5451
  const commandSequences = /* @__PURE__ */ new Set();
5081
5452
  const commands = root.commands.map((item) => {
5082
- const command = record3(item);
5453
+ const command = record5(item);
5083
5454
  const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
5084
5455
  const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
5085
- 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");
5456
+ 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");
5086
5457
  commandIds.add(command.commandId);
5087
5458
  commandSequences.add(sequenceKey);
5088
5459
  return command;
@@ -5090,10 +5461,10 @@ function parseSnapshot(value2) {
5090
5461
  return { host, bindings, commands };
5091
5462
  }
5092
5463
  async function parseSource(value2) {
5093
- const snapshot = record3(record3(value2)?.snapshot);
5464
+ const snapshot = record5(record5(value2)?.snapshot);
5094
5465
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
5095
5466
  const files = snapshot.files.map((value22) => {
5096
- const file = record3(value22);
5467
+ const file = record5(value22);
5097
5468
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
5098
5469
  return { path: file.path, content: file.content };
5099
5470
  });
@@ -5102,11 +5473,11 @@ async function parseSource(value2) {
5102
5473
  const aliases = /* @__PURE__ */ new Set();
5103
5474
  const references = [];
5104
5475
  for (const item of referencesValue) {
5105
- const reference = record3(item);
5476
+ const reference = record5(item);
5106
5477
  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");
5107
5478
  aliases.add(reference.alias);
5108
5479
  const referenceFiles = reference.files.map((entry) => {
5109
- const file = record3(entry);
5480
+ const file = record5(entry);
5110
5481
  if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
5111
5482
  return { path: file.path, content: file.content };
5112
5483
  });
@@ -5121,18 +5492,18 @@ async function parseSource(value2) {
5121
5492
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
5122
5493
  }
5123
5494
  function parseReview(value2) {
5124
- const review = record3(record3(value2)?.review);
5495
+ const review = record5(record5(value2)?.review);
5125
5496
  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");
5126
5497
  return review;
5127
5498
  }
5128
5499
  function parseCandidate(value2) {
5129
- const candidate = record3(record3(value2)?.candidate);
5500
+ const candidate = record5(record5(value2)?.candidate);
5130
5501
  if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
5131
5502
  throw invalid("candidate");
5132
5503
  }
5133
5504
  return { candidateId: candidate.candidateId, status: candidate.status };
5134
5505
  }
5135
- var record3 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
5506
+ var record5 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
5136
5507
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
5137
5508
  var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
5138
5509
  var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
@@ -5477,7 +5848,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
5477
5848
  const receipts = [];
5478
5849
  for (const artifact of recipe2.expectedArtifacts ?? []) {
5479
5850
  try {
5480
- const path = join9(workspaceDir, artifact.path);
5851
+ const path = join10(workspaceDir, artifact.path);
5481
5852
  const info = await lstat22(path);
5482
5853
  if (!info.isFile() || info.isSymbolicLink()) {
5483
5854
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
@@ -6495,13 +6866,6 @@ var CodePiRuntimeEngine = class {
6495
6866
  }
6496
6867
  };
6497
6868
 
6498
- // src/version.ts
6499
- import { readFileSync as readFileSync6 } from "fs";
6500
- function cliVersion() {
6501
- const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
6502
- return pkg.version ?? "unknown";
6503
- }
6504
-
6505
6869
  // src/code-local-source.ts
6506
6870
  import { execFile as execFile3 } from "child_process";
6507
6871
  import { createHash as createHash4 } from "crypto";
@@ -6720,25 +7084,25 @@ async function runCodeRuntime(input) {
6720
7084
  }
6721
7085
  }
6722
7086
  function parseConnection(value2, appId, appEnv) {
6723
- const root = record4(value2);
6724
- const host = record4(root?.host);
6725
- const offer = record4(root?.offer);
6726
- const binding = record4(root?.binding);
7087
+ const root = record6(value2);
7088
+ const host = record6(root?.host);
7089
+ const offer = record6(root?.offer);
7090
+ const binding = record6(root?.binding);
6727
7091
  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)) {
6728
7092
  throw new Error("connect Code host returned an invalid response");
6729
7093
  }
6730
7094
  return root;
6731
7095
  }
6732
7096
  function apiFailure(action2, status, value2) {
6733
- const message2 = record4(record4(value2)?.error)?.message;
7097
+ const message2 = record6(record6(value2)?.error)?.message;
6734
7098
  return `${action2} failed (${status})${typeof message2 === "string" ? `: ${message2}` : ""}`;
6735
7099
  }
6736
- function record4(value2) {
7100
+ function record6(value2) {
6737
7101
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
6738
7102
  }
6739
7103
 
6740
7104
  // src/provision.ts
6741
- import { createAppsClient as createAppsClient2, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
7105
+ import { createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
6742
7106
  import { putSecret as putSecret2 } from "@odla-ai/ai";
6743
7107
  import process8 from "process";
6744
7108
 
@@ -6959,7 +7323,7 @@ async function provision(options) {
6959
7323
  }
6960
7324
  const doFetch = options.fetch ?? fetch;
6961
7325
  const token = await getDeveloperToken(cfg, options, doFetch, out);
6962
- const apps = createAppsClient2({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
7326
+ const apps = createAppsClient3({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
6963
7327
  const existing = await apps.resolveApp(cfg.app.id);
6964
7328
  if (existing) {
6965
7329
  out.log(`app: ${cfg.app.id} already exists`);
@@ -7133,7 +7497,7 @@ var COMMAND_SURFACE = {
7133
7497
  calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
7134
7498
  capabilities: {},
7135
7499
  code: { connect: {} },
7136
- config: { diff: {}, plan: {} },
7500
+ config: { diff: {}, plan: {}, apply: {} },
7137
7501
  context: { show: {}, list: {}, save: {}, remove: {} },
7138
7502
  // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
7139
7503
  discuss: {
@@ -7151,6 +7515,7 @@ var COMMAND_SURFACE = {
7151
7515
  help: {},
7152
7516
  init: {},
7153
7517
  o11y: { status: {} },
7518
+ operations: { get: {}, wait: {} },
7154
7519
  platform: { status: {} },
7155
7520
  pm: {
7156
7521
  ...PM_ENTITIES,
@@ -7588,17 +7953,17 @@ function addOption(options, name, value2) {
7588
7953
 
7589
7954
  // src/operator-context.ts
7590
7955
  import { existsSync as existsSync10 } from "fs";
7591
- import { join as join11, resolve as resolve11 } from "path";
7956
+ import { join as join12, resolve as resolve11 } from "path";
7592
7957
  import process10 from "process";
7593
7958
 
7594
7959
  // src/operator-profiles.ts
7595
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
7960
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
7596
7961
  import { homedir as homedir2 } from "os";
7597
- import { dirname as dirname7, join as join10, resolve as resolve10 } from "path";
7962
+ import { dirname as dirname7, join as join11, resolve as resolve10 } from "path";
7598
7963
  import process9 from "process";
7599
7964
  function operatorProfileFile() {
7600
7965
  return resolve10(
7601
- clean(process9.env.ODLA_CONTEXT_FILE) ?? join10(homedir2(), ".odla", "contexts.json")
7966
+ clean(process9.env.ODLA_CONTEXT_FILE) ?? join11(homedir2(), ".odla", "contexts.json")
7602
7967
  );
7603
7968
  }
7604
7969
  function resolveOperatorProfile(parsed) {
@@ -7643,10 +8008,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
7643
8008
  return true;
7644
8009
  }
7645
8010
  function operatorCredentialFiles(selection) {
7646
- const base = selection.name ? join10(dirname7(selection.file), "profiles", selection.name) : join10(homedir2(), ".odla");
8011
+ const base = selection.name ? join11(dirname7(selection.file), "profiles", selection.name) : join11(homedir2(), ".odla");
7647
8012
  return {
7648
- developer: join10(base, "dev-token.json"),
7649
- scoped: join10(base, "admin-token.local.json")
8013
+ developer: join11(base, "dev-token.json"),
8014
+ scoped: join11(base, "admin-token.local.json")
7650
8015
  };
7651
8016
  }
7652
8017
  function assertOperatorName(value2, label) {
@@ -7660,7 +8025,7 @@ function readOperatorProfiles(file) {
7660
8025
  if (!existsSync9(file)) return emptyProfiles();
7661
8026
  let raw;
7662
8027
  try {
7663
- raw = JSON.parse(readFileSync7(file, "utf8"));
8028
+ raw = JSON.parse(readFileSync8(file, "utf8"));
7664
8029
  } catch {
7665
8030
  throw new Error(`operator context file ${file} is not valid JSON`);
7666
8031
  }
@@ -7757,7 +8122,7 @@ async function resolveOperatorContext(parsed, options = {}) {
7757
8122
  const rootDir = loaded?.rootDir ?? process10.cwd();
7758
8123
  const profileCredentials = operatorCredentialFiles(profile);
7759
8124
  const tokenFile = clean2(process10.env.ODLA_DEV_TOKEN_FILE) ? resolve11(process10.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
7760
- const scopedTokenFile = clean2(process10.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process10.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join11(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
8125
+ const scopedTokenFile = clean2(process10.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process10.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join12(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
7761
8126
  const cfg = loaded ? {
7762
8127
  ...loaded,
7763
8128
  platformUrl: platformValue,
@@ -7779,8 +8144,8 @@ async function resolveOperatorContext(parsed, options = {}) {
7779
8144
  services: [],
7780
8145
  local: {
7781
8146
  tokenFile,
7782
- credentialsFile: join11(rootDir, ".odla", "credentials.local.json"),
7783
- devVarsFile: join11(rootDir, ".dev.vars"),
8147
+ credentialsFile: join12(rootDir, ".odla", "credentials.local.json"),
8148
+ devVarsFile: join12(rootDir, ".dev.vars"),
7784
8149
  gitignore: true
7785
8150
  }
7786
8151
  };
@@ -8024,7 +8389,7 @@ async function appExport(options) {
8024
8389
  }
8025
8390
 
8026
8391
  // src/app-import.ts
8027
- import { readFileSync as readFileSync8 } from "fs";
8392
+ import { readFileSync as readFileSync9 } from "fs";
8028
8393
  import {
8029
8394
  buildImportOps,
8030
8395
  importMutationId,
@@ -8048,7 +8413,7 @@ async function appImport(options) {
8048
8413
  const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
8049
8414
  const doFetch = options.fetch ?? fetch;
8050
8415
  const { tenant } = resolveTenant(cfg, options.env);
8051
- const text = options.file === "-" ? (options.readStdin ?? (() => readFileSync8(0, "utf8")))() : readFileSync8(options.file, "utf8");
8416
+ const text = options.file === "-" ? (options.readStdin ?? (() => readFileSync9(0, "utf8")))() : readFileSync9(options.file, "utf8");
8052
8417
  const { format, sources } = parseImport(text, options.ns);
8053
8418
  if (format === "namespace-map" && options.ns) {
8054
8419
  throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
@@ -8559,10 +8924,14 @@ async function secretsCommand(parsed, deps) {
8559
8924
  async function projectCommand(command, parsed, deps) {
8560
8925
  if (command === "config") {
8561
8926
  const sub = parsed.positionals[1];
8562
- if (sub !== "diff" && sub !== "plan") {
8927
+ if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
8563
8928
  throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
8564
8929
  }
8565
- assertArgs(parsed, ["config", "token", "email", "open", "json"], 2);
8930
+ assertArgs(
8931
+ parsed,
8932
+ sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
8933
+ 2
8934
+ );
8566
8935
  const options = {
8567
8936
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
8568
8937
  token: stringOpt(parsed.options.token),
@@ -8574,7 +8943,42 @@ async function projectCommand(command, parsed, deps) {
8574
8943
  stdout: deps.stdout
8575
8944
  };
8576
8945
  if (sub === "diff") await configDiff(options);
8577
- else await configPlan(options);
8946
+ else if (sub === "plan") await configPlan(options);
8947
+ else await configApply({
8948
+ ...options,
8949
+ planPath: requiredString(parsed.options.plan, "--plan"),
8950
+ idempotencyKey: stringOpt(parsed.options["idempotency-key"])
8951
+ });
8952
+ return true;
8953
+ }
8954
+ if (command === "operations") {
8955
+ const sub = parsed.positionals[1];
8956
+ if (sub !== "get" && sub !== "wait") {
8957
+ throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
8958
+ }
8959
+ assertArgs(
8960
+ parsed,
8961
+ sub === "wait" ? ["config", "token", "email", "open", "json", "interval", "timeout"] : ["config", "token", "email", "open", "json"],
8962
+ 3
8963
+ );
8964
+ const operationId = parsed.positionals[2];
8965
+ if (!operationId) throw new Error(`operation id is required`);
8966
+ const options = {
8967
+ configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
8968
+ operationId,
8969
+ token: stringOpt(parsed.options.token),
8970
+ email: stringOpt(parsed.options.email),
8971
+ open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
8972
+ json: parsed.options.json === true,
8973
+ fetch: deps.fetch,
8974
+ openApprovalUrl: deps.openUrl,
8975
+ stdout: deps.stdout,
8976
+ pollWait: deps.pollWait,
8977
+ intervalSeconds: numberOpt(parsed.options.interval, "--interval"),
8978
+ timeoutSeconds: numberOpt(parsed.options.timeout, "--timeout")
8979
+ };
8980
+ if (sub === "get") await configOperationGet(options);
8981
+ else await configOperationWait(options);
8578
8982
  return true;
8579
8983
  }
8580
8984
  if (command === "init") {
@@ -8845,8 +9249,9 @@ Usage:
8845
9249
  odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
8846
9250
  odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod]
8847
9251
  odla-ai doctor [--config odla.config.mjs]
8848
- odla-ai config diff [--config odla.config.mjs] [--email <odla-account>] [--json]
8849
- odla-ai config plan [--config odla.config.mjs] [--email <odla-account>] [--json]
9252
+ odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
9253
+ odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
9254
+ odla-ai operations <get|wait> <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
8850
9255
  odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
8851
9256
  odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
8852
9257
  odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
@@ -8971,8 +9376,8 @@ Commands:
8971
9376
  setup Install offline odla runbooks for common coding-agent harnesses.
8972
9377
  init Create a generic odla.config.mjs plus starter schema/rules files.
8973
9378
  doctor Validate and summarize the project config without network calls.
8974
- config Read-only diff and revision-bound plan for checked-in Registry
8975
- intent; runtime-owned fields and excluded coverage stay explicit.
9379
+ config Diff Registry intent, freeze a CAS-bound plan, and conditionally apply its safe actions.
9380
+ operations Inspect or wait on one exact, durable config-operation receipt.
8976
9381
  calendar Inspect, connect, or disconnect the live Google booking connection.
8977
9382
  app Archive (suspend, data retained), restore, export, import, or
8978
9383
  manage the co-owners of the app. Archiving takes every
@@ -9679,8 +10084,8 @@ async function pmAdd(ctx, entity, parsed) {
9679
10084
  emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
9680
10085
  }
9681
10086
  async function pmGet(ctx, entity, id) {
9682
- const { record: record8 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
9683
- emit2(ctx, record8, () => printRecord(ctx, entity, record8));
10087
+ const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
10088
+ emit2(ctx, record10, () => printRecord(ctx, entity, record10));
9684
10089
  }
9685
10090
  async function pmSet(ctx, entity, id, parsed) {
9686
10091
  const patch2 = collectEntityFields(entity, parsed, true);
@@ -9725,9 +10130,9 @@ async function pmHandoff(ctx, parsed) {
9725
10130
  ]);
9726
10131
  const handoff = {
9727
10132
  appId,
9728
- unmetGoals: goals.filter((record8) => record8.status !== "met"),
9729
- activeTasks: tasks.filter((record8) => record8.column !== "done"),
9730
- openBugs: bugs.filter((record8) => record8.status !== "fixed" && record8.status !== "wontfix")
10133
+ unmetGoals: goals.filter((record10) => record10.status !== "met"),
10134
+ activeTasks: tasks.filter((record10) => record10.column !== "done"),
10135
+ openBugs: bugs.filter((record10) => record10.status !== "fixed" && record10.status !== "wontfix")
9731
10136
  };
9732
10137
  const result = {
9733
10138
  ...handoff,
@@ -9746,10 +10151,10 @@ async function pmHandoff(ctx, parsed) {
9746
10151
  ]) {
9747
10152
  ctx.out.log(`${label}:`);
9748
10153
  if (!records.length) ctx.out.log("- (none)");
9749
- else for (const record8 of records) printRecord(
10154
+ else for (const record10 of records) printRecord(
9750
10155
  ctx,
9751
10156
  label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
9752
- record8
10157
+ record10
9753
10158
  );
9754
10159
  }
9755
10160
  });
@@ -9990,17 +10395,17 @@ async function platformCommand(parsed, deps = {}) {
9990
10395
  }
9991
10396
  }
9992
10397
  function isPlatformStatus(value2) {
9993
- if (!record5(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
9994
- if (!record5(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
9995
- if (!record5(value2.catalog) || !record5(value2.summary)) return false;
10398
+ if (!record7(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
10399
+ if (!record7(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
10400
+ if (!record7(value2.catalog) || !record7(value2.summary)) return false;
9996
10401
  return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
9997
10402
  }
9998
10403
  function apiMessage(value2) {
9999
- if (!record5(value2)) return "request failed";
10000
- const error = record5(value2.error) ? value2.error : value2;
10404
+ if (!record7(value2)) return "request failed";
10405
+ const error = record7(value2.error) ? value2.error : value2;
10001
10406
  return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
10002
10407
  }
10003
- function record5(value2) {
10408
+ function record7(value2) {
10004
10409
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
10005
10410
  }
10006
10411
 
@@ -10041,7 +10446,7 @@ function statusVerdict(reads) {
10041
10446
  severity: "degraded"
10042
10447
  });
10043
10448
  }
10044
- const performance = record6(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
10449
+ const performance = record8(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
10045
10450
  if (performance?.status === "unavailable") {
10046
10451
  reasons.push({
10047
10452
  source: "liveSync",
@@ -10122,7 +10527,7 @@ function statusVerdict(reads) {
10122
10527
  reasons
10123
10528
  };
10124
10529
  }
10125
- function record6(value2) {
10530
+ function record8(value2) {
10126
10531
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
10127
10532
  }
10128
10533
  function numeric2(value2) {
@@ -10150,7 +10555,7 @@ function printO11yStatus(status, out) {
10150
10555
  out.log(
10151
10556
  `o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
10152
10557
  );
10153
- const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record7) : [];
10558
+ const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record9) : [];
10154
10559
  const requests = routes.reduce(
10155
10560
  (total, row) => total + numeric3(row.requests),
10156
10561
  0
@@ -10162,39 +10567,39 @@ function printO11yStatus(status, out) {
10162
10567
  out.log(
10163
10568
  `application ${status.application.httpStatus} ${requests} requests ${errors} errors`
10164
10569
  );
10165
- const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record7) : [];
10570
+ const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record9) : [];
10166
10571
  out.log(
10167
10572
  `application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
10168
10573
  (row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
10169
10574
  ).join(", ") : "none observed"}`
10170
10575
  );
10171
10576
  out.log(liveSyncLine(status.liveSync));
10172
- const canaryDurations = record7(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
10577
+ const canaryDurations = record9(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
10173
10578
  out.log(
10174
10579
  `canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
10175
10580
  );
10176
- const collectorIngest = record7(status.collector.body.ingest) ? status.collector.body.ingest : {};
10177
- const collectorStorage = record7(collectorIngest.storage) ? collectorIngest.storage : {};
10581
+ const collectorIngest = record9(status.collector.body.ingest) ? status.collector.body.ingest : {};
10582
+ const collectorStorage = record9(collectorIngest.storage) ? collectorIngest.storage : {};
10178
10583
  out.log(
10179
10584
  `collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
10180
10585
  );
10181
- const providerMetrics = record7(status.provider.body.metrics) ? status.provider.body.metrics : {};
10182
- const providerCapacity = record7(status.provider.body.capacity) ? status.provider.body.capacity : {};
10183
- const workerMemory = record7(providerCapacity.memory) ? providerCapacity.memory : {};
10586
+ const providerMetrics = record9(status.provider.body.metrics) ? status.provider.body.metrics : {};
10587
+ const providerCapacity = record9(status.provider.body.capacity) ? status.provider.body.capacity : {};
10588
+ const workerMemory = record9(providerCapacity.memory) ? providerCapacity.memory : {};
10184
10589
  out.log(
10185
10590
  `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`
10186
10591
  );
10187
10592
  for (const line of providerCapacityLines(status.providerCapacity)) {
10188
10593
  out.log(line);
10189
10594
  }
10190
- const coverage = record7(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
10191
- const coverageCounts = record7(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
10192
- const coverageBudget = record7(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
10595
+ const coverage = record9(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
10596
+ const coverageCounts = record9(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
10597
+ const coverageBudget = record9(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
10193
10598
  out.log(
10194
10599
  `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`
10195
10600
  );
10196
10601
  const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
10197
- const providerFreshness = record7(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
10602
+ const providerFreshness = record9(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
10198
10603
  out.log(
10199
10604
  `cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
10200
10605
  );
@@ -10203,17 +10608,17 @@ function printO11yStatus(status, out) {
10203
10608
  );
10204
10609
  }
10205
10610
  function providerCapacityLines(read3) {
10206
- const resources = record7(read3.body.resources) ? read3.body.resources : {};
10207
- const durableObjects = record7(resources.durableObjects) ? resources.durableObjects : {};
10208
- const periodic = record7(durableObjects.periodic) ? durableObjects.periodic : {};
10209
- const storage = record7(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
10210
- const d1 = record7(resources.d1) ? resources.d1 : {};
10211
- const d1Activity = record7(d1.activity) ? d1.activity : {};
10212
- const d1Storage = record7(d1.storage) ? d1.storage : {};
10213
- const d1Latency = record7(d1Activity.latency) ? d1Activity.latency : {};
10214
- const r2 = record7(resources.r2) ? resources.r2 : {};
10215
- const r2Operations = record7(r2.operations) ? r2.operations : {};
10216
- const r2Storage = record7(r2.storage) ? r2.storage : {};
10611
+ const resources = record9(read3.body.resources) ? read3.body.resources : {};
10612
+ const durableObjects = record9(resources.durableObjects) ? resources.durableObjects : {};
10613
+ const periodic = record9(durableObjects.periodic) ? durableObjects.periodic : {};
10614
+ const storage = record9(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
10615
+ const d1 = record9(resources.d1) ? resources.d1 : {};
10616
+ const d1Activity = record9(d1.activity) ? d1.activity : {};
10617
+ const d1Storage = record9(d1.storage) ? d1.storage : {};
10618
+ const d1Latency = record9(d1Activity.latency) ? d1Activity.latency : {};
10619
+ const r2 = record9(resources.r2) ? resources.r2 : {};
10620
+ const r2Operations = record9(r2.operations) ? r2.operations : {};
10621
+ const r2Storage = record9(r2.storage) ? r2.storage : {};
10217
10622
  const status = String(
10218
10623
  read3.body.status ?? read3.body.error ?? "unavailable"
10219
10624
  );
@@ -10224,11 +10629,11 @@ function providerCapacityLines(read3) {
10224
10629
  ];
10225
10630
  }
10226
10631
  function liveSyncLine(read3) {
10227
- const performance = record7(read3.body.performance) ? read3.body.performance : {};
10228
- const commitToSend = record7(performance.commitToSend) ? performance.commitToSend : {};
10632
+ const performance = record9(read3.body.performance) ? read3.body.performance : {};
10633
+ const commitToSend = record9(performance.commitToSend) ? performance.commitToSend : {};
10229
10634
  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`;
10230
10635
  }
10231
- function record7(value2) {
10636
+ function record9(value2) {
10232
10637
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
10233
10638
  }
10234
10639
  function numeric3(value2) {
@@ -10429,7 +10834,7 @@ function recordInvocation(parsed) {
10429
10834
  }
10430
10835
 
10431
10836
  // src/runbook-actions.ts
10432
- import { readFileSync as readFileSync9 } from "fs";
10837
+ import { readFileSync as readFileSync10 } from "fs";
10433
10838
 
10434
10839
  // src/runbook-requires.ts
10435
10840
  var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
@@ -10514,7 +10919,7 @@ async function bySlug(ctx, slug) {
10514
10919
  function readBody(file, inline) {
10515
10920
  if (inline !== void 0) return inline;
10516
10921
  if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
10517
- return readFileSync9(file === "-" ? 0 : file, "utf8");
10922
+ return readFileSync10(file === "-" ? 0 : file, "utf8");
10518
10923
  }
10519
10924
  var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
10520
10925
  async function runbookList(ctx, all, query) {
@@ -10601,8 +11006,8 @@ async function runbookRemove(ctx, slug) {
10601
11006
  }
10602
11007
 
10603
11008
  // src/runbook-import.ts
10604
- import { readFileSync as readFileSync10, readdirSync as readdirSync2, statSync } from "fs";
10605
- import { basename as basename2, join as join12 } from "path";
11009
+ import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
11010
+ import { basename as basename2, join as join13 } from "path";
10606
11011
  function parseRunbook(text, slug) {
10607
11012
  let rest = text;
10608
11013
  const meta = {};
@@ -10632,7 +11037,7 @@ function readRunbookDir(dir) {
10632
11037
  if (!files.length) throw new Error(`no .md files in ${dir}`);
10633
11038
  return files.map((file) => {
10634
11039
  const slug = basename2(file, ".md");
10635
- const parsed = parseRunbook(readFileSync10(join12(dir, file), "utf8"), slug);
11040
+ const parsed = parseRunbook(readFileSync11(join13(dir, file), "utf8"), slug);
10636
11041
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
10637
11042
  });
10638
11043
  }
@@ -10704,8 +11109,8 @@ async function upsert(ctx, r, visibility) {
10704
11109
 
10705
11110
  // src/runbook-impact.ts
10706
11111
  import { execFileSync as execFileSync2 } from "child_process";
10707
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
10708
- import { join as join13 } from "path";
11112
+ import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
11113
+ import { join as join14 } from "path";
10709
11114
 
10710
11115
  // src/runbook-impact-scan.ts
10711
11116
  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$]*)/;
@@ -10874,10 +11279,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
10874
11279
  }
10875
11280
  function manifestLabeller(root) {
10876
11281
  return (workspace) => {
10877
- const manifest = join13(root, workspace, "package.json");
11282
+ const manifest = join14(root, workspace, "package.json");
10878
11283
  if (!existsSync11(manifest)) return void 0;
10879
11284
  try {
10880
- const name = JSON.parse(readFileSync11(manifest, "utf8")).name;
11285
+ const name = JSON.parse(readFileSync12(manifest, "utf8")).name;
10881
11286
  return typeof name === "string" ? name : void 0;
10882
11287
  } catch {
10883
11288
  return void 0;
@@ -10944,7 +11349,7 @@ function report3(ctx, impacts) {
10944
11349
  async function runbookImpact(ctx, options, deps = {}) {
10945
11350
  const cwd = deps.cwd ?? process.cwd();
10946
11351
  const runGit = deps.runGit ?? gitRunner(cwd);
10947
- const read3 = deps.readRepoFile ?? ((path) => readFileSync11(join13(cwd, path), "utf8"));
11352
+ const read3 = deps.readRepoFile ?? ((path) => readFileSync12(join14(cwd, path), "utf8"));
10948
11353
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
10949
11354
  if (!surfaces.length) {
10950
11355
  return ctx.out.log(
@@ -11077,9 +11482,9 @@ async function runbookComment(ctx, slug, body) {
11077
11482
 
11078
11483
  // src/runbook-editor.ts
11079
11484
  import { spawnSync } from "child_process";
11080
- import { mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
11485
+ import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
11081
11486
  import { tmpdir as tmpdir4 } from "os";
11082
- import { join as join14 } from "path";
11487
+ import { join as join15 } from "path";
11083
11488
  import process13 from "process";
11084
11489
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
11085
11490
  function resolveEditor(env = process13.env) {
@@ -11105,13 +11510,13 @@ function editText(initial, slug, deps = {}) {
11105
11510
  );
11106
11511
  if (!interactive())
11107
11512
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
11108
- const dir = mkdtempSync(join14(tmpdir4(), "odla-runbook-"));
11109
- const file = join14(dir, `${slug}.md`);
11513
+ const dir = mkdtempSync(join15(tmpdir4(), "odla-runbook-"));
11514
+ const file = join15(dir, `${slug}.md`);
11110
11515
  try {
11111
11516
  writeFileSync4(file, initial, { mode: 384 });
11112
11517
  const code = defaultRunOrInjected(deps)(editor, file);
11113
11518
  if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
11114
- const edited = readFileSync12(file, "utf8");
11519
+ const edited = readFileSync13(file, "utf8");
11115
11520
  return edited === initial ? null : edited;
11116
11521
  } finally {
11117
11522
  rmSync2(dir, { recursive: true, force: true });
@@ -11805,11 +12210,11 @@ async function securityStatus(parsed, dependencies) {
11805
12210
  // src/cli.ts
11806
12211
  function exitCodeFor(err) {
11807
12212
  const code = err?.code;
11808
- if (code === "handshake_pending" || code === "watch_timeout") return 75;
12213
+ if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
11809
12214
  if (code === "checkpoint_required") return 3;
11810
12215
  if (code === "remote_unavailable") return 6;
11811
12216
  if (code === "auth_failed") return 5;
11812
- if (code === "invalid_cursor") return 2;
12217
+ if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
11813
12218
  return 1;
11814
12219
  }
11815
12220
  async function runCli(argv = process.argv.slice(2), dependencies = {}) {
@@ -11964,7 +12369,11 @@ export {
11964
12369
  calendarDisconnect,
11965
12370
  CAPABILITIES,
11966
12371
  printCapabilities,
12372
+ ConfigOperationCommandError,
11967
12373
  desiredRegistryState,
12374
+ configApply,
12375
+ configOperationGet,
12376
+ configOperationWait,
11968
12377
  reconcileConfig,
11969
12378
  configDiff,
11970
12379
  configPlan,
@@ -12005,4 +12414,4 @@ export {
12005
12414
  exitCodeFor,
12006
12415
  runCli
12007
12416
  };
12008
- //# sourceMappingURL=chunk-2AFAXLKE.js.map
12417
+ //# sourceMappingURL=chunk-JJ42NV7X.js.map