@kici-dev/orchestrator 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -254,6 +254,32 @@ var AdminApiClient = class {
254
254
  return this.request("DELETE", `/api/v1/admin/secrets/${encodeURIComponent(orgId)}/${encodeURIComponent(scope)}/${encodeURIComponent(key)}`);
255
255
  }
256
256
  /**
257
+ * Create an empty secret scope. A `<backend>:` qualifier selects the
258
+ * backend; an unqualified scope targets the PG backend. Creating a scope
259
+ * that already exists succeeds and changes nothing.
260
+ */
261
+ async createScope(orgId, scope) {
262
+ return this.request("POST", "/api/v1/admin/secrets/scopes", {
263
+ orgId,
264
+ scope
265
+ });
266
+ }
267
+ /**
268
+ * Rename a secret scope inside its backend. The route refuses a move between
269
+ * backends and a rename onto a scope that already exists.
270
+ */
271
+ async renameScope(orgId, oldScope, newScope) {
272
+ return this.request("PUT", "/api/v1/admin/secrets/scopes/rename", {
273
+ orgId,
274
+ oldScope,
275
+ newScope
276
+ });
277
+ }
278
+ /** Delete a secret scope and every secret in it. */
279
+ async deleteScope(orgId, scope) {
280
+ return this.request("DELETE", `/api/v1/admin/secrets/scopes/${encodeURIComponent(orgId)}/${encodeURIComponent(scope)}`);
281
+ }
282
+ /**
257
283
  * Create (or upsert) a deployment context. `allowLocalExecution` lets
258
284
  * CLI-initiated / local runs resolve secrets through this context.
259
285
  */
@@ -1284,7 +1310,7 @@ var AuditLogger = class {
1284
1310
  * engine's `shouldRecordSecretResolve` helper) since they're written
1285
1311
  * once per JOB-step secret resolution and dwarf every other action's
1286
1312
  * volume. Denied resolves and every other action (`setSecret`,
1287
- * `deleteSecret`, `rotateKey`, `secret-outputs.reveal`) bypass the
1313
+ * `deleteSecret`, the scope writes, `rotateKey`, `secret-outputs.reveal`) bypass the
1288
1314
  * sampler — they are transactional with the mutation and must always
1289
1315
  * land.
1290
1316
  *
@@ -1494,6 +1520,48 @@ async function warnIfContextUnbound(args) {
1494
1520
  if (found && type?.success && found.bindings.length === 0) (args.warn ?? console.error)(unboundContextWarning(args.orgId, args.name, type.data));
1495
1521
  }
1496
1522
  //#endregion
1523
+ //#region src/cli/commands/shared/confirm.ts
1524
+ /**
1525
+ * Yes/no confirmation prompt for the `kici-admin` verbs that change or destroy
1526
+ * state unless the operator passes `--yes`.
1527
+ */
1528
+ /** Message of the error raised when stdin ends before the operator answers. */
1529
+ const NO_CONFIRMATION_ANSWER = "no answer to the confirmation prompt: stdin closed before a line arrived. Pass --yes to run without the prompt.";
1530
+ /**
1531
+ * Write `prompt` and read one line of answer.
1532
+ *
1533
+ * Resolves true when the line is `y` or `yes` (any case, surrounding spaces
1534
+ * ignored) and false for any other line. Rejects with
1535
+ * {@link NO_CONFIRMATION_ANSWER} when stdin ends before a line arrives, as it
1536
+ * does for a script, a cron job or a `< /dev/null` redirect. Resolving false
1537
+ * there would let a verb run without `--yes` exit 0 having done nothing, and
1538
+ * the script that ran it would read that as success.
1539
+ */
1540
+ function confirmPrompt(prompt, io = {
1541
+ input: process.stdin,
1542
+ output: process.stderr
1543
+ }) {
1544
+ const rl = createInterface({
1545
+ input: io.input,
1546
+ output: io.output
1547
+ });
1548
+ return new Promise((resolve, reject) => {
1549
+ let answered = false;
1550
+ const settle = (answer) => {
1551
+ if (answered) return;
1552
+ answered = true;
1553
+ rl.close();
1554
+ const normalized = answer.trim().toLowerCase();
1555
+ resolve(normalized === "y" || normalized === "yes");
1556
+ };
1557
+ rl.once("close", () => {
1558
+ if (!answered) reject(/* @__PURE__ */ new Error(NO_CONFIRMATION_ANSWER));
1559
+ });
1560
+ rl.once("line", settle);
1561
+ rl.question(prompt, settle);
1562
+ });
1563
+ }
1564
+ //#endregion
1497
1565
  //#region src/cli/commands/secret.ts
1498
1566
  function resolveDirectDbUrl$10(explicit) {
1499
1567
  return explicit ?? process.env.KICI_DATABASE_URL ?? null;
@@ -1658,7 +1726,7 @@ function registerSecretCommands(program, getClient) {
1658
1726
  sec.command("delete <orgId> <scope> <key>").description("Delete a secret").option("--yes", "Skip confirmation prompt").action(async (orgId, scope, key, opts) => {
1659
1727
  try {
1660
1728
  if (!opts.yes) {
1661
- if (!await confirm$4(`Are you sure you want to delete secret '${key}' from scope '${scope}'?`)) {
1729
+ if (!await confirmPrompt(`Are you sure you want to delete secret '${key}' from scope '${scope}'? [y/N] `)) {
1662
1730
  console.log("Aborted.");
1663
1731
  return;
1664
1732
  }
@@ -1670,6 +1738,7 @@ function registerSecretCommands(program, getClient) {
1670
1738
  process.exit(1);
1671
1739
  }
1672
1740
  });
1741
+ registerSecretScopeCommands(sec, getClient);
1673
1742
  sec.command("fix-prefixed-scopes <orgId>").description("Repair PG-backend secret scopes stored with a stale pg: qualifier (direct-DB). Each affected scope is renamed to its bare path, re-encrypting every secret under the corrected AAD. Exits 2 when any scope needs manual repair.").option("--dry-run", "Print the plan and exit without writing").option("--database-url <url>", "Orchestrator DB URL (or KICI_DATABASE_URL)").action(async (orgId, opts) => {
1674
1743
  const dbUrl = resolveDirectDbUrl$10(opts.databaseUrl);
1675
1744
  if (!dbUrl) {
@@ -1720,17 +1789,48 @@ function registerSecretCommands(program, getClient) {
1720
1789
  if (skipped > 0) process.exit(2);
1721
1790
  });
1722
1791
  }
1723
- async function confirm$4(message) {
1724
- const { createInterface } = await import("node:readline");
1725
- const rl = createInterface({
1726
- input: process.stdin,
1727
- output: process.stderr
1792
+ /**
1793
+ * `secret scope create|rename|delete` — the operator path for the dashboard's
1794
+ * scope writes (`secrets.scope.*` in the dashboard-write policy). HTTP only,
1795
+ * like `secret scopes`: each verb calls the orchestrator admin API.
1796
+ */
1797
+ function registerSecretScopeCommands(sec, getClient) {
1798
+ const scope = sec.command("scope").description("Create, rename or delete a secret scope");
1799
+ scope.command("create <orgId> <scope>").description("Create an empty secret scope. A <backend>: qualifier selects the backend; an unqualified scope targets the PG backend. An existing scope stays unchanged.").option("--json", "Emit JSON output").action(async (orgId, scopeName, opts) => {
1800
+ try {
1801
+ const result = await getClient().createScope(orgId, scopeName);
1802
+ if (opts.json) console.log(JSON.stringify(result));
1803
+ else console.log(`Secret scope '${scopeName}' created for org ${orgId}.`);
1804
+ } catch (err) {
1805
+ console.error(`Error: ${toErrorMessage(err)}`);
1806
+ process.exit(1);
1807
+ }
1728
1808
  });
1729
- return new Promise((resolve) => {
1730
- rl.question(`${message} [y/N] `, (answer) => {
1731
- rl.close();
1732
- resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
1733
- });
1809
+ scope.command("rename <orgId> <oldScope> <newScope>").description("Rename a secret scope inside its backend. Refuses a move between backends and a rename onto a scope that already exists.").option("--json", "Emit JSON output").action(async (orgId, oldScope, newScope, opts) => {
1810
+ try {
1811
+ const result = await getClient().renameScope(orgId, oldScope, newScope);
1812
+ if (opts.json) console.log(JSON.stringify(result));
1813
+ else console.log(`Secret scope '${oldScope}' renamed to '${newScope}' for org ${orgId}.`);
1814
+ } catch (err) {
1815
+ console.error(`Error: ${toErrorMessage(err)}`);
1816
+ process.exit(1);
1817
+ }
1818
+ });
1819
+ scope.command("delete <orgId> <scope>").description("Delete a secret scope and every secret in it").option("--yes", "Skip confirmation prompt").option("--json", "Emit JSON output").action(async (orgId, scopeName, opts) => {
1820
+ try {
1821
+ if (!opts.yes) {
1822
+ if (!await confirmPrompt(`Are you sure you want to delete scope '${scopeName}' and every secret in it? [y/N] `)) {
1823
+ console.log("Aborted.");
1824
+ return;
1825
+ }
1826
+ }
1827
+ const result = await getClient().deleteScope(orgId, scopeName);
1828
+ if (opts.json) console.log(JSON.stringify(result));
1829
+ else console.log(`Secret scope '${scopeName}' deleted for org ${orgId}.`);
1830
+ } catch (err) {
1831
+ console.error(`Error: ${toErrorMessage(err)}`);
1832
+ process.exit(1);
1833
+ }
1734
1834
  });
1735
1835
  }
1736
1836
  //#endregion
@@ -1808,7 +1908,7 @@ function formatAuditTable(entries) {
1808
1908
  ].join("\n");
1809
1909
  }
1810
1910
  function registerAuditCommands(program, getClient) {
1811
- program.command("audit").description("Query the secrets audit log").option("--context <name>", "Filter by context name").option("--routing-key <rk>", "Filter by routing key (required for cold-store scan)").option("--action <action>", "Filter by action type").option("--from <date>", "From date (ISO 8601)").option("--to <date>", "To date (ISO 8601)").option("--limit <n>", "Max entries to return", "100").option("--offset <n>", "Offset for pagination").option("--include-archived", "Include rows from cold storage (Phase D)", false).action(async (opts) => {
1911
+ program.command("audit").description("Query the secrets audit log").option("--context <name>", "Filter by context name").option("--routing-key <rk>", "Filter by routing key (required for cold-store scan)").option("--action <action>", "Filter by action type").option("--from <date>", "From date (ISO 8601)").option("--to <date>", "To date (ISO 8601)").option("--limit <n>", "Max entries to return", "100").option("--offset <n>", "Offset for pagination").option("--include-archived", "Include rows from cold storage", false).action(async (opts) => {
1812
1912
  try {
1813
1913
  const entries = await getClient().queryAudit({
1814
1914
  contextName: opts.context,
@@ -13768,7 +13868,7 @@ function resolveDatabaseUrl$7(explicit) {
13768
13868
  if (!url) throw new Error("Database URL required. Pass --database-url or set KICI_DATABASE_URL.");
13769
13869
  return url;
13770
13870
  }
13771
- async function confirmInteractive$4(prompt, expected) {
13871
+ async function confirmInteractive$1(prompt, expected) {
13772
13872
  if (!process.stdin.isTTY) return false;
13773
13873
  process.stderr.write(prompt);
13774
13874
  const rl = createInterface({
@@ -13896,7 +13996,7 @@ function registerDbCommands(program, getClient) {
13896
13996
  const url = resolveDatabaseUrl$7(opts.databaseUrl);
13897
13997
  const { dbName } = parseDatabaseUrl(url);
13898
13998
  if (!opts.yes) {
13899
- if (!await confirmInteractive$4(`About to DROP + RECREATE database "${dbName}". Type "${dbName}" to confirm: `, dbName)) {
13999
+ if (!await confirmInteractive$1(`About to DROP + RECREATE database "${dbName}". Type "${dbName}" to confirm: `, dbName)) {
13900
14000
  console.error("Aborted.");
13901
14001
  process.exit(1);
13902
14002
  }
@@ -13948,7 +14048,7 @@ function registerDbCommands(program, getClient) {
13948
14048
  const url = resolveDatabaseUrl$7(opts.databaseUrl);
13949
14049
  const { dbName } = parseDatabaseUrl(url);
13950
14050
  if (!opts.yes) {
13951
- if (!await confirmInteractive$4(`About to RESTORE (--clean) into database "${dbName}", overwriting its contents. Type "${dbName}" to confirm: `, dbName)) {
14051
+ if (!await confirmInteractive$1(`About to RESTORE (--clean) into database "${dbName}", overwriting its contents. Type "${dbName}" to confirm: `, dbName)) {
13952
14052
  console.error("Aborted.");
13953
14053
  process.exit(1);
13954
14054
  }
@@ -16007,19 +16107,9 @@ function registerSourceCommands(program, getClient) {
16007
16107
  });
16008
16108
  src.command("remove <routingKey>").description("Remove a source (GitHub, or generic/local with --generic / --local)").option("--yes", "Skip confirmation").option("--generic", "Remove a generic source (routingKey is treated as source ID)").option("--local", "Remove a local (file://) source (routingKey is treated as source ID)").option("--hard", "Permanently delete a generic/local source (requires --generic or --local)").option("--database-url <url>", "Use direct DB access instead of HTTP (offline mode)").action(async (routingKey, opts) => {
16009
16109
  try {
16010
- if (!opts.yes) {
16011
- const rl = createInterface({
16012
- input: process.stdin,
16013
- output: process.stdout
16014
- });
16015
- const answer = await new Promise((resolve) => {
16016
- rl.question(`Remove source "${routingKey}"? (y/N) `, resolve);
16017
- });
16018
- rl.close();
16019
- if (answer.toLowerCase() !== "y") {
16020
- console.log("Cancelled.");
16021
- return;
16022
- }
16110
+ if (!opts.yes && !await confirmPrompt(`Remove source "${routingKey}"? (y/N) `)) {
16111
+ console.log("Cancelled.");
16112
+ return;
16023
16113
  }
16024
16114
  const dbUrl = resolveDirectDbUrl$9(opts.databaseUrl);
16025
16115
  if (dbUrl) {
@@ -18292,16 +18382,6 @@ function readAttestations(db) {
18292
18382
  }
18293
18383
  //#endregion
18294
18384
  //#region src/cli/commands/attestations.ts
18295
- /**
18296
- * `kici-admin attestations` — orchestrator-DB-direct provenance maintenance.
18297
- *
18298
- * attestations reverify [--all] Recompute stored verdicts (verify-at-ingest
18299
- * backfill). Default scope: pending /
18300
- * unverifiable rows; `--all` re-evaluates
18301
- * every row. Direct DB + object storage.
18302
- *
18303
- * Classified `kici-admin` (orchestrator DB plane) per .claude/rules/platform-admin.md.
18304
- */
18305
18385
  init_client();
18306
18386
  const logger$15 = createLogger({ prefix: "kici-admin-attestations" });
18307
18387
  function resolveDatabaseUrl$5(explicit) {
@@ -18330,18 +18410,6 @@ function buildStorage(config) {
18330
18410
  signingSecret: "kici-admin-reverify"
18331
18411
  });
18332
18412
  }
18333
- async function confirmInteractive$3(prompt) {
18334
- const rl = createInterface({
18335
- input: process.stdin,
18336
- output: process.stderr
18337
- });
18338
- try {
18339
- const answer = await new Promise((resolve) => rl.question(prompt, resolve));
18340
- return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
18341
- } finally {
18342
- rl.close();
18343
- }
18344
- }
18345
18413
  /** Render attestation rows as a compact human table (stderr note when empty). */
18346
18414
  function printAttestationTable(rows) {
18347
18415
  if (rows.length === 0) {
@@ -18371,7 +18439,7 @@ function registerAttestationsCommands(program, getClient) {
18371
18439
  const config = loadConfig();
18372
18440
  const url = resolveDatabaseUrl$5(opts.databaseUrl ?? (config.databaseUrl || void 0));
18373
18441
  if (opts.all && !opts.yes) {
18374
- if (!await confirmInteractive$3("Re-evaluate the verdict of EVERY attestation (including already-verified)? [y/N] ")) {
18442
+ if (!await confirmPrompt("Re-evaluate the verdict of EVERY attestation (including already-verified)? [y/N] ")) {
18375
18443
  console.error("Aborted.");
18376
18444
  process.exit(1);
18377
18445
  }
@@ -18725,7 +18793,7 @@ var DbSigner = class DbSigner {
18725
18793
  * halves ONLY — never private material.
18726
18794
  *
18727
18795
  * The PRIVATE key is non-exportable by design: there is no export of private
18728
- * material and no import. Loss recovery is a routine rotation (§ H of the design).
18796
+ * material and no import. Loss recovery is a routine rotation.
18729
18797
  */
18730
18798
  init_client();
18731
18799
  const logger$14 = createLogger({ prefix: "kici-admin-signing-key" });
@@ -18766,18 +18834,6 @@ function resolveSecretKey$1() {
18766
18834
  if (!config.secretKey) throw new Error("KICI_SECRET_KEY is required to generate a db-custody signing key (it wraps the private JWK).");
18767
18835
  return config.secretKey;
18768
18836
  }
18769
- async function confirmInteractive$2(prompt) {
18770
- const rl = createInterface({
18771
- input: process.stdin,
18772
- output: process.stderr
18773
- });
18774
- try {
18775
- const a = (await new Promise((resolve) => rl.question(prompt, resolve))).trim().toLowerCase();
18776
- return a === "y" || a === "yes";
18777
- } finally {
18778
- rl.close();
18779
- }
18780
- }
18781
18837
  async function withRepo$1(databaseUrl, fn) {
18782
18838
  const pool = createPool(resolveDatabaseUrl$4(databaseUrl));
18783
18839
  const db = createDb$1(pool);
@@ -18869,7 +18925,7 @@ function registerSigningKeyCommands(program, tryGetClient) {
18869
18925
  process.stderr.write(`Generated active signing key ${outcome.kid}.\n`);
18870
18926
  }
18871
18927
  }, {
18872
- confirm: confirmInteractive$2,
18928
+ confirm: confirmPrompt,
18873
18929
  yes: opts.yes,
18874
18930
  dryRun: opts.dryRun
18875
18931
  });
@@ -18893,7 +18949,7 @@ function registerSigningKeyCommands(program, tryGetClient) {
18893
18949
  process.stderr.write(`Rotated: new active signing key ${kid}.\n`);
18894
18950
  }
18895
18951
  }, {
18896
- confirm: confirmInteractive$2,
18952
+ confirm: confirmPrompt,
18897
18953
  yes: opts.yes,
18898
18954
  dryRun: opts.dryRun
18899
18955
  });
@@ -18915,7 +18971,7 @@ function registerSigningKeyCommands(program, tryGetClient) {
18915
18971
  signingKey.command("revoke <kid>").description("Distrust a compromised key (REMOVED from the JWKS; everything it signed is distrusted)").requiredOption("--reason <reason>", "Why the key is being revoked (audit)").option("--database-url <url>", "Orchestrator DB URL (else KICI_DATABASE_URL)").option("--yes", "Skip the confirmation prompt").action(async (kid, opts) => {
18916
18972
  try {
18917
18973
  if (!opts.yes) {
18918
- if (!await confirmInteractive$2(`Revoke signing key ${kid}? Everything it ever signed becomes UNVERIFIABLE. [y/N] `)) {
18974
+ if (!await confirmPrompt(`Revoke signing key ${kid}? Everything it ever signed becomes UNVERIFIABLE. [y/N] `)) {
18919
18975
  console.error("Aborted.");
18920
18976
  process.exit(1);
18921
18977
  }
@@ -19287,24 +19343,6 @@ async function generateDashboardEncryptionKey(secretKey) {
19287
19343
  }
19288
19344
  //#endregion
19289
19345
  //#region src/cli/commands/dashboard-encryption-key.ts
19290
- /**
19291
- * `kici-admin dashboard-encryption-key` — the orchestrator's X25519
19292
- * dashboard-encryption key: the trust root a browser seals a secret / variable
19293
- * value to under the `encrypted` dashboard-write posture, so the hosted control
19294
- * plane relays only opaque ciphertext.
19295
- *
19296
- * dashboard-encryption-key show Print the active kid, its public JWK, where
19297
- * the JWKS is published, and the Verified-tier
19298
- * URL (when that tier is opted into).
19299
- * dashboard-encryption-key list List every key on record (kid / status / created_at).
19300
- * dashboard-encryption-key rotate Mint a new active key; the prior active key
19301
- * is retired from the published JWKS, but its
19302
- * private half stays on record so envelopes
19303
- * already sealed to it still decrypt.
19304
- *
19305
- * The PRIVATE half is master-key wrapped (`KICI_SECRET_KEY`) in the orchestrator
19306
- * DB and is never exportable — recovery from a lost key is a routine rotation.
19307
- */
19308
19346
  init_client();
19309
19347
  const logger$9 = createLogger({ prefix: "kici-admin-dashboard-encryption-key" });
19310
19348
  function resolveDatabaseUrl$3(explicit) {
@@ -19317,18 +19355,6 @@ function resolveSecretKey() {
19317
19355
  if (!config.secretKey) throw new Error("KICI_SECRET_KEY is required for the dashboard encryption key (it wraps the private key).");
19318
19356
  return config.secretKey;
19319
19357
  }
19320
- async function confirmInteractive$1(prompt) {
19321
- const rl = createInterface({
19322
- input: process.stdin,
19323
- output: process.stderr
19324
- });
19325
- try {
19326
- const a = (await new Promise((resolve) => rl.question(prompt, resolve))).trim().toLowerCase();
19327
- return a === "y" || a === "yes";
19328
- } finally {
19329
- rl.close();
19330
- }
19331
- }
19332
19358
  async function withRepo(databaseUrl, fn) {
19333
19359
  const pool = createPool(resolveDatabaseUrl$3(databaseUrl));
19334
19360
  const db = createDb$1(pool);
@@ -19435,7 +19461,7 @@ function registerDashboardEncryptionKeyCommands(program) {
19435
19461
  process.stderr.write(`Rotated: new active dashboard-encryption key ${kid}.\n`);
19436
19462
  }
19437
19463
  }, {
19438
- confirm: confirmInteractive$1,
19464
+ confirm: confirmPrompt,
19439
19465
  yes: opts.yes,
19440
19466
  dryRun: opts.dryRun
19441
19467
  });
@@ -19705,7 +19731,7 @@ function registerBackendCommands(program, getClient) {
19705
19731
  try {
19706
19732
  scopeInfo = ` has ${(await getClient().getBackend(name)).scopeCount ?? 0} discovered scopes -- they will become unavailable.`;
19707
19733
  } catch {}
19708
- if (!await confirm$3(`Backend "${name}"${scopeInfo} Continue? [y/N] `)) {
19734
+ if (!await confirmPrompt(`Backend "${name}"${scopeInfo} Continue? [y/N] `)) {
19709
19735
  console.log("Aborted.");
19710
19736
  return;
19711
19737
  }
@@ -19892,21 +19918,6 @@ function printBackendSummary(b) {
19892
19918
  console.log(` Sync interval: ${Math.round(Number(b.syncIntervalMs) / 6e4)}m`);
19893
19919
  console.log(` Enabled: ${b.enabled}`);
19894
19920
  }
19895
- /**
19896
- * Simple confirmation prompt.
19897
- */
19898
- async function confirm$3(message) {
19899
- const rl = createInterface({
19900
- input: process.stdin,
19901
- output: process.stderr
19902
- });
19903
- return new Promise((resolve) => {
19904
- rl.question(message, (answer) => {
19905
- rl.close();
19906
- resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
19907
- });
19908
- });
19909
- }
19910
19921
  //#endregion
19911
19922
  //#region src/cli/commands/diagnose.ts
19912
19923
  /** ANSI color codes for terminal output. */
@@ -20491,18 +20502,12 @@ async function resolveUpgradeTarget(args) {
20491
20502
  platform
20492
20503
  };
20493
20504
  }
20494
- /** Prompt the user for confirmation (returns true if yes). */
20495
- async function confirm$2(message) {
20496
- const rl = createInterface({
20505
+ /** Prompt the user for confirmation on stdout, beside the upgrade plan it follows. */
20506
+ function confirm$1(message) {
20507
+ return confirmPrompt(`${message} [y/N] `, {
20497
20508
  input: process.stdin,
20498
20509
  output: process.stdout
20499
20510
  });
20500
- return new Promise((resolve) => {
20501
- rl.question(`${message} [y/N] `, (answer) => {
20502
- rl.close();
20503
- resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
20504
- });
20505
- });
20506
20511
  }
20507
20512
  /** Download a file from a URL to a local path. */
20508
20513
  async function downloadArchive(url, destPath) {
@@ -20924,7 +20929,7 @@ async function performVersionedUpgrade(component, opts, hooks = {}) {
20924
20929
  console.log(` Install path: ${versionedDirPath}`);
20925
20930
  console.log(" The service will be stopped during upgrade.");
20926
20931
  console.log("");
20927
- if (!await confirm$2("Proceed with upgrade?")) {
20932
+ if (!await confirm$1("Proceed with upgrade?")) {
20928
20933
  console.log("Upgrade cancelled.");
20929
20934
  return;
20930
20935
  }
@@ -21037,7 +21042,7 @@ async function restartOnlyUpgrade(component, config, resolvedInstance, manager,
21037
21042
  console.log(`This will restart "${config.name}" onto the npm-installed version ${verdict.version}.`);
21038
21043
  console.log(" The service will be stopped briefly during the restart.");
21039
21044
  console.log("");
21040
- if (!await confirm$2("Proceed with upgrade?")) {
21045
+ if (!await confirm$1("Proceed with upgrade?")) {
21041
21046
  console.log("Upgrade cancelled.");
21042
21047
  return;
21043
21048
  }
@@ -21063,7 +21068,7 @@ async function performSelfDrivingInstall(component, config, resolvedInstance, ma
21063
21068
  console.log(`This will install ${target.owningPackage}@${version} using ${target.npmPath}`);
21064
21069
  console.log(` (node runtime: ${target.nodeExecPath}), then restart "${config.name}".`);
21065
21070
  console.log("");
21066
- if (!await confirm$2("Proceed with upgrade?")) {
21071
+ if (!await confirm$1("Proceed with upgrade?")) {
21067
21072
  console.log("Upgrade cancelled.");
21068
21073
  return;
21069
21074
  }
@@ -21120,7 +21125,7 @@ async function handlePick(component, platform, installBase, config, manager, res
21120
21125
  console.log(` Install path: ${path.join(installBase, `${component}-${target}`)}`);
21121
21126
  console.log(" The service will be stopped during the switch.");
21122
21127
  console.log("");
21123
- if (!await confirm$2("Proceed with switch?")) {
21128
+ if (!await confirm$1("Proceed with switch?")) {
21124
21129
  console.log("Pick cancelled.");
21125
21130
  return;
21126
21131
  }
@@ -21168,7 +21173,7 @@ async function assertSchemaAllowsSwitch(args) {
21168
21173
  console.log("");
21169
21174
  console.log(" !! The running service could not be reached, so the schema was not checked.");
21170
21175
  console.log("");
21171
- return yes ? true : confirm$2("Proceed with the switch anyway?");
21176
+ return yes ? true : confirm$1("Proceed with the switch anyway?");
21172
21177
  }
21173
21178
  const recordedHead = resolvedInstance.manifest.migrationHeads?.[targetVersion];
21174
21179
  const verdict = schemaGuardVerdict({
@@ -21181,7 +21186,7 @@ async function assertSchemaAllowsSwitch(args) {
21181
21186
  console.log("");
21182
21187
  console.log(verdict.message);
21183
21188
  console.log("");
21184
- return yes ? true : confirm$2("Proceed with the switch anyway?");
21189
+ return yes ? true : confirm$1("Proceed with the switch anyway?");
21185
21190
  }
21186
21191
  if (!migrateDown) {
21187
21192
  console.error("");
@@ -21276,7 +21281,7 @@ async function handleRollback(component, platform, installBase, config, manager,
21276
21281
  console.log(` Current version: ${currentVersion}`);
21277
21282
  console.log(` Rollback to: ${previousVersion}`);
21278
21283
  console.log("");
21279
- if (!await confirm$2("Proceed with rollback?")) {
21284
+ if (!await confirm$1("Proceed with rollback?")) {
21280
21285
  console.log("Rollback cancelled.");
21281
21286
  return;
21282
21287
  }
@@ -22802,7 +22807,7 @@ init_manifest();
22802
22807
  const ALL_PLATFORMS = AgentPlatform.options;
22803
22808
  /** The orchestrator's own version (single-version invariant) = the packaged agent version. */
22804
22809
  function resolveKiciVersion() {
22805
- return "0.10.0";
22810
+ return "0.11.0";
22806
22811
  }
22807
22812
  /** Parse the --platform value: default set | single | CSV | `all`. */
22808
22813
  function parsePlatforms(raw) {
@@ -26832,6 +26837,72 @@ function registerContextCommands(program, getClient) {
26832
26837
  process.exit(1);
26833
26838
  }
26834
26839
  });
26840
+ registerSourceOverrideCommands(env, getClient);
26841
+ }
26842
+ function printSourceOverrides(rows) {
26843
+ if (rows.length === 0) {
26844
+ console.log("No source overrides found.");
26845
+ return;
26846
+ }
26847
+ const header = [
26848
+ "ROUTING KEY",
26849
+ "KEY",
26850
+ "VALUE"
26851
+ ];
26852
+ const data = rows.map((r) => [
26853
+ r.routing_key,
26854
+ r.key,
26855
+ r.value
26856
+ ]);
26857
+ const widths = header.map((h, i) => Math.max(h.length, ...data.map((row) => row[i].length)));
26858
+ const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
26859
+ console.log(header.map((h, i) => pad(h, widths[i])).join(" "));
26860
+ for (const row of data) console.log(row.map((c, i) => pad(c, widths[i])).join(" "));
26861
+ }
26862
+ /** Admin API path of one source override, every segment URI-encoded. */
26863
+ function sourceOverridePath(opts) {
26864
+ const params = new URLSearchParams({ orgId: opts.org });
26865
+ return `/api/v1/admin/contexts/${encodeURIComponent(opts.env)}/source-overrides/${encodeURIComponent(opts.routingKey)}/${encodeURIComponent(opts.key)}?${params}`;
26866
+ }
26867
+ /**
26868
+ * `context source-override list|set|delete` — the operator path for the
26869
+ * dashboard's per-source override writes (`contexts.source_overrides.*` in the
26870
+ * dashboard-write policy). HTTP only: each verb calls the orchestrator admin API.
26871
+ */
26872
+ function registerSourceOverrideCommands(context, getClient) {
26873
+ const so = context.command("source-override").description("Per-source variable overrides in a context (HTTP only)");
26874
+ so.command("list").description("List the source overrides in a context, for every source unless --routing-key").requiredOption("--org <id>", "Org ID").requiredOption("--env <name>", "Context name").option("--routing-key <key>", "Only list the overrides for this source routing key").option("--json", "Emit JSON output").action(async (opts) => {
26875
+ try {
26876
+ const params = new URLSearchParams({ orgId: opts.org });
26877
+ if (opts.routingKey) params.set("routingKey", opts.routingKey);
26878
+ const result = await getClient().get(`/api/v1/admin/contexts/${encodeURIComponent(opts.env)}/source-overrides?${params}`);
26879
+ if (opts.json) console.log(JSON.stringify(result));
26880
+ else printSourceOverrides(result.overrides);
26881
+ } catch (err) {
26882
+ console.error(`Error: ${toErrorMessage(err)}`);
26883
+ process.exit(1);
26884
+ }
26885
+ });
26886
+ so.command("set").description("Set the value one source sees for a context variable key. A locked context variable keeps its own value.").requiredOption("--org <id>", "Org ID").requiredOption("--env <name>", "Context name").requiredOption("--routing-key <key>", "Routing key of the source the override applies to").requiredOption("--key <key>", "Variable key").requiredOption("--value <value>", "Override value").option("--json", "Emit JSON output").action(async (opts) => {
26887
+ try {
26888
+ const result = await getClient().put(sourceOverridePath(opts), { value: opts.value });
26889
+ if (opts.json) console.log(JSON.stringify(result));
26890
+ else console.log(`context source-override set: key=${opts.key} routing-key=${opts.routingKey} (env=${opts.env})`);
26891
+ } catch (err) {
26892
+ console.error(`Error: ${toErrorMessage(err)}`);
26893
+ process.exit(1);
26894
+ }
26895
+ });
26896
+ so.command("delete").description("Delete one source override, so the source resolves the key from the context variables").requiredOption("--org <id>", "Org ID").requiredOption("--env <name>", "Context name").requiredOption("--routing-key <key>", "Routing key of the source the override applies to").requiredOption("--key <key>", "Variable key").option("--json", "Emit JSON output").action(async (opts) => {
26897
+ try {
26898
+ const result = await getClient().delete(sourceOverridePath(opts));
26899
+ if (opts.json) console.log(JSON.stringify(result));
26900
+ else console.log(`context source-override delete: key=${opts.key} routing-key=${opts.routingKey} (env=${opts.env})`);
26901
+ } catch (err) {
26902
+ console.error(`Error: ${toErrorMessage(err)}`);
26903
+ process.exit(1);
26904
+ }
26905
+ });
26835
26906
  }
26836
26907
  //#endregion
26837
26908
  //#region src/cli/commands/variable.ts
@@ -26886,7 +26957,7 @@ function registerVariableCommands(program, getClient) {
26886
26957
  vr.command("delete <orgId> <context> <key>").description("Delete a context variable").option("--yes", "Skip confirmation prompt").action(async (orgId, context, key, opts) => {
26887
26958
  try {
26888
26959
  if (!opts.yes) {
26889
- if (!await confirm$1(`Are you sure you want to delete variable '${key}' from context '${context}'?`)) {
26960
+ if (!await confirmPrompt(`Are you sure you want to delete variable '${key}' from context '${context}'? [y/N] `)) {
26890
26961
  console.log("Aborted.");
26891
26962
  return;
26892
26963
  }
@@ -26899,19 +26970,6 @@ function registerVariableCommands(program, getClient) {
26899
26970
  }
26900
26971
  });
26901
26972
  }
26902
- async function confirm$1(message) {
26903
- const { createInterface } = await import("node:readline");
26904
- const rl = createInterface({
26905
- input: process.stdin,
26906
- output: process.stderr
26907
- });
26908
- return new Promise((resolve) => {
26909
- rl.question(`${message} [y/N] `, (answer) => {
26910
- rl.close();
26911
- resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
26912
- });
26913
- });
26914
- }
26915
26973
  //#endregion
26916
26974
  //#region src/cli/commands/queue.ts
26917
26975
  function resolveDirectDbUrl$3(explicit) {
@@ -27214,7 +27272,7 @@ function printRegistrationShow(res) {
27214
27272
  console.log(`updated_at: ${r.updated_at}`);
27215
27273
  }
27216
27274
  function registerRegistrationCommands(program, getClient) {
27217
- const reg = program.command("registration").description("Registered workflow instance read (workflow_registrations table)");
27275
+ const reg = program.command("registration").description("Registered workflow instances (workflow_registrations table)");
27218
27276
  reg.command("list").description("List workflow_registrations rows (also returns registry_version)").option("--org <id>", "Filter by customer_id").option("--routing-key <k>", "Filter by routing_key").option("--repo <ident>", "Filter by repo_identifier").option("--trigger-type <type>", "Filter by trigger type (in trigger_types[])").option("--limit <n>", "Max rows (default 100, max 1000)").option("--database-url <url>", "Use direct DB access instead of HTTP (offline mode)").option("--json", "Emit JSON output").action(async (opts) => {
27219
27277
  try {
27220
27278
  const limit = parseIntOption(opts.limit, "--limit");
@@ -27257,6 +27315,42 @@ function registerRegistrationCommands(program, getClient) {
27257
27315
  process.exit(1);
27258
27316
  }
27259
27317
  });
27318
+ registerRegistrationWriteCommands(reg, getClient);
27319
+ }
27320
+ /**
27321
+ * `registration disable|enable|delete` — the operator path for the dashboard's
27322
+ * `registration.disable` / `registration.delete` writes. `enable` is the same
27323
+ * admin route as `disable`, with the flag cleared.
27324
+ */
27325
+ function registerRegistrationWriteCommands(reg, getClient) {
27326
+ const setDisabled = async (id, disabled, json) => {
27327
+ try {
27328
+ const result = await getClient().patch(`/api/v1/admin/registrations/${encodeURIComponent(id)}/disable`, { disabled });
27329
+ if (json) console.log(JSON.stringify(result));
27330
+ else console.log(`registration ${disabled ? "disable" : "enable"}: id=${id} disabled=${result.disabled} registry_version=${result.registryVersion}`);
27331
+ } catch (err) {
27332
+ console.error(`Error: ${toErrorMessage(err)}`);
27333
+ process.exit(1);
27334
+ }
27335
+ };
27336
+ reg.command("disable <id>").description("Disable a workflow registration: it stays registered, but its triggers stop dispatching").option("--json", "Emit JSON output").action(async (id, opts) => setDisabled(id, true, opts.json));
27337
+ reg.command("enable <id>").description("Re-enable a disabled workflow registration").option("--json", "Emit JSON output").action(async (id, opts) => setDisabled(id, false, opts.json));
27338
+ reg.command("delete <id>").description("Delete a workflow registration by id").option("--yes", "Skip confirmation prompt").option("--json", "Emit JSON output").action(async (id, opts) => {
27339
+ try {
27340
+ if (!opts.yes) {
27341
+ if (!await confirmPrompt(`Are you sure you want to delete registration '${id}'? [y/N] `)) {
27342
+ console.log("Aborted.");
27343
+ return;
27344
+ }
27345
+ }
27346
+ const result = await getClient().delete(`/api/v1/admin/registrations/${encodeURIComponent(id)}`);
27347
+ if (opts.json) console.log(JSON.stringify(result));
27348
+ else console.log(`registration delete: id=${id} deleted=${result.deleted} registry_version=${result.registryVersion}`);
27349
+ } catch (err) {
27350
+ console.error(`Error: ${toErrorMessage(err)}`);
27351
+ process.exit(1);
27352
+ }
27353
+ });
27260
27354
  }
27261
27355
  //#endregion
27262
27356
  //#region src/cli/commands/event.ts
@@ -28770,7 +28864,7 @@ var init_orchestrator_cold_store = __esmMin((() => {
28770
28864
  //#endregion
28771
28865
  //#region src/cli/commands/cold-store-impl.ts
28772
28866
  /**
28773
- * Phase C implementations for `kici-admin cold-store` subcommands.
28867
+ * Implementations for `kici-admin cold-store` subcommands.
28774
28868
  *
28775
28869
  * Mirrors the Platform-side implementation in
28776
28870
  * `packages/platform/src/admin/cold-store-commands.ts` — same shape,
@@ -28785,9 +28879,8 @@ var init_orchestrator_cold_store = __esmMin((() => {
28785
28879
  * - prints results to stdout (JSON Lines for list-chunks, JSONL for
28786
28880
  * peek-chunk, free-form for the others)
28787
28881
  *
28788
- * Audit emission lives one level up in the command registration so we
28789
- * can write one `access_log` breadcrumb row per CLI invocation in the
28790
- * same shape the Platform admin CLI uses for `audit_log`.
28882
+ * No per-invocation breadcrumb row is written: the Platform admin CLI's
28883
+ * `audit_log` breadcrumb has no `access_log` counterpart here.
28791
28884
  */
28792
28885
  var cold_store_impl_exports = /* @__PURE__ */ __exportAll({
28793
28886
  archiveNow: () => archiveNow,
@@ -29050,7 +29143,7 @@ async function replayChunk(opts) {
29050
29143
  }
29051
29144
  }
29052
29145
  /**
29053
- * Phase F — promote a chunk's rows BACK into the orchestrator PG.
29146
+ * Promote a chunk's rows back into the orchestrator PG.
29054
29147
  * Mirrors the Platform-side `replayIntoPg`. Adapter must implement
29055
29148
  * `replayInsert` — currently only `execution_runs`.
29056
29149
  */
@@ -29182,7 +29275,7 @@ async function reconcile(opts) {
29182
29275
  }
29183
29276
  }
29184
29277
  /**
29185
- * Phase 2 — list chunks past their cold-retention horizon. Read-only;
29278
+ * List chunks past their cold-retention horizon. Read-only;
29186
29279
  * no S3 or PG mutation. Emits one JSON line per row so the output is
29187
29280
  * easy to pipe through `jq` / `column -t` / etc.
29188
29281
  */
@@ -29206,7 +29299,7 @@ async function listPurgeable(opts) {
29206
29299
  }
29207
29300
  }
29208
29301
  /**
29209
- * Phase 2 — delete S3 objects + clean up PG bookkeeping for chunks
29302
+ * Delete S3 objects + clean up PG bookkeeping for chunks
29210
29303
  * past their cold-retention horizon. Defaults to dry-run; pass
29211
29304
  * `--apply` to actually delete.
29212
29305
  */
@@ -29358,7 +29451,7 @@ function registerColdStoreCommands(program, _getClient) {
29358
29451
  process.exit(2);
29359
29452
  }
29360
29453
  });
29361
- coldStore.command("replay-into-pg <chunkId>").description("Phase F: promote every row in a chunk BACK into orchestrator PG (clear archived_at, write replay audit)").option("--database-url <url>", "Orchestrator Postgres URL (else KICI_DATABASE_URL)").requiredOption("--table <table>", "Adapter table name (currently: execution_runs)").requiredOption("--tenant <rk>", "Routing key for the chunk").requiredOption("--partition-date <YYYY-MM-DD>", "Partition date for the chunk").action(async (chunkId, opts) => {
29454
+ coldStore.command("replay-into-pg <chunkId>").description("Promote every row in a chunk back into orchestrator PG (clear archived_at, write a replay audit row)").option("--database-url <url>", "Orchestrator Postgres URL (else KICI_DATABASE_URL)").requiredOption("--table <table>", "Adapter table name (currently: execution_runs)").requiredOption("--tenant <rk>", "Routing key for the chunk").requiredOption("--partition-date <YYYY-MM-DD>", "Partition date for the chunk").action(async (chunkId, opts) => {
29362
29455
  try {
29363
29456
  const databaseUrl = resolveDatabaseUrl(opts);
29364
29457
  const { replayIntoPg } = await Promise.resolve().then(() => (init_cold_store_impl(), cold_store_impl_exports));
@@ -29389,7 +29482,7 @@ function registerColdStoreCommands(program, _getClient) {
29389
29482
  process.exit(2);
29390
29483
  }
29391
29484
  });
29392
- coldStore.command("list-purgeable").description("Phase 2: list chunks past their cold-retention horizon (read-only)").option("--database-url <url>", "Orchestrator Postgres URL (else KICI_DATABASE_URL)").option("--table <table>", "Filter to a single adapter table (else all)").option("--bucket <bucket>", "Filter to a single cold-bucket (30d / 180d / 1y / 2y)").option("--limit <n>", "Max candidates to inspect", "1000").action(async (opts) => {
29485
+ coldStore.command("list-purgeable").description("List chunks past their cold-retention horizon (read-only)").option("--database-url <url>", "Orchestrator Postgres URL (else KICI_DATABASE_URL)").option("--table <table>", "Filter to a single adapter table (else all)").option("--bucket <bucket>", "Filter to a single cold-bucket (30d / 180d / 1y / 2y)").option("--limit <n>", "Max candidates to inspect", "1000").action(async (opts) => {
29393
29486
  try {
29394
29487
  const databaseUrl = resolveDatabaseUrl(opts);
29395
29488
  const { listPurgeable } = await Promise.resolve().then(() => (init_cold_store_impl(), cold_store_impl_exports));
@@ -29404,7 +29497,7 @@ function registerColdStoreCommands(program, _getClient) {
29404
29497
  process.exit(2);
29405
29498
  }
29406
29499
  });
29407
- coldStore.command("purge-now").description("Phase 2: purge expired chunks from S3 + PG bookkeeping. DRY-RUN by default — pass --apply to actually delete.").option("--database-url <url>", "Orchestrator Postgres URL (else KICI_DATABASE_URL)").option("--table <table>", "Filter to a single adapter table (else all)").option("--bucket <bucket>", "Filter to a single cold-bucket (30d / 180d / 1y / 2y)").option("--limit <n>", "Max candidates to process", "1000").option("--apply", "Actually delete (default is dry-run)").action(async (opts) => {
29500
+ coldStore.command("purge-now").description("Purge expired chunks from S3 + PG bookkeeping. DRY-RUN by default — pass --apply to actually delete.").option("--database-url <url>", "Orchestrator Postgres URL (else KICI_DATABASE_URL)").option("--table <table>", "Filter to a single adapter table (else all)").option("--bucket <bucket>", "Filter to a single cold-bucket (30d / 180d / 1y / 2y)").option("--limit <n>", "Max candidates to process", "1000").option("--apply", "Actually delete (default is dry-run)").action(async (opts) => {
29408
29501
  try {
29409
29502
  const databaseUrl = resolveDatabaseUrl(opts);
29410
29503
  const { purgeNow } = await Promise.resolve().then(() => (init_cold_store_impl(), cold_store_impl_exports));
@@ -33902,7 +33995,7 @@ Token vocabulary:
33902
33995
  * where no bundler substituted the constant — the same shape `server.ts` and
33903
33996
  * `standalone.ts` use for `ORCHESTRATOR_VERSION`.
33904
33997
  */
33905
- const CLI_VERSION = "0.10.0";
33998
+ const CLI_VERSION = "0.11.0";
33906
33999
  /**
33907
34000
  * Build the kici-admin Commander program with every command group registered.
33908
34001
  * Exported so the surface registry can walk the real command tree without