@kici-dev/orchestrator 0.1.16 → 0.1.17

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.
Files changed (58) hide show
  1. package/dist/agent/dispatcher.d.ts +18 -1
  2. package/dist/approvals/apply-decision.d.ts +11 -1
  3. package/dist/cache/pending-inits.d.ts +5 -0
  4. package/dist/cli/api-client.d.ts +11 -0
  5. package/dist/cli/commands/local-hook.d.ts +15 -0
  6. package/dist/cli/commands/local-trigger.d.ts +24 -0
  7. package/dist/cli/commands/remote-source.d.ts +17 -0
  8. package/dist/cli/commands/shared/versioned-upgrade.d.ts +14 -0
  9. package/dist/cli/kici-admin.d.ts +13 -0
  10. package/dist/cli.js +726 -177
  11. package/dist/config.d.ts +0 -4
  12. package/dist/dashboard/handler.d.ts +26 -2
  13. package/dist/dashboard/needs-edges.d.ts +13 -0
  14. package/dist/db/migrations/035_pending_workflow_contexts.d.ts +11 -0
  15. package/dist/db/migrations/036_attestations.d.ts +15 -0
  16. package/dist/db/migrations/037_generic_sources_provider_type_local.d.ts +19 -0
  17. package/dist/db/migrations/038_remote_sources.d.ts +14 -0
  18. package/dist/db/types.d.ts +83 -10
  19. package/dist/diagnostics/fleet-collector.d.ts +1 -1
  20. package/dist/entry-helpers.d.ts +7 -29
  21. package/dist/environments/held-runs.d.ts +24 -0
  22. package/dist/index.js +1 -0
  23. package/dist/metrics/prometheus.d.ts +4 -2
  24. package/dist/orchestrator-core.d.ts +48 -0
  25. package/dist/pipeline/dispatch-matched-workflow.d.ts +18 -1
  26. package/dist/pipeline/install-secrets-resolver.d.ts +32 -5
  27. package/dist/pipeline/needs-scheduler.d.ts +12 -10
  28. package/dist/pipeline/pending-workflow-context.d.ts +44 -0
  29. package/dist/pipeline/processor.d.ts +6 -4
  30. package/dist/pipeline/remote-source-store.d.ts +21 -0
  31. package/dist/pipeline/resume-workflow.d.ts +26 -0
  32. package/dist/providers/local/index.d.ts +33 -0
  33. package/dist/providers/local/local-source-config.d.ts +17 -0
  34. package/dist/providers/{internal → local}/lock-file-fetcher.d.ts +6 -6
  35. package/dist/providers/{internal → local}/normalizer.d.ts +28 -28
  36. package/dist/providers/{internal → local}/repo-url-builder.d.ts +6 -6
  37. package/dist/reporting/execution-tracker.d.ts +33 -0
  38. package/dist/routes/admin-events.d.ts +3 -4
  39. package/dist/routes/uploads.d.ts +36 -26
  40. package/dist/server.js +27910 -26483
  41. package/dist/sources/build-platform-sources.d.ts +4 -2
  42. package/dist/stale-detector/stale-run-detector.d.ts +14 -1
  43. package/dist/standalone.js +15708 -15619
  44. package/dist/webhook/generic-sources-listener.d.ts +4 -0
  45. package/dist/webhook/generic-sources.d.ts +37 -11
  46. package/dist/webhook/register-source-bundle.d.ts +14 -4
  47. package/dist/ws/agent-handler.d.ts +17 -0
  48. package/dist/ws/dashboard-dispatch-guard.d.ts +21 -0
  49. package/dist/ws/dashboard-env-handler.d.ts +13 -1
  50. package/dist/ws/oidc-token-relay.d.ts +59 -0
  51. package/dist/ws/platform-client.d.ts +30 -1
  52. package/dist/ws/test-relay-handlers.d.ts +112 -0
  53. package/installer-image-digests.json +3 -3
  54. package/package.json +4 -4
  55. package/sbom.spdx.json +98 -48
  56. package/dist/providers/internal/index.d.ts +0 -32
  57. package/dist/routes/test-trigger.d.ts +0 -41
  58. package/dist/ws/observer-handler.d.ts +0 -42
package/dist/cli.js CHANGED
@@ -3,9 +3,9 @@ import { createRequire } from "node:module";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import * as path$1 from "node:path";
5
5
  import path, { join, resolve } from "node:path";
6
- import { Command } from "commander";
7
6
  import * as fs$1 from "node:fs";
8
- import fs, { closeSync, createReadStream, createWriteStream, existsSync, openSync, promises, readFileSync, statSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
7
+ import fs, { chmodSync, closeSync, createReadStream, createWriteStream, existsSync, openSync, promises, readFileSync, realpathSync, statSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
8
+ import { Command } from "commander";
9
9
  import { BaseColdStore, ChunkLru, addLogsToArchive, chunkObjectKey, clearDispatchQueueDirect, computeChunkId, computeMigrationsHash, createDb, createDbRole, createEnvironmentTemplateDirect, createLogger, createPool, createReadOnlyDbUser, decodeChunk, deleteEnvironmentDirect, dropAndCreateDatabase, emitKiciEventDirect, encodeKeySegment, ensureDatabase, formatBytes, formatUptime, isSchemaCurrent, listEnvironmentsDirect, listExecutionRunsDirect, listQueueDirect, listRegistrationsDirect, maskDatabaseUrl, parseDatabaseUrl, parseManifest, prunePeerCredentialsDirect, purgeScopedSecretsDirect, purgeSecretBackendsDirect, purgeStaleExecutionDirect, purgeStaleSourcesDirect, registerWorkflowManualDirect, resetRaftStateDirect, seedEnvironmentBindingDirect, seedEnvironmentDirect, setEnvironmentPolicyDirect, setEnvironmentSecretDirect, sha256, showEnvironmentDirect, showExecutionRunDirect, showQueueEntryDirect, showRegistrationDirect, storeMigrationContentHash, tablePrefix, toErrorMessage } from "@kici-dev/shared";
10
10
  import crypto, { createDecipheriv, createHash, hkdfSync, randomBytes, randomUUID } from "node:crypto";
11
11
  import { createInterface } from "node:readline";
@@ -52,6 +52,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
52
52
  * error formatting, and URL construction.
53
53
  */
54
54
  var AdminApiClient = class {
55
+ baseUrl;
56
+ token;
55
57
  constructor(baseUrl, token) {
56
58
  this.baseUrl = baseUrl;
57
59
  this.token = token;
@@ -1548,8 +1550,8 @@ var init_client = __esmMin((() => {
1548
1550
  //#region src/db/migrations/001_initial.ts
1549
1551
  init_client();
1550
1552
  var _001_initial_exports = /* @__PURE__ */ __exportAll({
1551
- down: () => down$33,
1552
- up: () => up$33
1553
+ down: () => down$37,
1554
+ up: () => up$37
1553
1555
  });
1554
1556
  /**
1555
1557
  * Squashed initial migration -- creates the complete Orchestrator database schema.
@@ -2241,7 +2243,7 @@ const DDL_STATEMENTS = [
2241
2243
  `ALTER TABLE ONLY public.held_runs
2242
2244
  ADD CONSTRAINT held_runs_environment_id_fkey FOREIGN KEY (environment_id) REFERENCES public.environments(id);`
2243
2245
  ];
2244
- async function up$33(db) {
2246
+ async function up$37(db) {
2245
2247
  for (const stmt of DDL_STATEMENTS) await sql.raw(stmt).execute(db);
2246
2248
  await sql`
2247
2249
  INSERT INTO cluster_meta (key, value)
@@ -2263,7 +2265,7 @@ async function up$33(db) {
2263
2265
  * Rollback drops everything created above. Uses CASCADE on table drops to cut
2264
2266
  * through the FK graph without relying on exact topological order.
2265
2267
  */
2266
- async function down$33(db) {
2268
+ async function down$37(db) {
2267
2269
  for (const [trig, tbl] of [["source_secrets_change_trigger", "scoped_secrets"], ["sources_change_trigger", "sources"]]) await sql.raw(`DROP TRIGGER IF EXISTS ${trig} ON public.${tbl}`).execute(db);
2268
2270
  for (const table of [
2269
2271
  "workflow_registrations",
@@ -2310,8 +2312,8 @@ async function down$33(db) {
2310
2312
  //#endregion
2311
2313
  //#region src/db/migrations/002_config_versions_key_version.ts
2312
2314
  var _002_config_versions_key_version_exports = /* @__PURE__ */ __exportAll({
2313
- down: () => down$32,
2314
- up: () => up$32
2315
+ down: () => down$36,
2316
+ up: () => up$36
2315
2317
  });
2316
2318
  /**
2317
2319
  * Add key_version column to config_versions so that sensitive-field encryption
@@ -2325,13 +2327,13 @@ var _002_config_versions_key_version_exports = /* @__PURE__ */ __exportAll({
2325
2327
  * No index is needed: rotation does a full-table scan; reads are by
2326
2328
  * `version` primary key and never filter on `key_version`.
2327
2329
  */
2328
- async function up$32(db) {
2330
+ async function up$36(db) {
2329
2331
  await sql`
2330
2332
  ALTER TABLE public.config_versions
2331
2333
  ADD COLUMN key_version integer NOT NULL DEFAULT 1
2332
2334
  `.execute(db);
2333
2335
  }
2334
- async function down$32(db) {
2336
+ async function down$36(db) {
2335
2337
  await sql`
2336
2338
  ALTER TABLE public.config_versions
2337
2339
  DROP COLUMN key_version
@@ -2340,8 +2342,8 @@ async function down$32(db) {
2340
2342
  //#endregion
2341
2343
  //#region src/db/migrations/003_access_log.ts
2342
2344
  var _003_access_log_exports = /* @__PURE__ */ __exportAll({
2343
- down: () => down$31,
2344
- up: () => up$31
2345
+ down: () => down$35,
2346
+ up: () => up$35
2345
2347
  });
2346
2348
  /**
2347
2349
  * Access log: one row per read or orchestrator-admin mutation attributable
@@ -2365,7 +2367,7 @@ var _003_access_log_exports = /* @__PURE__ */ __exportAll({
2365
2367
  * Retention is TTL-based via expires_at; packages/orchestrator/src/queue/
2366
2368
  * cleanup.ts picks up the prune pass.
2367
2369
  */
2368
- async function up$31(db) {
2370
+ async function up$35(db) {
2369
2371
  await sql`
2370
2372
  CREATE TABLE public.access_log (
2371
2373
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -2403,28 +2405,28 @@ async function up$31(db) {
2403
2405
  ON public.access_log (actor_type, actor_id, created_at DESC)
2404
2406
  `.execute(db);
2405
2407
  }
2406
- async function down$31(db) {
2408
+ async function down$35(db) {
2407
2409
  await sql`DROP TABLE IF EXISTS public.access_log`.execute(db);
2408
2410
  }
2409
2411
  //#endregion
2410
2412
  //#region src/db/migrations/004_rename_bundle_to_source.ts
2411
2413
  var _004_rename_bundle_to_source_exports = /* @__PURE__ */ __exportAll({
2412
- down: () => down$30,
2413
- up: () => up$30
2414
+ down: () => down$34,
2415
+ up: () => up$34
2414
2416
  });
2415
- async function up$30(db) {
2417
+ async function up$34(db) {
2416
2418
  await db.schema.alterTable("dispatch_queue").renameColumn("bundle_url", "source_tar_url").execute();
2417
2419
  await db.schema.alterTable("dispatch_queue").renameColumn("bundle_hash", "source_tar_hash").execute();
2418
2420
  }
2419
- async function down$30(db) {
2421
+ async function down$34(db) {
2420
2422
  await db.schema.alterTable("dispatch_queue").renameColumn("source_tar_url", "bundle_url").execute();
2421
2423
  await db.schema.alterTable("dispatch_queue").renameColumn("source_tar_hash", "bundle_hash").execute();
2422
2424
  }
2423
2425
  //#endregion
2424
2426
  //#region src/db/migrations/005_cold_store_chunk_counter.ts
2425
2427
  var _005_cold_store_chunk_counter_exports = /* @__PURE__ */ __exportAll({
2426
- down: () => down$29,
2427
- up: () => up$29
2428
+ down: () => down$33,
2429
+ up: () => up$33
2428
2430
  });
2429
2431
  /**
2430
2432
  * Cold-store chunk counter table.
@@ -2441,7 +2443,7 @@ var _005_cold_store_chunk_counter_exports = /* @__PURE__ */ __exportAll({
2441
2443
  *
2442
2444
  * sections 5 and 8.
2443
2445
  */
2444
- async function up$29(db) {
2446
+ async function up$33(db) {
2445
2447
  await sql`
2446
2448
  CREATE TABLE public.cold_store_chunk_counts (
2447
2449
  db TEXT NOT NULL,
@@ -2459,14 +2461,14 @@ async function up$29(db) {
2459
2461
  ON public.cold_store_chunk_counts (db, table_name)
2460
2462
  `.execute(db);
2461
2463
  }
2462
- async function down$29(db) {
2464
+ async function down$33(db) {
2463
2465
  await sql`DROP TABLE IF EXISTS public.cold_store_chunk_counts`.execute(db);
2464
2466
  }
2465
2467
  //#endregion
2466
2468
  //#region src/db/migrations/006_runs_jobs_steps_archived_at.ts
2467
2469
  var _006_runs_jobs_steps_archived_at_exports = /* @__PURE__ */ __exportAll({
2468
- down: () => down$28,
2469
- up: () => up$28
2470
+ down: () => down$32,
2471
+ up: () => up$32
2470
2472
  });
2471
2473
  /**
2472
2474
  * `execution_runs` / `execution_jobs` / `execution_steps` cold-store
@@ -2502,7 +2504,7 @@ var _006_runs_jobs_steps_archived_at_exports = /* @__PURE__ */ __exportAll({
2502
2504
  * - `idx_execution_jobs_routing_key_created (routing_key, created_at)`
2503
2505
  * - `idx_execution_steps_routing_key_created (routing_key, created_at)`
2504
2506
  */
2505
- async function up$28(db) {
2507
+ async function up$32(db) {
2506
2508
  await sql`
2507
2509
  ALTER TABLE public.execution_runs
2508
2510
  ADD COLUMN archived_at TIMESTAMPTZ NULL,
@@ -2547,7 +2549,7 @@ async function up$28(db) {
2547
2549
  ON public.execution_steps (routing_key, created_at)
2548
2550
  `.execute(db);
2549
2551
  }
2550
- async function down$28(db) {
2552
+ async function down$32(db) {
2551
2553
  await sql`DROP INDEX IF EXISTS public.idx_execution_steps_routing_key_created`.execute(db);
2552
2554
  await sql`
2553
2555
  ALTER TABLE public.execution_steps
@@ -2572,8 +2574,8 @@ async function down$28(db) {
2572
2574
  //#endregion
2573
2575
  //#region src/db/migrations/007_audit_logs_archived_at.ts
2574
2576
  var _007_audit_logs_archived_at_exports = /* @__PURE__ */ __exportAll({
2575
- down: () => down$27,
2576
- up: () => up$27
2577
+ down: () => down$31,
2578
+ up: () => up$31
2577
2579
  });
2578
2580
  /**
2579
2581
  * `secret_audit_log` and `access_log` cold-store schema additions, plus
@@ -2613,7 +2615,7 @@ var _007_audit_logs_archived_at_exports = /* @__PURE__ */ __exportAll({
2613
2615
  * `down()` would not have meaningful retention bounds. Acceptable for
2614
2616
  * staging.
2615
2617
  */
2616
- async function up$27(db) {
2618
+ async function up$31(db) {
2617
2619
  await sql`
2618
2620
  ALTER TABLE public.secret_audit_log
2619
2621
  ADD COLUMN archived_at TIMESTAMPTZ NULL,
@@ -2634,7 +2636,7 @@ async function up$27(db) {
2634
2636
  DROP COLUMN IF EXISTS expires_at
2635
2637
  `.execute(db);
2636
2638
  }
2637
- async function down$27(db) {
2639
+ async function down$31(db) {
2638
2640
  await sql`
2639
2641
  ALTER TABLE public.access_log
2640
2642
  ADD COLUMN expires_at TIMESTAMPTZ NOT NULL DEFAULT (now() + INTERVAL '90 days')
@@ -2662,8 +2664,8 @@ async function down$27(db) {
2662
2664
  //#endregion
2663
2665
  //#region src/db/migrations/008_event_log_archived_at.ts
2664
2666
  var _008_event_log_archived_at_exports = /* @__PURE__ */ __exportAll({
2665
- down: () => down$26,
2666
- up: () => up$26
2667
+ down: () => down$30,
2668
+ up: () => up$30
2667
2669
  });
2668
2670
  /**
2669
2671
  * `event_log` cold-store schema additions plus removal of the
@@ -2701,7 +2703,7 @@ var _008_event_log_archived_at_exports = /* @__PURE__ */ __exportAll({
2701
2703
  * — best effort; rows inserted between `up()` and a hypothetical
2702
2704
  * `down()` would not have meaningful retention bounds.
2703
2705
  */
2704
- async function up$26(db) {
2706
+ async function up$30(db) {
2705
2707
  await sql`
2706
2708
  ALTER TABLE public.event_log
2707
2709
  ADD COLUMN archived_at TIMESTAMPTZ NULL,
@@ -2717,7 +2719,7 @@ async function up$26(db) {
2717
2719
  DROP COLUMN IF EXISTS expires_at
2718
2720
  `.execute(db);
2719
2721
  }
2720
- async function down$26(db) {
2722
+ async function down$30(db) {
2721
2723
  await sql`
2722
2724
  ALTER TABLE public.event_log
2723
2725
  ADD COLUMN expires_at TIMESTAMPTZ NOT NULL DEFAULT (now() + INTERVAL '30 days')
@@ -2736,8 +2738,8 @@ async function down$26(db) {
2736
2738
  //#endregion
2737
2739
  //#region src/db/migrations/009_access_log_trigram.ts
2738
2740
  var _009_access_log_trigram_exports = /* @__PURE__ */ __exportAll({
2739
- down: () => down$25,
2740
- up: () => up$25
2741
+ down: () => down$29,
2742
+ up: () => up$29
2741
2743
  });
2742
2744
  /**
2743
2745
  * Trigram (pg_trgm) index on access_log.error_message for the federated
@@ -2750,7 +2752,7 @@ var _009_access_log_trigram_exports = /* @__PURE__ */ __exportAll({
2750
2752
  * EXISTS` are both safe to re-run. No CONCURRENTLY because Kysely runs
2751
2753
  * migrations inside a transaction; the lock is brief on a sampled table.
2752
2754
  */
2753
- async function up$25(db) {
2755
+ async function up$29(db) {
2754
2756
  await sql`CREATE EXTENSION IF NOT EXISTS pg_trgm`.execute(db);
2755
2757
  await sql`
2756
2758
  CREATE INDEX IF NOT EXISTS access_log_error_message_trgm_idx
@@ -2759,14 +2761,14 @@ async function up$25(db) {
2759
2761
  WHERE error_message IS NOT NULL
2760
2762
  `.execute(db);
2761
2763
  }
2762
- async function down$25(db) {
2764
+ async function down$29(db) {
2763
2765
  await sql`DROP INDEX IF EXISTS public.access_log_error_message_trgm_idx`.execute(db);
2764
2766
  }
2765
2767
  //#endregion
2766
2768
  //#region src/db/migrations/010_cold_store_chunks.ts
2767
2769
  var _010_cold_store_chunks_exports = /* @__PURE__ */ __exportAll({
2768
- down: () => down$24,
2769
- up: () => up$24
2770
+ down: () => down$28,
2771
+ up: () => up$28
2770
2772
  });
2771
2773
  /**
2772
2774
  * Cold-store chunk index — Phase 2 (cold-store purge).
@@ -2798,7 +2800,7 @@ var _010_cold_store_chunks_exports = /* @__PURE__ */ __exportAll({
2798
2800
  * forever. Adapters that don't opt into per-bucket archival via
2799
2801
  * `coldTtlDays` don't insert here either.
2800
2802
  */
2801
- async function up$24(db) {
2803
+ async function up$28(db) {
2802
2804
  await sql`
2803
2805
  CREATE TABLE public.cold_store_chunks (
2804
2806
  db TEXT NOT NULL,
@@ -2825,14 +2827,14 @@ async function up$24(db) {
2825
2827
  ON public.cold_store_chunks (db, table_name, tenant_id, archived_at DESC)
2826
2828
  `.execute(db);
2827
2829
  }
2828
- async function down$24(db) {
2830
+ async function down$28(db) {
2829
2831
  await sql`DROP TABLE IF EXISTS public.cold_store_chunks`.execute(db);
2830
2832
  }
2831
2833
  //#endregion
2832
2834
  //#region src/db/migrations/011_drop_source_secrets_notify.ts
2833
2835
  var _011_drop_source_secrets_notify_exports = /* @__PURE__ */ __exportAll({
2834
- down: () => down$23,
2835
- up: () => up$23
2836
+ down: () => down$27,
2837
+ up: () => up$27
2836
2838
  });
2837
2839
  /**
2838
2840
  * Drop the `source_secrets_change_trigger` and the
@@ -2851,11 +2853,11 @@ var _011_drop_source_secrets_notify_exports = /* @__PURE__ */ __exportAll({
2851
2853
  * in `001_initial.ts`. They wake up no consumer until the `WebhookSecretManager`
2852
2854
  * is restored, so this migration is safe to roll back.
2853
2855
  */
2854
- async function up$23(db) {
2856
+ async function up$27(db) {
2855
2857
  await sql`DROP TRIGGER IF EXISTS source_secrets_change_trigger ON public.scoped_secrets`.execute(db);
2856
2858
  await sql`DROP FUNCTION IF EXISTS public.notify_source_secrets_change() CASCADE`.execute(db);
2857
2859
  }
2858
- async function down$23(db) {
2860
+ async function down$27(db) {
2859
2861
  await sql`
2860
2862
  CREATE OR REPLACE FUNCTION public.notify_source_secrets_change() RETURNS trigger
2861
2863
  LANGUAGE plpgsql
@@ -2894,8 +2896,8 @@ async function down$23(db) {
2894
2896
  //#endregion
2895
2897
  //#region src/db/migrations/012_peer_credentials_active_uniq.ts
2896
2898
  var _012_peer_credentials_active_uniq_exports = /* @__PURE__ */ __exportAll({
2897
- down: () => down$22,
2898
- up: () => up$22
2899
+ down: () => down$26,
2900
+ up: () => up$26
2899
2901
  });
2900
2902
  /**
2901
2903
  * Add a partial unique index on `peer_credentials (instance_id) WHERE
@@ -2921,7 +2923,7 @@ var _012_peer_credentials_active_uniq_exports = /* @__PURE__ */ __exportAll({
2921
2923
  * `down()` only drops the index; it does NOT undo the dedupe (there's no
2922
2924
  * safe way to recreate revoked rows, and the dedupe is monotonic).
2923
2925
  */
2924
- async function up$22(db) {
2926
+ async function up$26(db) {
2925
2927
  await sql`
2926
2928
  UPDATE public.peer_credentials
2927
2929
  SET revoked_at = NOW()
@@ -2939,14 +2941,14 @@ async function up$22(db) {
2939
2941
  WHERE revoked_at IS NULL
2940
2942
  `.execute(db);
2941
2943
  }
2942
- async function down$22(db) {
2944
+ async function down$26(db) {
2943
2945
  await sql`DROP INDEX IF EXISTS public.peer_credentials_active_uniq`.execute(db);
2944
2946
  }
2945
2947
  //#endregion
2946
2948
  //#region src/db/migrations/013_execution_log_bytes.ts
2947
2949
  var _013_execution_log_bytes_exports = /* @__PURE__ */ __exportAll({
2948
- down: () => down$21,
2949
- up: () => up$21
2950
+ down: () => down$25,
2951
+ up: () => up$25
2950
2952
  });
2951
2953
  /**
2952
2954
  * Add `log_bytes BIGINT NOT NULL DEFAULT 0` columns to `execution_runs` and
@@ -2965,7 +2967,7 @@ var _013_execution_log_bytes_exports = /* @__PURE__ */ __exportAll({
2965
2967
  *
2966
2968
  * Idempotent (`ADD COLUMN IF NOT EXISTS`).
2967
2969
  */
2968
- async function up$21(db) {
2970
+ async function up$25(db) {
2969
2971
  await sql`
2970
2972
  ALTER TABLE public.execution_runs
2971
2973
  ADD COLUMN IF NOT EXISTS log_bytes BIGINT NOT NULL DEFAULT 0
@@ -2975,15 +2977,15 @@ async function up$21(db) {
2975
2977
  ADD COLUMN IF NOT EXISTS log_bytes BIGINT NOT NULL DEFAULT 0
2976
2978
  `.execute(db);
2977
2979
  }
2978
- async function down$21(db) {
2980
+ async function down$25(db) {
2979
2981
  await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS log_bytes`.execute(db);
2980
2982
  await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS log_bytes`.execute(db);
2981
2983
  }
2982
2984
  //#endregion
2983
2985
  //#region src/db/migrations/014_kici_events_lease_retry.ts
2984
2986
  var _014_kici_events_lease_retry_exports = /* @__PURE__ */ __exportAll({
2985
- down: () => down$20,
2986
- up: () => up$20
2987
+ down: () => down$24,
2988
+ up: () => up$24
2987
2989
  });
2988
2990
  /**
2989
2991
  * Add lease + retry + DLQ columns to `kici_events` so the EventRouter can
@@ -3017,7 +3019,7 @@ var _014_kici_events_lease_retry_exports = /* @__PURE__ */ __exportAll({
3017
3019
  *
3018
3020
  * Idempotent (`ADD COLUMN IF NOT EXISTS` + `CREATE INDEX IF NOT EXISTS`).
3019
3021
  */
3020
- async function up$20(db) {
3022
+ async function up$24(db) {
3021
3023
  await sql`
3022
3024
  ALTER TABLE public.kici_events
3023
3025
  ADD COLUMN IF NOT EXISTS claimed_at TIMESTAMPTZ,
@@ -3048,7 +3050,7 @@ async function up$20(db) {
3048
3050
  WHERE dlq_at IS NOT NULL
3049
3051
  `.execute(db);
3050
3052
  }
3051
- async function down$20(db) {
3053
+ async function down$24(db) {
3052
3054
  await sql`DROP INDEX IF EXISTS public.idx_kici_events_dlq`.execute(db);
3053
3055
  await sql`DROP INDEX IF EXISTS public.idx_kici_events_lease_expired`.execute(db);
3054
3056
  await sql`DROP INDEX IF EXISTS public.idx_kici_events_retry_due`.execute(db);
@@ -3066,8 +3068,8 @@ async function down$20(db) {
3066
3068
  //#endregion
3067
3069
  //#region src/db/migrations/015_org_settings_customer_scoped.ts
3068
3070
  var _015_org_settings_customer_scoped_exports = /* @__PURE__ */ __exportAll({
3069
- down: () => down$19,
3070
- up: () => up$19
3071
+ down: () => down$23,
3072
+ up: () => up$23
3071
3073
  });
3072
3074
  /**
3073
3075
  * Org-scope `org_settings` and qualify each glob entry by source.
@@ -3095,7 +3097,7 @@ var _015_org_settings_customer_scoped_exports = /* @__PURE__ */ __exportAll({
3095
3097
  * Idempotent: a re-run on an already-migrated DB sees `customer_id` exists
3096
3098
  * and the list columns are already jsonb, so it is a no-op.
3097
3099
  */
3098
- async function up$19(db) {
3100
+ async function up$23(db) {
3099
3101
  if ((await sql`
3100
3102
  SELECT EXISTS (
3101
3103
  SELECT 1 FROM information_schema.columns
@@ -3220,7 +3222,7 @@ async function up$19(db) {
3220
3222
  await sql`DROP TABLE _org_settings_merged`.execute(db);
3221
3223
  await sql`DROP TABLE _org_settings_stage`.execute(db);
3222
3224
  }
3223
- async function down$19(db) {
3225
+ async function down$23(db) {
3224
3226
  if (!(await sql`
3225
3227
  SELECT EXISTS (
3226
3228
  SELECT 1 FROM information_schema.columns
@@ -3245,8 +3247,8 @@ async function down$19(db) {
3245
3247
  //#endregion
3246
3248
  //#region src/db/migrations/016_org_settings_allow_http_npm.ts
3247
3249
  var _016_org_settings_allow_http_npm_exports = /* @__PURE__ */ __exportAll({
3248
- down: () => down$18,
3249
- up: () => up$18
3250
+ down: () => down$22,
3251
+ up: () => up$22
3250
3252
  });
3251
3253
  /**
3252
3254
  * Add `org_settings.allow_http_npm_registries boolean NOT NULL DEFAULT false`.
@@ -3259,7 +3261,7 @@ var _016_org_settings_allow_http_npm_exports = /* @__PURE__ */ __exportAll({
3259
3261
  *
3260
3262
  * Idempotent: a re-run on a DB that already has the column is a no-op.
3261
3263
  */
3262
- async function up$18(db) {
3264
+ async function up$22(db) {
3263
3265
  if ((await sql`
3264
3266
  SELECT EXISTS (
3265
3267
  SELECT 1 FROM information_schema.columns
@@ -3273,7 +3275,7 @@ async function up$18(db) {
3273
3275
  ADD COLUMN allow_http_npm_registries boolean NOT NULL DEFAULT false
3274
3276
  `.execute(db);
3275
3277
  }
3276
- async function down$18(db) {
3278
+ async function down$22(db) {
3277
3279
  await sql`
3278
3280
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS allow_http_npm_registries
3279
3281
  `.execute(db);
@@ -3281,8 +3283,8 @@ async function down$18(db) {
3281
3283
  //#endregion
3282
3284
  //#region src/db/migrations/017_org_id_widen.ts
3283
3285
  var _017_org_id_widen_exports = /* @__PURE__ */ __exportAll({
3284
- down: () => down$17,
3285
- up: () => up$17
3286
+ down: () => down$21,
3287
+ up: () => up$21
3286
3288
  });
3287
3289
  /**
3288
3290
  * Widen every orchestrator-side `org_id varchar(12)` column to
@@ -3322,17 +3324,17 @@ const ORG_ID_TABLES$1 = [
3322
3324
  "held_runs",
3323
3325
  "scoped_secrets"
3324
3326
  ];
3325
- async function up$17(db) {
3327
+ async function up$21(db) {
3326
3328
  for (const table of ORG_ID_TABLES$1) await sql.raw(`ALTER TABLE public.${table} ALTER COLUMN org_id TYPE varchar(16)`).execute(db);
3327
3329
  }
3328
- async function down$17(db) {
3330
+ async function down$21(db) {
3329
3331
  for (const table of ORG_ID_TABLES$1) await sql.raw(`ALTER TABLE public.${table} ALTER COLUMN org_id TYPE varchar(12)`).execute(db);
3330
3332
  }
3331
3333
  //#endregion
3332
3334
  //#region src/db/migrations/018_org_id_prefix_backfill.ts
3333
3335
  var _018_org_id_prefix_backfill_exports = /* @__PURE__ */ __exportAll({
3334
- down: () => down$16,
3335
- up: () => up$16
3336
+ down: () => down$20,
3337
+ up: () => up$20
3336
3338
  });
3337
3339
  /**
3338
3340
  * Prefix every orchestrator-side tenant string with `org_` to align
@@ -3377,19 +3379,19 @@ const CUSTOMER_ID_TABLES = [
3377
3379
  "workflow_registrations",
3378
3380
  "org_settings"
3379
3381
  ];
3380
- async function up$16(db) {
3382
+ async function up$20(db) {
3381
3383
  for (const table of ORG_ID_TABLES) await sql.raw(`UPDATE public.${table} SET org_id = 'org_' || org_id WHERE org_id <> 'kici-admin' AND org_id NOT LIKE 'org\\_%' ESCAPE '\\'`).execute(db);
3382
3384
  for (const table of CUSTOMER_ID_TABLES) await sql.raw(`UPDATE public.${table} SET customer_id = 'org_' || customer_id WHERE customer_id <> 'kici-admin' AND customer_id NOT LIKE 'org\\_%' ESCAPE '\\'`).execute(db);
3383
3385
  }
3384
- async function down$16(db) {
3386
+ async function down$20(db) {
3385
3387
  for (const table of CUSTOMER_ID_TABLES) await sql.raw(`UPDATE public.${table} SET customer_id = substring(customer_id from 5) WHERE customer_id LIKE 'org\\_%' ESCAPE '\\'`).execute(db);
3386
3388
  for (const table of ORG_ID_TABLES) await sql.raw(`UPDATE public.${table} SET org_id = substring(org_id from 5) WHERE org_id LIKE 'org\\_%' ESCAPE '\\'`).execute(db);
3387
3389
  }
3388
3390
  //#endregion
3389
3391
  //#region src/db/migrations/019_generic_sources_change_notify.ts
3390
3392
  var _019_generic_sources_change_notify_exports = /* @__PURE__ */ __exportAll({
3391
- down: () => down$15,
3392
- up: () => up$15
3393
+ down: () => down$19,
3394
+ up: () => up$19
3393
3395
  });
3394
3396
  /**
3395
3397
  * Add a Postgres trigger on `generic_webhook_sources` that emits
@@ -3412,7 +3414,7 @@ var _019_generic_sources_change_notify_exports = /* @__PURE__ */ __exportAll({
3412
3414
  * (`notify_sources_change()` + `sources_change_trigger`, defined in
3413
3415
  * `001_initial.ts`).
3414
3416
  */
3415
- async function up$15(db) {
3417
+ async function up$19(db) {
3416
3418
  await sql`
3417
3419
  CREATE FUNCTION public.notify_generic_sources_change() RETURNS trigger
3418
3420
  LANGUAGE plpgsql
@@ -3433,15 +3435,15 @@ async function up$15(db) {
3433
3435
  FOR EACH ROW EXECUTE FUNCTION public.notify_generic_sources_change()
3434
3436
  `.execute(db);
3435
3437
  }
3436
- async function down$15(db) {
3438
+ async function down$19(db) {
3437
3439
  await sql`DROP TRIGGER IF EXISTS generic_sources_change_trigger ON public.generic_webhook_sources`.execute(db);
3438
3440
  await sql`DROP FUNCTION IF EXISTS public.notify_generic_sources_change()`.execute(db);
3439
3441
  }
3440
3442
  //#endregion
3441
3443
  //#region src/db/migrations/020_org_settings_dashboard_write_policy.ts
3442
3444
  var _020_org_settings_dashboard_write_policy_exports = /* @__PURE__ */ __exportAll({
3443
- down: () => down$14,
3444
- up: () => up$14
3445
+ down: () => down$18,
3446
+ up: () => up$18
3445
3447
  });
3446
3448
  /**
3447
3449
  * Add `org_settings.dashboard_write_policy jsonb NOT NULL DEFAULT '{}'`.
@@ -3456,7 +3458,7 @@ var _020_org_settings_dashboard_write_policy_exports = /* @__PURE__ */ __exportA
3456
3458
  *
3457
3459
  * Idempotent: a re-run on a DB that already has the column is a no-op.
3458
3460
  */
3459
- async function up$14(db) {
3461
+ async function up$18(db) {
3460
3462
  if ((await sql`
3461
3463
  SELECT EXISTS (
3462
3464
  SELECT 1 FROM information_schema.columns
@@ -3470,7 +3472,7 @@ async function up$14(db) {
3470
3472
  ADD COLUMN dashboard_write_policy jsonb NOT NULL DEFAULT '{}'::jsonb
3471
3473
  `.execute(db);
3472
3474
  }
3473
- async function down$14(db) {
3475
+ async function down$18(db) {
3474
3476
  await sql`
3475
3477
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS dashboard_write_policy
3476
3478
  `.execute(db);
@@ -3478,8 +3480,8 @@ async function down$14(db) {
3478
3480
  //#endregion
3479
3481
  //#region src/db/migrations/021_check_run_tracking.ts
3480
3482
  var _021_check_run_tracking_exports = /* @__PURE__ */ __exportAll({
3481
- down: () => down$13,
3482
- up: () => up$13
3483
+ down: () => down$17,
3484
+ up: () => up$17
3483
3485
  });
3484
3486
  /**
3485
3487
  * Add `check_run_tracking` table for HA-safe check-run state persistence.
@@ -3503,7 +3505,7 @@ var _021_check_run_tracking_exports = /* @__PURE__ */ __exportAll({
3503
3505
  *
3504
3506
  * Idempotent: a re-run on a DB that already has the table is a no-op.
3505
3507
  */
3506
- async function up$13(db) {
3508
+ async function up$17(db) {
3507
3509
  if ((await sql`
3508
3510
  SELECT EXISTS (
3509
3511
  SELECT 1 FROM information_schema.tables
@@ -3534,14 +3536,14 @@ async function up$13(db) {
3534
3536
  WHERE run_id IS NOT NULL
3535
3537
  `.execute(db);
3536
3538
  }
3537
- async function down$13(db) {
3539
+ async function down$17(db) {
3538
3540
  await sql`DROP TABLE IF EXISTS public.check_run_tracking`.execute(db);
3539
3541
  }
3540
3542
  //#endregion
3541
3543
  //#region src/db/migrations/022_scaler_manager_state.ts
3542
3544
  var _022_scaler_manager_state_exports = /* @__PURE__ */ __exportAll({
3543
- down: () => down$12,
3544
- up: () => up$12
3545
+ down: () => down$16,
3546
+ up: () => up$16
3545
3547
  });
3546
3548
  /**
3547
3549
  * Add three tables persisting `ScalerManager` per-coord state:
@@ -3568,7 +3570,7 @@ var _022_scaler_manager_state_exports = /* @__PURE__ */ __exportAll({
3568
3570
  * Idempotent: a re-run on a DB that already has any of these tables
3569
3571
  * leaves the existing one alone.
3570
3572
  */
3571
- async function up$12(db) {
3573
+ async function up$16(db) {
3572
3574
  const tableExists = async (name) => {
3573
3575
  return (await sql`
3574
3576
  SELECT EXISTS (
@@ -3618,7 +3620,7 @@ async function up$12(db) {
3618
3620
  `.execute(db);
3619
3621
  }
3620
3622
  }
3621
- async function down$12(db) {
3623
+ async function down$16(db) {
3622
3624
  await sql`DROP TABLE IF EXISTS public.scaler_reservations`.execute(db);
3623
3625
  await sql`DROP TABLE IF EXISTS public.scaler_agent_jobs`.execute(db);
3624
3626
  await sql`DROP TABLE IF EXISTS public.scaler_spawning_agents`.execute(db);
@@ -3626,8 +3628,8 @@ async function down$12(db) {
3626
3628
  //#endregion
3627
3629
  //#region src/db/migrations/023_dispatch_queue_recovery_deadline.ts
3628
3630
  var _023_dispatch_queue_recovery_deadline_exports = /* @__PURE__ */ __exportAll({
3629
- down: () => down$11,
3630
- up: () => up$11
3631
+ down: () => down$15,
3632
+ up: () => up$15
3631
3633
  });
3632
3634
  /**
3633
3635
  * Add `dispatch_queue.recovery_deadline TIMESTAMPTZ` and
@@ -3651,7 +3653,7 @@ var _023_dispatch_queue_recovery_deadline_exports = /* @__PURE__ */ __exportAll(
3651
3653
  * Idempotent: re-running on a DB that already has either column is a
3652
3654
  * no-op.
3653
3655
  */
3654
- async function up$11(db) {
3656
+ async function up$15(db) {
3655
3657
  const colExists = async (name) => {
3656
3658
  return (await sql`
3657
3659
  SELECT EXISTS (
@@ -3676,7 +3678,7 @@ async function up$11(db) {
3676
3678
  WHERE recovery_deadline IS NOT NULL
3677
3679
  `.execute(db);
3678
3680
  }
3679
- async function down$11(db) {
3681
+ async function down$15(db) {
3680
3682
  await sql`DROP INDEX IF EXISTS public.idx_dispatch_queue_recovery_deadline`.execute(db);
3681
3683
  await sql`
3682
3684
  ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS recovery_agent_id
@@ -3688,8 +3690,8 @@ async function down$11(db) {
3688
3690
  //#endregion
3689
3691
  //#region src/db/migrations/024_dispatch_queue_provisioning_error.ts
3690
3692
  var _024_dispatch_queue_provisioning_error_exports = /* @__PURE__ */ __exportAll({
3691
- down: () => down$10,
3692
- up: () => up$10
3693
+ down: () => down$14,
3694
+ up: () => up$14
3693
3695
  });
3694
3696
  /**
3695
3697
  * Add `dispatch_queue.last_provisioning_error TEXT` recording the most
@@ -3705,7 +3707,7 @@ var _024_dispatch_queue_provisioning_error_exports = /* @__PURE__ */ __exportAll
3705
3707
  *
3706
3708
  * Idempotent: re-running on a DB that already has the column is a no-op.
3707
3709
  */
3708
- async function up$10(db) {
3710
+ async function up$14(db) {
3709
3711
  const colExists = async (name) => {
3710
3712
  return (await sql`
3711
3713
  SELECT EXISTS (
@@ -3721,7 +3723,7 @@ async function up$10(db) {
3721
3723
  ADD COLUMN last_provisioning_error TEXT
3722
3724
  `.execute(db);
3723
3725
  }
3724
- async function down$10(db) {
3726
+ async function down$14(db) {
3725
3727
  await sql`
3726
3728
  ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS last_provisioning_error
3727
3729
  `.execute(db);
@@ -3729,8 +3731,8 @@ async function down$10(db) {
3729
3731
  //#endregion
3730
3732
  //#region src/db/migrations/025_init_failure.ts
3731
3733
  var _025_init_failure_exports = /* @__PURE__ */ __exportAll({
3732
- down: () => down$9,
3733
- up: () => up$9
3734
+ down: () => down$13,
3735
+ up: () => up$13
3734
3736
  });
3735
3737
  /**
3736
3738
  * Add `init_failure jsonb` columns to `execution_runs` and `execution_jobs`.
@@ -3744,7 +3746,7 @@ var _025_init_failure_exports = /* @__PURE__ */ __exportAll({
3744
3746
  * Idempotent: re-running on a DB that already has either column is a no-op
3745
3747
  * for that column.
3746
3748
  */
3747
- async function up$9(db) {
3749
+ async function up$13(db) {
3748
3750
  const colExists = async (table, name) => {
3749
3751
  return (await sql`
3750
3752
  SELECT EXISTS (
@@ -3764,7 +3766,7 @@ async function up$9(db) {
3764
3766
  ADD COLUMN init_failure JSONB DEFAULT NULL
3765
3767
  `.execute(db);
3766
3768
  }
3767
- async function down$9(db) {
3769
+ async function down$13(db) {
3768
3770
  await sql`
3769
3771
  ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS init_failure
3770
3772
  `.execute(db);
@@ -3775,8 +3777,8 @@ async function down$9(db) {
3775
3777
  //#endregion
3776
3778
  //#region src/db/migrations/026_event_log_lockfile_corrupt.ts
3777
3779
  var _026_event_log_lockfile_corrupt_exports = /* @__PURE__ */ __exportAll({
3778
- down: () => down$8,
3779
- up: () => up$8
3780
+ down: () => down$12,
3781
+ up: () => up$12
3780
3782
  });
3781
3783
  /**
3782
3784
  * Extend the event_log.status CHECK constraint with 'lockfile_corrupt' so the
@@ -3785,7 +3787,7 @@ var _026_event_log_lockfile_corrupt_exports = /* @__PURE__ */ __exportAll({
3785
3787
  *
3786
3788
  * Idempotent: the DROP ... IF EXISTS / re-ADD pair re-runs cleanly.
3787
3789
  */
3788
- async function up$8(db) {
3790
+ async function up$12(db) {
3789
3791
  await sql`ALTER TABLE event_log DROP CONSTRAINT IF EXISTS event_log_status_check`.execute(db);
3790
3792
  await sql`
3791
3793
  ALTER TABLE event_log ADD CONSTRAINT event_log_status_check
@@ -3795,7 +3797,7 @@ async function up$8(db) {
3795
3797
  ])))
3796
3798
  `.execute(db);
3797
3799
  }
3798
- async function down$8(db) {
3800
+ async function down$12(db) {
3799
3801
  await sql`ALTER TABLE event_log DROP CONSTRAINT IF EXISTS event_log_status_check`.execute(db);
3800
3802
  await sql`
3801
3803
  ALTER TABLE event_log ADD CONSTRAINT event_log_status_check
@@ -3808,8 +3810,8 @@ async function down$8(db) {
3808
3810
  //#endregion
3809
3811
  //#region src/db/migrations/027_workflow_timeout.ts
3810
3812
  var _027_workflow_timeout_exports = /* @__PURE__ */ __exportAll({
3811
- down: () => down$7,
3812
- up: () => up$7
3813
+ down: () => down$11,
3814
+ up: () => up$11
3813
3815
  });
3814
3816
  /**
3815
3817
  * Add `workflow_timeout_ms integer` to `execution_runs`.
@@ -3827,13 +3829,13 @@ var _027_workflow_timeout_exports = /* @__PURE__ */ __exportAll({
3827
3829
  *
3828
3830
  * Idempotent: re-running on a DB that already has the column is a no-op.
3829
3831
  */
3830
- async function up$7(db) {
3832
+ async function up$11(db) {
3831
3833
  await sql`
3832
3834
  ALTER TABLE public.execution_runs
3833
3835
  ADD COLUMN IF NOT EXISTS workflow_timeout_ms INTEGER DEFAULT NULL
3834
3836
  `.execute(db);
3835
3837
  }
3836
- async function down$7(db) {
3838
+ async function down$11(db) {
3837
3839
  await sql`
3838
3840
  ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS workflow_timeout_ms
3839
3841
  `.execute(db);
@@ -3841,8 +3843,8 @@ async function down$7(db) {
3841
3843
  //#endregion
3842
3844
  //#region src/db/migrations/028_org_settings_user_cache.ts
3843
3845
  var _028_org_settings_user_cache_exports = /* @__PURE__ */ __exportAll({
3844
- down: () => down$6,
3845
- up: () => up$6
3846
+ down: () => down$10,
3847
+ up: () => up$10
3846
3848
  });
3847
3849
  /**
3848
3850
  * Add `org_settings.user_cache_quota_bytes bigint` and
@@ -3871,7 +3873,7 @@ async function columnExists(db, column) {
3871
3873
  ) AS exists
3872
3874
  `.execute(db)).rows[0]?.exists ?? false;
3873
3875
  }
3874
- async function up$6(db) {
3876
+ async function up$10(db) {
3875
3877
  if (!await columnExists(db, "user_cache_quota_bytes")) await sql`
3876
3878
  ALTER TABLE public.org_settings
3877
3879
  ADD COLUMN user_cache_quota_bytes bigint
@@ -3881,7 +3883,7 @@ async function up$6(db) {
3881
3883
  ADD COLUMN user_cache_ttl_ms bigint
3882
3884
  `.execute(db);
3883
3885
  }
3884
- async function down$6(db) {
3886
+ async function down$10(db) {
3885
3887
  await sql`
3886
3888
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS user_cache_quota_bytes
3887
3889
  `.execute(db);
@@ -3892,8 +3894,8 @@ async function down$6(db) {
3892
3894
  //#endregion
3893
3895
  //#region src/db/migrations/029_dispatch_queue_attempts.ts
3894
3896
  var _029_dispatch_queue_attempts_exports = /* @__PURE__ */ __exportAll({
3895
- down: () => down$5,
3896
- up: () => up$5
3897
+ down: () => down$9,
3898
+ up: () => up$9
3897
3899
  });
3898
3900
  /**
3899
3901
  * Add `dispatch_queue.dispatch_attempts INT NOT NULL DEFAULT 0`.
@@ -3907,7 +3909,7 @@ var _029_dispatch_queue_attempts_exports = /* @__PURE__ */ __exportAll({
3907
3909
  *
3908
3910
  * Idempotent: re-running on a DB that already has the column is a no-op.
3909
3911
  */
3910
- async function up$5(db) {
3912
+ async function up$9(db) {
3911
3913
  if (!((await sql`
3912
3914
  SELECT EXISTS (
3913
3915
  SELECT 1 FROM information_schema.columns
@@ -3920,7 +3922,7 @@ async function up$5(db) {
3920
3922
  ADD COLUMN dispatch_attempts INT NOT NULL DEFAULT 0
3921
3923
  `.execute(db);
3922
3924
  }
3923
- async function down$5(db) {
3925
+ async function down$9(db) {
3924
3926
  await sql`
3925
3927
  ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS dispatch_attempts
3926
3928
  `.execute(db);
@@ -3928,8 +3930,8 @@ async function down$5(db) {
3928
3930
  //#endregion
3929
3931
  //#region src/db/migrations/030_held_runs_env_set_null.ts
3930
3932
  var _030_held_runs_env_set_null_exports = /* @__PURE__ */ __exportAll({
3931
- down: () => down$4,
3932
- up: () => up$4
3933
+ down: () => down$8,
3934
+ up: () => up$8
3933
3935
  });
3934
3936
  /**
3935
3937
  * held_runs.environment_id becomes nullable with ON DELETE SET NULL so
@@ -3940,14 +3942,14 @@ var _030_held_runs_env_set_null_exports = /* @__PURE__ */ __exportAll({
3940
3942
  * Idempotent: dropping the NOT NULL and the constraint are both no-ops on a
3941
3943
  * re-run, and the constraint is re-created with the SET NULL action.
3942
3944
  */
3943
- async function up$4(db) {
3945
+ async function up$8(db) {
3944
3946
  await sql`ALTER TABLE public.held_runs ALTER COLUMN environment_id DROP NOT NULL`.execute(db);
3945
3947
  await sql`ALTER TABLE public.held_runs DROP CONSTRAINT IF EXISTS held_runs_environment_id_fkey`.execute(db);
3946
3948
  await sql`ALTER TABLE public.held_runs
3947
3949
  ADD CONSTRAINT held_runs_environment_id_fkey
3948
3950
  FOREIGN KEY (environment_id) REFERENCES public.environments(id) ON DELETE SET NULL`.execute(db);
3949
3951
  }
3950
- async function down$4(db) {
3952
+ async function down$8(db) {
3951
3953
  await sql`ALTER TABLE public.held_runs DROP CONSTRAINT IF EXISTS held_runs_environment_id_fkey`.execute(db);
3952
3954
  await sql`ALTER TABLE public.held_runs
3953
3955
  ADD CONSTRAINT held_runs_environment_id_fkey
@@ -3957,8 +3959,8 @@ async function down$4(db) {
3957
3959
  //#endregion
3958
3960
  //#region src/db/migrations/031_dispatch_queue_ack_deadline.ts
3959
3961
  var _031_dispatch_queue_ack_deadline_exports = /* @__PURE__ */ __exportAll({
3960
- down: () => down$3,
3961
- up: () => up$3
3962
+ down: () => down$7,
3963
+ up: () => up$7
3962
3964
  });
3963
3965
  /**
3964
3966
  * Add `dispatch_queue.ack_deadline TIMESTAMPTZ` and
@@ -3975,7 +3977,7 @@ var _031_dispatch_queue_ack_deadline_exports = /* @__PURE__ */ __exportAll({
3975
3977
  *
3976
3978
  * Idempotent: re-running on a DB that already has either column is a no-op.
3977
3979
  */
3978
- async function up$3(db) {
3980
+ async function up$7(db) {
3979
3981
  const colExists = async (name) => {
3980
3982
  return (await sql`
3981
3983
  SELECT EXISTS (
@@ -4000,7 +4002,7 @@ async function up$3(db) {
4000
4002
  WHERE ack_deadline IS NOT NULL
4001
4003
  `.execute(db);
4002
4004
  }
4003
- async function down$3(db) {
4005
+ async function down$7(db) {
4004
4006
  await sql`DROP INDEX IF EXISTS public.idx_dispatch_queue_ack_deadline`.execute(db);
4005
4007
  await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS ack_agent_id`.execute(db);
4006
4008
  await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS ack_deadline`.execute(db);
@@ -4008,8 +4010,8 @@ async function down$3(db) {
4008
4010
  //#endregion
4009
4011
  //#region src/db/migrations/032_org_settings_dispatch_ack_timeout.ts
4010
4012
  var _032_org_settings_dispatch_ack_timeout_exports = /* @__PURE__ */ __exportAll({
4011
- down: () => down$2,
4012
- up: () => up$2
4013
+ down: () => down$6,
4014
+ up: () => up$6
4013
4015
  });
4014
4016
  /**
4015
4017
  * Add `org_settings.dispatch_ack_timeout_ms BIGINT` (nullable).
@@ -4021,7 +4023,7 @@ var _032_org_settings_dispatch_ack_timeout_exports = /* @__PURE__ */ __exportAll
4021
4023
  *
4022
4024
  * Idempotent: a re-run on a DB that already has the column is a no-op.
4023
4025
  */
4024
- async function up$2(db) {
4026
+ async function up$6(db) {
4025
4027
  if ((await sql`
4026
4028
  SELECT EXISTS (
4027
4029
  SELECT 1 FROM information_schema.columns
@@ -4035,7 +4037,7 @@ async function up$2(db) {
4035
4037
  ADD COLUMN dispatch_ack_timeout_ms BIGINT
4036
4038
  `.execute(db);
4037
4039
  }
4038
- async function down$2(db) {
4040
+ async function down$6(db) {
4039
4041
  await sql`
4040
4042
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS dispatch_ack_timeout_ms
4041
4043
  `.execute(db);
@@ -4043,8 +4045,8 @@ async function down$2(db) {
4043
4045
  //#endregion
4044
4046
  //#region src/db/migrations/033_org_settings_approval.ts
4045
4047
  var _033_org_settings_approval_exports = /* @__PURE__ */ __exportAll({
4046
- down: () => down$1,
4047
- up: () => up$1
4048
+ down: () => down$5,
4049
+ up: () => up$5
4048
4050
  });
4049
4051
  /**
4050
4052
  * Add the two approval-policy columns to `org_settings`:
@@ -4061,7 +4063,7 @@ var _033_org_settings_approval_exports = /* @__PURE__ */ __exportAll({
4061
4063
  * and the orchestrator admin route. Idempotent: a re-run on a DB that already
4062
4064
  * has the columns is a no-op (each column is guarded independently).
4063
4065
  */
4064
- async function up$1(db) {
4066
+ async function up$5(db) {
4065
4067
  const colExists = async (column) => {
4066
4068
  return (await sql`
4067
4069
  SELECT EXISTS (
@@ -4081,7 +4083,7 @@ async function up$1(db) {
4081
4083
  ADD COLUMN allow_self_approval BOOLEAN NOT NULL DEFAULT true
4082
4084
  `.execute(db);
4083
4085
  }
4084
- async function down$1(db) {
4086
+ async function down$5(db) {
4085
4087
  await sql`
4086
4088
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS approval_expiry_seconds
4087
4089
  `.execute(db);
@@ -4092,8 +4094,8 @@ async function down$1(db) {
4092
4094
  //#endregion
4093
4095
  //#region src/db/migrations/034_held_runs_generalize.ts
4094
4096
  var _034_held_runs_generalize_exports = /* @__PURE__ */ __exportAll({
4095
- down: () => down,
4096
- up: () => up
4097
+ down: () => down$4,
4098
+ up: () => up$4
4097
4099
  });
4098
4100
  /**
4099
4101
  * Generalize `held_runs` from an environment-only hold into the unified
@@ -4115,7 +4117,7 @@ var _034_held_runs_generalize_exports = /* @__PURE__ */ __exportAll({
4115
4117
  * New `held_run_approvals` table: one row per approver decision, FK to
4116
4118
  * `held_runs.id` (uuid) with ON DELETE CASCADE.
4117
4119
  */
4118
- async function up(db) {
4120
+ async function up$4(db) {
4119
4121
  const colExists = async (column) => {
4120
4122
  return (await sql`
4121
4123
  SELECT EXISTS (
@@ -4148,7 +4150,7 @@ async function up(db) {
4148
4150
  ON public.held_run_approvals USING btree (held_run_id)
4149
4151
  `.execute(db);
4150
4152
  }
4151
- async function down(db) {
4153
+ async function down$4(db) {
4152
4154
  await sql`DROP TABLE IF EXISTS public.held_run_approvals`.execute(db);
4153
4155
  await sql`ALTER TABLE public.held_runs DROP COLUMN IF EXISTS approval_requirement`.execute(db);
4154
4156
  await sql`ALTER TABLE public.held_runs DROP COLUMN IF EXISTS trigger_source`.execute(db);
@@ -4156,6 +4158,169 @@ async function down(db) {
4156
4158
  await sql`ALTER TABLE public.held_runs DROP COLUMN IF EXISTS hold_scope`.execute(db);
4157
4159
  }
4158
4160
  //#endregion
4161
+ //#region src/db/migrations/035_pending_workflow_contexts.ts
4162
+ var _035_pending_workflow_contexts_exports = /* @__PURE__ */ __exportAll({
4163
+ down: () => down$3,
4164
+ up: () => up$3
4165
+ });
4166
+ /**
4167
+ * Pending workflow dispatch context — backs resume of a workflow whose install
4168
+ * gate held. One row per held run: the serializable dispatch inputs needed to
4169
+ * rebuild the WorkflowDispatchContext when the hold releases (reviewer approve,
4170
+ * wait-timer expiry, concurrency slot free). The row is deleted once the resume
4171
+ * dispatch has been kicked off.
4172
+ */
4173
+ async function up$3(db) {
4174
+ await sql`
4175
+ CREATE TABLE IF NOT EXISTS public.pending_workflow_contexts (
4176
+ run_id text PRIMARY KEY,
4177
+ org_id text NOT NULL,
4178
+ context jsonb NOT NULL,
4179
+ created_at timestamp with time zone DEFAULT now() NOT NULL
4180
+ )
4181
+ `.execute(db);
4182
+ }
4183
+ async function down$3(db) {
4184
+ await sql`DROP TABLE IF EXISTS public.pending_workflow_contexts`.execute(db);
4185
+ }
4186
+ //#endregion
4187
+ //#region src/db/migrations/036_attestations.ts
4188
+ var _036_attestations_exports = /* @__PURE__ */ __exportAll({
4189
+ down: () => down$2,
4190
+ up: () => up$2
4191
+ });
4192
+ /**
4193
+ * Add the `attestations` table for build-provenance bundles.
4194
+ *
4195
+ * When a workflow step calls `ctx.attestProvenance`, the agent uploads the
4196
+ * signed KiCI bundle to object storage under
4197
+ * `provenance/{run_id}/{job_id}/{subject_digest}.kici.json` and the
4198
+ * orchestrator records one row here so the dashboard can list and fetch
4199
+ * attestations per run/job.
4200
+ *
4201
+ * Idempotent: a re-run on a DB that already has the table is a no-op.
4202
+ */
4203
+ async function up$2(db) {
4204
+ if ((await sql`
4205
+ SELECT EXISTS (
4206
+ SELECT 1 FROM information_schema.tables
4207
+ WHERE table_schema = 'public'
4208
+ AND table_name = 'attestations'
4209
+ ) AS exists
4210
+ `.execute(db)).rows[0]?.exists) return;
4211
+ await sql`
4212
+ CREATE TABLE public.attestations (
4213
+ id TEXT PRIMARY KEY,
4214
+ run_id TEXT NOT NULL,
4215
+ job_id TEXT NOT NULL,
4216
+ subject_name TEXT NOT NULL,
4217
+ subject_digest TEXT NOT NULL,
4218
+ storage_key TEXT NOT NULL,
4219
+ mode TEXT NOT NULL,
4220
+ media_type TEXT NOT NULL,
4221
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
4222
+ )
4223
+ `.execute(db);
4224
+ await sql`
4225
+ CREATE INDEX idx_attestations_run_job
4226
+ ON public.attestations (run_id, job_id)
4227
+ `.execute(db);
4228
+ }
4229
+ async function down$2(db) {
4230
+ await sql`DROP TABLE IF EXISTS public.attestations`.execute(db);
4231
+ }
4232
+ //#endregion
4233
+ //#region src/db/migrations/037_generic_sources_provider_type_local.ts
4234
+ var _037_generic_sources_provider_type_local_exports = /* @__PURE__ */ __exportAll({
4235
+ down: () => down$1,
4236
+ up: () => up$1
4237
+ });
4238
+ /**
4239
+ * Replace the `generic_webhook_sources.provider_type` CHECK constraint so it
4240
+ * permits `'local'` instead of `'internal'`, and migrate any carried-over
4241
+ * `'internal'` rows to `'local'`.
4242
+ *
4243
+ * The local filesystem (`file://`) provider is `provider_type='local'`; the
4244
+ * value used to be `'internal'`. The original CHECK constraint
4245
+ * (`generic_webhook_sources_provider_type_check`) allowed only
4246
+ * `{'generic','internal'}`, so writing a `'local'` row fails. Universal-git
4247
+ * sources keep `provider_type='generic'` (discriminated by `git_config`), so
4248
+ * the new allowed set is `{'generic','local'}`.
4249
+ *
4250
+ * Idempotent: the constraint is dropped IF EXISTS and recreated; the data
4251
+ * backfill is a plain UPDATE that is a no-op once no `'internal'` rows remain.
4252
+ */
4253
+ async function up$1(db) {
4254
+ await sql`
4255
+ ALTER TABLE public.generic_webhook_sources
4256
+ DROP CONSTRAINT IF EXISTS generic_webhook_sources_provider_type_check
4257
+ `.execute(db);
4258
+ await sql`
4259
+ UPDATE public.generic_webhook_sources
4260
+ SET provider_type = 'local'
4261
+ WHERE provider_type = 'internal'
4262
+ `.execute(db);
4263
+ await sql`
4264
+ ALTER TABLE public.generic_webhook_sources
4265
+ ADD CONSTRAINT generic_webhook_sources_provider_type_check
4266
+ CHECK (provider_type = ANY (ARRAY['generic'::text, 'local'::text]))
4267
+ `.execute(db);
4268
+ }
4269
+ async function down$1(db) {
4270
+ await sql`
4271
+ ALTER TABLE public.generic_webhook_sources
4272
+ DROP CONSTRAINT IF EXISTS generic_webhook_sources_provider_type_check
4273
+ `.execute(db);
4274
+ await sql`
4275
+ UPDATE public.generic_webhook_sources
4276
+ SET provider_type = 'internal'
4277
+ WHERE provider_type = 'local'
4278
+ `.execute(db);
4279
+ await sql`
4280
+ ALTER TABLE public.generic_webhook_sources
4281
+ ADD CONSTRAINT generic_webhook_sources_provider_type_check
4282
+ CHECK (provider_type = ANY (ARRAY['generic'::text, 'internal'::text]))
4283
+ `.execute(db);
4284
+ }
4285
+ //#endregion
4286
+ //#region src/db/migrations/038_remote_sources.ts
4287
+ var _038_remote_sources_exports = /* @__PURE__ */ __exportAll({
4288
+ down: () => down,
4289
+ up: () => up
4290
+ });
4291
+ /**
4292
+ * `remote_sources` anchors a Platform-relayed `kici run remote` to its real
4293
+ * org. One row per org served by this orchestrator: routing key
4294
+ * `remote:<orgId>` maps to the canonical org id, so `resolveOrgId` resolves
4295
+ * the real tenant through the same local-source path a webhook takes — no
4296
+ * GitHub App, no webhook secret, no manual setup. The row is auto-provisioned
4297
+ * on Platform auth from the canonical org id carried on `auth.success`.
4298
+ *
4299
+ * Idempotent: a re-run on a DB that already has the table is a no-op.
4300
+ */
4301
+ async function up(db) {
4302
+ if ((await sql`
4303
+ SELECT EXISTS (
4304
+ SELECT 1 FROM information_schema.tables
4305
+ WHERE table_schema = 'public' AND table_name = 'remote_sources'
4306
+ ) AS exists
4307
+ `.execute(db)).rows[0]?.exists) return;
4308
+ await sql`
4309
+ CREATE TABLE public.remote_sources (
4310
+ customer_id VARCHAR(255) NOT NULL,
4311
+ routing_key VARCHAR(255) NOT NULL,
4312
+ cluster_id TEXT,
4313
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
4314
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
4315
+ CONSTRAINT remote_sources_customer_id_key UNIQUE (customer_id),
4316
+ CONSTRAINT remote_sources_routing_key_key UNIQUE (routing_key)
4317
+ )
4318
+ `.execute(db);
4319
+ }
4320
+ async function down(db) {
4321
+ await sql`DROP TABLE IF EXISTS public.remote_sources`.execute(db);
4322
+ }
4323
+ //#endregion
4159
4324
  //#region src/db/migration-provider.ts
4160
4325
  function createMigrationProvider() {
4161
4326
  return { async getMigrations() {
@@ -4193,7 +4358,11 @@ function createMigrationProvider() {
4193
4358
  "031_dispatch_queue_ack_deadline": _031_dispatch_queue_ack_deadline_exports,
4194
4359
  "032_org_settings_dispatch_ack_timeout": _032_org_settings_dispatch_ack_timeout_exports,
4195
4360
  "033_org_settings_approval": _033_org_settings_approval_exports,
4196
- "034_held_runs_generalize": _034_held_runs_generalize_exports
4361
+ "034_held_runs_generalize": _034_held_runs_generalize_exports,
4362
+ "035_pending_workflow_contexts": _035_pending_workflow_contexts_exports,
4363
+ "036_attestations": _036_attestations_exports,
4364
+ "037_generic_sources_provider_type_local": _037_generic_sources_provider_type_local_exports,
4365
+ "038_remote_sources": _038_remote_sources_exports
4197
4366
  };
4198
4367
  } };
4199
4368
  }
@@ -4260,7 +4429,7 @@ async function runMigrations(opts) {
4260
4429
  * as `kici-admin peer create-token` — open a pool from KICI_DATABASE_URL /
4261
4430
  * DATABASE_URL / --database-url.
4262
4431
  */
4263
- function resolveDatabaseUrl$1(explicit) {
4432
+ function resolveDatabaseUrl$2(explicit) {
4264
4433
  const url = explicit ?? process.env.KICI_DATABASE_URL;
4265
4434
  if (!url) throw new Error("Database URL required. Pass --database-url or set KICI_DATABASE_URL.");
4266
4435
  return url;
@@ -4307,7 +4476,7 @@ function registerDbCommands(program, getClient) {
4307
4476
  });
4308
4477
  db.command("fresh").description("DROP + CREATE the orchestrator DB, run migrations, record content hash").option("--database-url <url>", "Target database URL (else KICI_DATABASE_URL / DATABASE_URL)").requiredOption("--confirm", "Explicit confirmation (destructive)").option("--yes", "Skip interactive confirmation (for scripted use)").action(async (opts) => {
4309
4478
  try {
4310
- const url = resolveDatabaseUrl$1(opts.databaseUrl);
4479
+ const url = resolveDatabaseUrl$2(opts.databaseUrl);
4311
4480
  const { dbName } = parseDatabaseUrl(url);
4312
4481
  if (!opts.yes) {
4313
4482
  if (!await confirmInteractive$1(`About to DROP + RECREATE database "${dbName}". Type "${dbName}" to confirm: `, dbName)) {
@@ -4338,7 +4507,7 @@ function registerDbCommands(program, getClient) {
4338
4507
  });
4339
4508
  db.command("ensure <name>").description("CREATE DATABASE IF NOT EXISTS (idempotent)").option("--database-url <url>", "Admin DB URL (else KICI_DATABASE_URL / DATABASE_URL)").option("--owner <role>", "DB owner role (default: URL user). Pass when the admin connection is privileged but the new DB should be owned by a separate non-privileged role.").option("--revoke-connect-public", "After ensure, REVOKE CONNECT ON DATABASE \"<name>\" FROM PUBLIC (recommended on shared clusters).").option("--grant-connect-role <role>", "After ensure (and any --revoke-connect-public), GRANT CONNECT ON DATABASE \"<name>\" TO \"<role>\". Repeatable.", (val, acc) => acc.concat([val]), []).action(async (name, opts) => {
4340
4509
  try {
4341
- const baseUrl = resolveDatabaseUrl$1(opts.databaseUrl);
4510
+ const baseUrl = resolveDatabaseUrl$2(opts.databaseUrl);
4342
4511
  const url = new URL(baseUrl);
4343
4512
  url.pathname = `/${name}`;
4344
4513
  const targetUrl = url.toString();
@@ -4357,7 +4526,7 @@ function registerDbCommands(program, getClient) {
4357
4526
  });
4358
4527
  db.command("create-role").description("CREATE / ALTER ROLE with LOGIN [+ CREATEDB] (idempotent)").option("--database-url <url>", "Admin DB URL (else KICI_DATABASE_URL / DATABASE_URL)").requiredOption("--user <name>", "Role name to create or update").requiredOption("--password <password>", "Role password (raw — quote as needed)").option("--createdb", "Grant CREATEDB to the new role", false).action(async (opts) => {
4359
4528
  try {
4360
- const { adminUrl } = parseDatabaseUrl(resolveDatabaseUrl$1(opts.databaseUrl));
4529
+ const { adminUrl } = parseDatabaseUrl(resolveDatabaseUrl$2(opts.databaseUrl));
4361
4530
  logInvocation(`create-role ${opts.user}`, adminUrl);
4362
4531
  const outcome = await createDbRole(adminUrl, {
4363
4532
  username: opts.user,
@@ -4372,7 +4541,7 @@ function registerDbCommands(program, getClient) {
4372
4541
  });
4373
4542
  db.command("create-readonly-user").description("Create a read-only role with SELECT on all tables + default privileges").option("--database-url <url>", "Target DB URL (must connect as owner)").requiredOption("--user <name>", "Read-only role name").requiredOption("--password <password>", "Role password").action(async (opts) => {
4374
4543
  try {
4375
- const url = resolveDatabaseUrl$1(opts.databaseUrl);
4544
+ const url = resolveDatabaseUrl$2(opts.databaseUrl);
4376
4545
  const { dbName } = parseDatabaseUrl(url);
4377
4546
  logInvocation(`create-readonly-user ${opts.user}`, url);
4378
4547
  const outcome = await createReadOnlyDbUser(url, {
@@ -4387,7 +4556,7 @@ function registerDbCommands(program, getClient) {
4387
4556
  });
4388
4557
  db.command("check-schema").description("Compare bundled migrations vs live schema. Exit 2 on drift.").option("--database-url <url>", "Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)").option("--json", "Emit JSON instead of a human-readable line", false).action(async (opts) => {
4389
4558
  try {
4390
- const pool = createPool(resolveDatabaseUrl$1(opts.databaseUrl));
4559
+ const pool = createPool(resolveDatabaseUrl$2(opts.databaseUrl));
4391
4560
  try {
4392
4561
  const status = await isSchemaCurrent(pool, createMigrationProvider());
4393
4562
  if (opts.json) process.stdout.write(JSON.stringify(status) + "\n");
@@ -4404,7 +4573,7 @@ function registerDbCommands(program, getClient) {
4404
4573
  });
4405
4574
  db.command("collation-check").description("Compare pg_database.datcollversion against the running libc collation version. Exit 2 on drift.").option("--database-url <url>", "Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)").option("--json", "Emit JSON instead of a human-readable line", false).action(async (opts) => {
4406
4575
  try {
4407
- const url = resolveDatabaseUrl$1(opts.databaseUrl);
4576
+ const url = resolveDatabaseUrl$2(opts.databaseUrl);
4408
4577
  const { dbName } = parseDatabaseUrl(url);
4409
4578
  const pool = createPool(url);
4410
4579
  try {
@@ -4434,7 +4603,7 @@ function registerDbCommands(program, getClient) {
4434
4603
  });
4435
4604
  db.command("reindex").description("REINDEX DATABASE CONCURRENTLY <db>. Rebuilds every index under the running libc collation rules. Non-blocking but takes minutes + ~2× temp disk.").option("--database-url <url>", "Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)").requiredOption("--confirm", "Explicit confirmation (destructive — long-running)").requiredOption("--reason <text>", "Reason (recorded in stderr banner)").action(async (opts) => {
4436
4605
  try {
4437
- const url = resolveDatabaseUrl$1(opts.databaseUrl);
4606
+ const url = resolveDatabaseUrl$2(opts.databaseUrl);
4438
4607
  const { dbName } = parseDatabaseUrl(url);
4439
4608
  logInvocation(`reindex ${dbName} (${opts.reason})`, url);
4440
4609
  const pool = createPool(url);
@@ -4451,7 +4620,7 @@ function registerDbCommands(program, getClient) {
4451
4620
  });
4452
4621
  db.command("refresh-collation-version").description("ALTER DATABASE <db> REFRESH COLLATION VERSION. Metadata-only bump; pair with db reindex after a libc-base image rebuild.").option("--database-url <url>", "Target DB URL (else KICI_DATABASE_URL / DATABASE_URL)").requiredOption("--reason <text>", "Reason (recorded in stderr banner)").action(async (opts) => {
4453
4622
  try {
4454
- const url = resolveDatabaseUrl$1(opts.databaseUrl);
4623
+ const url = resolveDatabaseUrl$2(opts.databaseUrl);
4455
4624
  const { dbName } = parseDatabaseUrl(url);
4456
4625
  logInvocation(`refresh-collation-version ${dbName} (${opts.reason})`, url);
4457
4626
  const pool = createPool(url);
@@ -4707,6 +4876,119 @@ promisify(execFile);
4707
4876
  createLogger({ prefix: "universal-git:lock-file" });
4708
4877
  createLogger({ prefix: "universal-git:clone-token" });
4709
4878
  //#endregion
4879
+ //#region src/providers/local/local-source-config.ts
4880
+ /**
4881
+ * Configuration for a local filesystem (`file://`) source, stored as JSONB in
4882
+ * `generic_webhook_sources.git_config` and discriminated from universal-git
4883
+ * config by the row's `provider_type='local'`.
4884
+ *
4885
+ * A local source clones a git repository that already exists on the agent's
4886
+ * filesystem (host path for bare-metal scalers, image-bundled / rootfs /
4887
+ * bind-mounted path for container + Firecracker scalers). The orchestrator does
4888
+ * not verify in-agent reachability — that is the operator's responsibility.
4889
+ */
4890
+ const LocalSourceConfigSchema = z.object({
4891
+ /** Absolute path to the repo (or base dir of repos) on the agent filesystem. */
4892
+ repoBasePath: z.string().min(1).refine((p) => path.isAbsolute(p), { message: "repoBasePath must be an absolute path" }),
4893
+ /** Optional network clone base (git:// / http://) for remote agents that do
4894
+ * not share the orchestrator's filesystem. When unset, file:// is used. */
4895
+ cloneUrlBase: z.string().min(1).optional()
4896
+ }).strict();
4897
+ //#endregion
4898
+ //#region src/cli/commands/local-trigger.ts
4899
+ /**
4900
+ * Build and send the synthetic webhook that triggers a run against a local
4901
+ * filesystem (`file://`) source. The orchestrator's provider-agnostic generic
4902
+ * webhook route (`POST /webhook/:orgId/generic/:sourceId`) dispatches it through
4903
+ * the local provider's normalizer. Shared by the `source trigger-local` CLI
4904
+ * command and the generated `post-receive` hook.
4905
+ *
4906
+ * A local repo has no remote forge to send webhooks, so this is the operator's
4907
+ * way to drive a run: either by hand (`trigger-local`) or on every push via an
4908
+ * installed `post-receive` hook (`install-hook`).
4909
+ */
4910
+ /** Build the GitHub-shaped webhook request the local provider normalizer expects. */
4911
+ function buildLocalTriggerRequest(input) {
4912
+ const body = JSON.stringify({
4913
+ ref: input.ref,
4914
+ after: input.sha,
4915
+ repository: {
4916
+ full_name: input.repoFullName,
4917
+ default_branch: input.defaultBranch
4918
+ }
4919
+ });
4920
+ return {
4921
+ path: `/webhook/${input.orgId}/generic/${input.sourceId}`,
4922
+ headers: {
4923
+ "content-type": "application/json",
4924
+ "x-event-type": input.event,
4925
+ "x-delivery-id": randomUUID()
4926
+ },
4927
+ body
4928
+ };
4929
+ }
4930
+ /** Read HEAD ref + sha from a local git repo (used when the operator omits --ref/--sha). */
4931
+ function readRepoHead(repoPath) {
4932
+ const sha = execFileSync("git", [
4933
+ "-C",
4934
+ repoPath,
4935
+ "rev-parse",
4936
+ "HEAD"
4937
+ ], { encoding: "utf8" }).trim();
4938
+ return {
4939
+ ref: `refs/heads/${execFileSync("git", [
4940
+ "-C",
4941
+ repoPath,
4942
+ "rev-parse",
4943
+ "--abbrev-ref",
4944
+ "HEAD"
4945
+ ], { encoding: "utf8" }).trim()}`,
4946
+ sha
4947
+ };
4948
+ }
4949
+ /** POST the trigger request to the orchestrator base URL. Returns the HTTP status. */
4950
+ async function sendLocalTrigger(baseUrl, req) {
4951
+ return (await fetch(`${baseUrl.replace(/\/$/, "")}${req.path}`, {
4952
+ method: "POST",
4953
+ headers: req.headers,
4954
+ body: req.body
4955
+ })).status;
4956
+ }
4957
+ //#endregion
4958
+ //#region src/cli/commands/local-hook.ts
4959
+ /**
4960
+ * Render + install a git `post-receive` hook that triggers a KiCI run on every
4961
+ * push to a local filesystem (`file://`) source. The hook is a thin wrapper
4962
+ * around `kici-admin source trigger-local`; it carries no logic of its own.
4963
+ */
4964
+ /**
4965
+ * Render the post-receive hook script. For each pushed ref the hook calls
4966
+ * `kici-admin source trigger-local <id> --ref <ref> --sha <new> --base-url <url>`,
4967
+ * so a push to any branch dispatches a run for that ref.
4968
+ */
4969
+ function renderPostReceiveHook(opts) {
4970
+ return `#!/usr/bin/env bash
4971
+ # KiCI local-source post-receive hook (generated by 'kici-admin source install-hook').
4972
+ # Triggers a run for each pushed branch ref.
4973
+ set -euo pipefail
4974
+ while read -r _old _new ref; do
4975
+ kici-admin source trigger-local ${opts.sourceId} --ref "$ref" --sha "$_new" --base-url ${opts.baseUrl}
4976
+ done
4977
+ `;
4978
+ }
4979
+ /**
4980
+ * Write the rendered hook into the repo's `.git/hooks/post-receive` and make it
4981
+ * executable. Throws when the path is not a git repo (no `.git/hooks` dir).
4982
+ */
4983
+ function installPostReceiveHook(repoPath, script) {
4984
+ const hooksDir = path.join(repoPath, ".git", "hooks");
4985
+ if (!existsSync(hooksDir)) throw new Error(`Not a git repo (no .git/hooks): ${repoPath}`);
4986
+ const hookPath = path.join(hooksDir, "post-receive");
4987
+ writeFileSync(hookPath, script, { mode: 493 });
4988
+ chmodSync(hookPath, 493);
4989
+ return hookPath;
4990
+ }
4991
+ //#endregion
4710
4992
  //#region src/cli/commands/source.ts
4711
4993
  /**
4712
4994
  * Source management commands for kici-admin.
@@ -4728,6 +5010,24 @@ createLogger({ prefix: "universal-git:clone-token" });
4728
5010
  * --from-env <VAR_NAME> Read from environment variable
4729
5011
  * --stdin Read from stdin
4730
5012
  */
5013
+ /** Default orchestrator base URL for the webhook trigger route, matching the
5014
+ * kici-admin global `--url` default. */
5015
+ const DEFAULT_ORCH_URL = process.env.KICI_ADMIN_URL ?? "http://localhost:8080";
5016
+ /** Parse a generic source's git_config (string or object) into a LocalSourceConfig.
5017
+ * Returns null when the row is not a valid local source. */
5018
+ function parseLocalConfig(s) {
5019
+ if (s.provider_type !== "local" || !s.git_config) return null;
5020
+ const raw = typeof s.git_config === "string" ? safeParse(s.git_config) : s.git_config;
5021
+ const parsed = LocalSourceConfigSchema.safeParse(raw);
5022
+ return parsed.success ? parsed.data : null;
5023
+ }
5024
+ function safeParse(value) {
5025
+ try {
5026
+ return JSON.parse(value);
5027
+ } catch {
5028
+ return null;
5029
+ }
5030
+ }
4731
5031
  /**
4732
5032
  * Short reason shown in `(unavailable — <reason>)` when no webhook URL could be
4733
5033
  * resolved for an added source.
@@ -4809,7 +5109,11 @@ function formatGenericSource(s) {
4809
5109
  } catch {
4810
5110
  gc = null;
4811
5111
  }
4812
- if (gc) {
5112
+ if (gc && s.provider_type === "local") {
5113
+ console.log(` Local (file://): yes`);
5114
+ if (typeof gc.repoBasePath === "string") console.log(` Repo base path: ${gc.repoBasePath}`);
5115
+ if (typeof gc.cloneUrlBase === "string") console.log(` Clone URL base: ${gc.cloneUrlBase}`);
5116
+ } else if (gc) {
4813
5117
  console.log(` Universal-git: yes`);
4814
5118
  if (typeof gc.preset === "string") console.log(` Preset: ${gc.preset}`);
4815
5119
  if (typeof gc.gitUrlTemplate === "string") console.log(` Git URL template: ${gc.gitUrlTemplate}`);
@@ -4907,7 +5211,7 @@ function registerSourceCommands(program, getClient) {
4907
5211
  process.exit(1);
4908
5212
  }
4909
5213
  });
4910
- add.command("generic").description("Add a new generic webhook source").requiredOption("--org <orgId>", "Organization/customer ID").requiredOption("--name <name>", "Human-readable source name").option("--verification <method>", "Verification method: hmac_sha256, bearer_token, ip_allowlist, none").option("--secret <value>", "Verification secret (HMAC secret or bearer token, prefix with @ for file)").option("--from-env <varName>", "Read verification secret from environment variable").option("--stdin", "Read verification secret from stdin").option("--event-type-header <header>", "Header name for event type extraction").option("--event-type-path <jsonpath>", "JSONPath for event type extraction from body").option("--idempotency-key-header <header>", "Header name for idempotency key").option("--idempotency-key-path <jsonpath>", "JSONPath for idempotency key from body").option("--dedup-window <seconds>", "Dedup window in seconds (default: 300)", parseInt).option("--max-payload <bytes>", "Maximum payload size in bytes (default: 1048576)", parseInt).option("--allowed-events <events>", "Comma-separated list of allowed event types").option("--strip-headers <headers>", "Comma-separated list of headers to strip").option("--rate-limit <rpm>", "Rate limit in requests per minute (default: 600)", parseInt).option("--preset <name>", "Universal-git preset: forgejo, gitea, gogs, gitlab-repo, github-repo, custom").option("--git-url-template <url>", "Clone URL template with {owner}/{name}/{repo}").option("--credential-ref <key>", "Secret key name (under __source__/<id> scope)").option("--credential-store <backend>", "Secret backend name (default: pg)").option("--credential-type <type>", "Credential type: pat, basic, ssh").option("--credential-user <user>", "Username for PAT/basic auth (default: x-access-token)").option("--ssh-host-key-policy <policy>", "SSH host-key policy: accept-new, pinned").option("--ssh-known-hosts-pem <pathOrValue>", "Pinned SSH known_hosts (prefix with @ for file). Required when --ssh-host-key-policy=pinned").option("--provider-type <type>", "Provider implementation: generic (default) or internal (for E2E/staging internal-webhook routing)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (opts) => {
5214
+ add.command("generic").description("Add a new generic webhook source").requiredOption("--org <orgId>", "Organization/customer ID").requiredOption("--name <name>", "Human-readable source name").option("--verification <method>", "Verification method: hmac_sha256, bearer_token, ip_allowlist, none").option("--secret <value>", "Verification secret (HMAC secret or bearer token, prefix with @ for file)").option("--from-env <varName>", "Read verification secret from environment variable").option("--stdin", "Read verification secret from stdin").option("--event-type-header <header>", "Header name for event type extraction").option("--event-type-path <jsonpath>", "JSONPath for event type extraction from body").option("--idempotency-key-header <header>", "Header name for idempotency key").option("--idempotency-key-path <jsonpath>", "JSONPath for idempotency key from body").option("--dedup-window <seconds>", "Dedup window in seconds (default: 300)", parseInt).option("--max-payload <bytes>", "Maximum payload size in bytes (default: 1048576)", parseInt).option("--allowed-events <events>", "Comma-separated list of allowed event types").option("--strip-headers <headers>", "Comma-separated list of headers to strip").option("--rate-limit <rpm>", "Rate limit in requests per minute (default: 600)", parseInt).option("--preset <name>", "Universal-git preset: forgejo, gitea, gogs, gitlab-repo, github-repo, custom").option("--git-url-template <url>", "Clone URL template with {owner}/{name}/{repo}").option("--credential-ref <key>", "Secret key name (under __source__/<id> scope)").option("--credential-store <backend>", "Secret backend name (default: pg)").option("--credential-type <type>", "Credential type: pat, basic, ssh").option("--credential-user <user>", "Username for PAT/basic auth (default: x-access-token)").option("--ssh-host-key-policy <policy>", "SSH host-key policy: accept-new, pinned").option("--ssh-known-hosts-pem <pathOrValue>", "Pinned SSH known_hosts (prefix with @ for file). Required when --ssh-host-key-policy=pinned").option("--provider-type <type>", "Provider implementation: generic (default) or local (a git repo on the agent filesystem cloned via file://)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (opts) => {
4911
5215
  try {
4912
5216
  const secret = await resolveSecret({
4913
5217
  value: opts.secret,
@@ -4959,6 +5263,39 @@ function registerSourceCommands(program, getClient) {
4959
5263
  process.exit(1);
4960
5264
  }
4961
5265
  });
5266
+ add.command("local").description("Register a git repo present on the agent filesystem as a file:// source").requiredOption("--org <orgId>", "Organization/customer ID").requiredOption("--path <dir>", "Absolute path to the repo (or base dir of repos) on the agent filesystem").option("--name <name>", "Human-readable source name", "local").option("--clone-url-base <url>", "Optional git://|http:// base for remote agents that do not share the orchestrator filesystem (default: file://)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (opts) => {
5267
+ try {
5268
+ if (!path.isAbsolute(opts.path)) {
5269
+ console.error(`Error: --path must be an absolute path: ${opts.path}`);
5270
+ process.exit(1);
5271
+ }
5272
+ const localConfig = { repoBasePath: opts.path };
5273
+ if (opts.cloneUrlBase) localConfig.cloneUrlBase = opts.cloneUrlBase;
5274
+ const s = (await getClient().createGenericSource({
5275
+ orgId: opts.org,
5276
+ name: opts.name,
5277
+ providerType: "local",
5278
+ verificationMethod: "none",
5279
+ localConfig
5280
+ })).source;
5281
+ if (opts.json) {
5282
+ console.log(JSON.stringify(s, null, 2));
5283
+ return;
5284
+ }
5285
+ console.log(`Local source created:`);
5286
+ console.log(` ID: ${s.id}`);
5287
+ console.log(` Name: ${s.name}`);
5288
+ console.log(` Routing key: ${s.routing_key}`);
5289
+ console.log(` Org: ${s.customer_id}`);
5290
+ console.log(` Repo path: ${opts.path}`);
5291
+ console.log("");
5292
+ console.log(`Trigger runs with: kici-admin source trigger-local ${s.id}`);
5293
+ console.log(`Or install a push hook: kici-admin source install-hook ${s.id}`);
5294
+ } catch (err) {
5295
+ console.error(`Error: ${toErrorMessage(err)}`);
5296
+ process.exit(1);
5297
+ }
5298
+ });
4962
5299
  src.command("list-presets").description("List built-in universal-git presets (forge shapes supported out of the box)").option("--format <format>", "Output format: table|json", "table").action((opts) => {
4963
5300
  const rows = Object.entries(UNIVERSAL_GIT_PRESETS).map(([name, def]) => ({
4964
5301
  preset: name,
@@ -5070,7 +5407,7 @@ function registerSourceCommands(program, getClient) {
5070
5407
  process.exit(1);
5071
5408
  }
5072
5409
  });
5073
- src.command("update-generic <id>").description("Update a generic webhook source").option("--name <name>", "New name").option("--verification <method>", "Verification method: hmac_sha256, bearer_token, ip_allowlist, none").option("--secret <value>", "New verification secret (prefix with @ for file)").option("--from-env <varName>", "Read verification secret from environment variable").option("--stdin", "Read verification secret from stdin").option("--event-type-header <header>", "Header name for event type extraction").option("--event-type-path <jsonpath>", "JSONPath for event type extraction from body").option("--idempotency-key-header <header>", "Header name for idempotency key").option("--idempotency-key-path <jsonpath>", "JSONPath for idempotency key from body").option("--dedup-window <seconds>", "Dedup window in seconds", parseInt).option("--max-payload <bytes>", "Maximum payload size in bytes", parseInt).option("--allowed-events <events>", "Comma-separated list of allowed event types").option("--strip-headers <headers>", "Comma-separated list of headers to strip").option("--rate-limit <rpm>", "Rate limit in requests per minute", parseInt).option("--preset <name>", "Universal-git preset").option("--git-url-template <url>", "Clone URL template").option("--credential-ref <key>", "Secret key name").option("--credential-store <backend>", "Secret backend name").option("--credential-type <type>", "Credential type: pat, basic, ssh").option("--credential-user <user>", "Username for PAT/basic auth").option("--ssh-host-key-policy <policy>", "SSH host-key policy: accept-new, pinned").option("--ssh-known-hosts-pem <pathOrValue>", "Pinned SSH known_hosts (prefix with @ for file)").option("--clear-git-config", "Revert this source back to a payload-only generic webhook").option("--provider-type <type>", "Provider implementation: generic (default) or internal (for E2E/staging internal-webhook routing)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (id, opts) => {
5410
+ src.command("update-generic <id>").description("Update a generic webhook source").option("--name <name>", "New name").option("--verification <method>", "Verification method: hmac_sha256, bearer_token, ip_allowlist, none").option("--secret <value>", "New verification secret (prefix with @ for file)").option("--from-env <varName>", "Read verification secret from environment variable").option("--stdin", "Read verification secret from stdin").option("--event-type-header <header>", "Header name for event type extraction").option("--event-type-path <jsonpath>", "JSONPath for event type extraction from body").option("--idempotency-key-header <header>", "Header name for idempotency key").option("--idempotency-key-path <jsonpath>", "JSONPath for idempotency key from body").option("--dedup-window <seconds>", "Dedup window in seconds", parseInt).option("--max-payload <bytes>", "Maximum payload size in bytes", parseInt).option("--allowed-events <events>", "Comma-separated list of allowed event types").option("--strip-headers <headers>", "Comma-separated list of headers to strip").option("--rate-limit <rpm>", "Rate limit in requests per minute", parseInt).option("--preset <name>", "Universal-git preset").option("--git-url-template <url>", "Clone URL template").option("--credential-ref <key>", "Secret key name").option("--credential-store <backend>", "Secret backend name").option("--credential-type <type>", "Credential type: pat, basic, ssh").option("--credential-user <user>", "Username for PAT/basic auth").option("--ssh-host-key-policy <policy>", "SSH host-key policy: accept-new, pinned").option("--ssh-known-hosts-pem <pathOrValue>", "Pinned SSH known_hosts (prefix with @ for file)").option("--clear-git-config", "Revert this source back to a payload-only generic webhook").option("--provider-type <type>", "Provider implementation: generic (default) or local (a git repo on the agent filesystem cloned via file://)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (id, opts) => {
5074
5411
  try {
5075
5412
  const secret = await resolveSecret({
5076
5413
  value: opts.secret,
@@ -5117,7 +5454,32 @@ function registerSourceCommands(program, getClient) {
5117
5454
  process.exit(1);
5118
5455
  }
5119
5456
  });
5120
- src.command("remove <routingKey>").description("Remove a source (GitHub or generic with --generic flag)").option("--yes", "Skip confirmation").option("--generic", "Remove a generic source (routingKey is treated as source ID)").option("--hard", "Permanently delete a generic source (requires --generic)").action(async (routingKey, opts) => {
5457
+ src.command("update-local <id>").description("Update a local filesystem (file://) source").option("--name <name>", "New name").option("--path <dir>", "New absolute repo base path on the agent filesystem").option("--clone-url-base <url>", "New git://|http:// clone base (default: file://)").option("--json", "Emit raw JSON (the full source row) instead of formatted text").action(async (id, opts) => {
5458
+ try {
5459
+ const data = {};
5460
+ if (opts.name) data.name = opts.name;
5461
+ if (opts.path !== void 0) {
5462
+ if (!path.isAbsolute(opts.path)) {
5463
+ console.error(`Error: --path must be an absolute path: ${opts.path}`);
5464
+ process.exit(1);
5465
+ }
5466
+ const localConfig = { repoBasePath: opts.path };
5467
+ if (opts.cloneUrlBase) localConfig.cloneUrlBase = opts.cloneUrlBase;
5468
+ data.localConfig = localConfig;
5469
+ }
5470
+ if (Object.keys(data).length === 0) {
5471
+ console.error("Error: no fields to update. Provide --path and/or --name.");
5472
+ process.exit(1);
5473
+ }
5474
+ const result = await getClient().updateGenericSource(id, data);
5475
+ if (opts.json) console.log(JSON.stringify(result.source, null, 2));
5476
+ else console.log(`Local source updated: ${result.source.id} (${result.source.name})`);
5477
+ } catch (err) {
5478
+ console.error(`Error: ${toErrorMessage(err)}`);
5479
+ process.exit(1);
5480
+ }
5481
+ });
5482
+ 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)").action(async (routingKey, opts) => {
5121
5483
  try {
5122
5484
  if (!opts.yes) {
5123
5485
  const rl = createInterface({
@@ -5133,12 +5495,13 @@ function registerSourceCommands(program, getClient) {
5133
5495
  return;
5134
5496
  }
5135
5497
  }
5136
- if (opts.generic) {
5498
+ if (opts.generic || opts.local) {
5137
5499
  const mode = (await getClient().deleteGenericSource(routingKey, opts.hard)).hard ? "permanently deleted" : "soft-deleted";
5138
- console.log(`Generic source ${mode}: ${routingKey}`);
5500
+ const kind = opts.local ? "Local" : "Generic";
5501
+ console.log(`${kind} source ${mode}: ${routingKey}`);
5139
5502
  } else {
5140
5503
  if (opts.hard) {
5141
- console.error("Error: --hard flag is only supported with --generic");
5504
+ console.error("Error: --hard flag is only supported with --generic / --local");
5142
5505
  process.exit(1);
5143
5506
  }
5144
5507
  await getClient().delete(`/api/v1/admin/sources/${encodeURIComponent(routingKey)}`);
@@ -5167,6 +5530,106 @@ function registerSourceCommands(program, getClient) {
5167
5530
  process.exit(1);
5168
5531
  }
5169
5532
  });
5533
+ src.command("trigger-local <id>").description("Trigger a run against a local source (reads HEAD when --ref/--sha omitted)").option("--event <event>", "push | pull_request", "push").option("--ref <ref>", "Git ref (default: repo HEAD branch)").option("--sha <sha>", "Commit SHA (default: repo HEAD)").option("--repo-full-name <name>", "owner/name identifier used in the payload", "local/repo").option("--base-url <url>", "Orchestrator base URL", DEFAULT_ORCH_URL).action(async (id, opts) => {
5534
+ try {
5535
+ const { source } = await getClient().getGenericSource(id);
5536
+ const cfg = parseLocalConfig(source);
5537
+ if (!cfg) {
5538
+ console.error(`Error: no local source with id ${id}`);
5539
+ process.exit(1);
5540
+ }
5541
+ const head = opts.ref && opts.sha ? {
5542
+ ref: opts.ref,
5543
+ sha: opts.sha
5544
+ } : readRepoHead(cfg.repoBasePath);
5545
+ const orgId = source.customer_id;
5546
+ const sourceId = source.name;
5547
+ const req = buildLocalTriggerRequest({
5548
+ orgId,
5549
+ sourceId,
5550
+ repoFullName: opts.repoFullName,
5551
+ event: opts.event === "pull_request" ? "pull_request" : "push",
5552
+ ref: head.ref,
5553
+ sha: head.sha,
5554
+ defaultBranch: head.ref.replace("refs/heads/", "")
5555
+ });
5556
+ const status = await sendLocalTrigger(opts.baseUrl, req);
5557
+ console.log(`Triggered local source ${id}: HTTP ${status}`);
5558
+ if (status >= 400) process.exit(1);
5559
+ } catch (err) {
5560
+ console.error(`Error: ${toErrorMessage(err)}`);
5561
+ process.exit(1);
5562
+ }
5563
+ });
5564
+ src.command("install-hook <id>").description("Install a post-receive hook in the local source repo that triggers runs on push").option("--repo <path>", "Repo path (default: the source repoBasePath)").option("--base-url <url>", "Orchestrator base URL", DEFAULT_ORCH_URL).action(async (id, opts) => {
5565
+ try {
5566
+ const { source } = await getClient().getGenericSource(id);
5567
+ const cfg = parseLocalConfig(source);
5568
+ if (!cfg) {
5569
+ console.error(`Error: no local source with id ${id}`);
5570
+ process.exit(1);
5571
+ }
5572
+ const repoPath = opts.repo ?? cfg.repoBasePath;
5573
+ const hookPath = installPostReceiveHook(repoPath, renderPostReceiveHook({
5574
+ sourceId: id,
5575
+ baseUrl: opts.baseUrl
5576
+ }));
5577
+ console.log(`Installed post-receive hook: ${hookPath}`);
5578
+ console.log(`Every push to ${repoPath} will now trigger a run for source ${id}.`);
5579
+ } catch (err) {
5580
+ console.error(`Error: ${toErrorMessage(err)}`);
5581
+ process.exit(1);
5582
+ }
5583
+ });
5584
+ }
5585
+ /** Read the auto-provisioned remote-source row for an org, if it exists. */
5586
+ async function getRemoteSource(db, orgId) {
5587
+ return db.selectFrom("remote_sources").selectAll().where("customer_id", "=", orgId).executeTakeFirst();
5588
+ }
5589
+ //#endregion
5590
+ //#region src/cli/commands/remote-source.ts
5591
+ init_client();
5592
+ function resolveDatabaseUrl$1(explicit) {
5593
+ const url = explicit ?? process.env.KICI_DATABASE_URL;
5594
+ if (!url) throw new Error("Database URL required. Pass --database-url or set KICI_DATABASE_URL.");
5595
+ return url;
5596
+ }
5597
+ function registerRemoteSourceCommands(program) {
5598
+ program.command("remote-source").description("Inspect the auto-provisioned remote-source org anchor").command("show <orgId>").description("Print the remote_sources anchor row for an org (routing key remote:<orgId>).").option("--database-url <url>", "Orchestrator DB URL (else KICI_DATABASE_URL)").option("--format <format>", "Output format: json|table", "table").action(async (orgId, opts) => {
5599
+ let url;
5600
+ try {
5601
+ url = resolveDatabaseUrl$1(opts.databaseUrl);
5602
+ } catch (err) {
5603
+ console.error(`Error: ${toErrorMessage(err)}`);
5604
+ process.exit(1);
5605
+ return;
5606
+ }
5607
+ const pool = createPool$1(url);
5608
+ const db = createDb$1(pool);
5609
+ try {
5610
+ const row = await getRemoteSource(db, orgId);
5611
+ if (!row) {
5612
+ console.error(`No remote_sources anchor found for org ${orgId}.`);
5613
+ process.exit(1);
5614
+ return;
5615
+ }
5616
+ if (opts.format === "json") {
5617
+ console.log(JSON.stringify(row, null, 2));
5618
+ return;
5619
+ }
5620
+ console.log(`customer_id: ${row.customer_id}`);
5621
+ console.log(`routing_key: ${row.routing_key}`);
5622
+ console.log(`cluster_id: ${row.cluster_id ?? "(none)"}`);
5623
+ console.log(`created_at: ${new Date(row.created_at).toISOString()}`);
5624
+ console.log(`updated_at: ${new Date(row.updated_at).toISOString()}`);
5625
+ } catch (err) {
5626
+ console.error(`Error: ${toErrorMessage(err)}`);
5627
+ process.exit(1);
5628
+ } finally {
5629
+ await db.destroy();
5630
+ await pool.end().catch(() => void 0);
5631
+ }
5632
+ });
5170
5633
  }
5171
5634
  //#endregion
5172
5635
  //#region src/cli/commands/workflow.ts
@@ -7452,61 +7915,61 @@ var init_registry = __esmMin((() => {
7452
7915
  rolldown: {
7453
7916
  "linux-x64": {
7454
7917
  name: "rolldown",
7455
- version: "1.0.0-rc.9",
7918
+ version: "1.1.0",
7456
7919
  platform: "linux",
7457
7920
  arch: "x64",
7458
- url: "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz",
7459
- sha256: "e97ccb470b8310892a5b5cfae640c728396c16317d1e7c8d305c738f63cfc13b",
7921
+ url: "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.0.tgz",
7922
+ sha256: "07ccc910deab806eae00c93cfa633eeedfe5df92cabd0fc92111dd982ab3c324",
7460
7923
  extractPath: "package/",
7461
7924
  archiveType: "tar.gz"
7462
7925
  },
7463
7926
  "linux-arm64": {
7464
7927
  name: "rolldown",
7465
- version: "1.0.0-rc.9",
7928
+ version: "1.1.0",
7466
7929
  platform: "linux",
7467
7930
  arch: "arm64",
7468
- url: "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz",
7469
- sha256: "65b675cd2d065f9cac4acba9cab743269f5f21cfe2d34f18dc4ae5e5a80b01e8",
7931
+ url: "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.0.tgz",
7932
+ sha256: "e7e812642e292101c65703101e29622da59279c53cd68deefed2a356a76ceaf0",
7470
7933
  extractPath: "package/",
7471
7934
  archiveType: "tar.gz"
7472
7935
  },
7473
7936
  "darwin-x64": {
7474
7937
  name: "rolldown",
7475
- version: "1.0.0-rc.9",
7938
+ version: "1.1.0",
7476
7939
  platform: "darwin",
7477
7940
  arch: "x64",
7478
- url: "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz",
7479
- sha256: "a7b282b2c2f809aed6ef8d90fe1412730e8d73de4a67997f3a91d9fca0568dd5",
7941
+ url: "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.0.tgz",
7942
+ sha256: "48f52e0b62d87117f96c7660e2838dded1d6a99f45d56ae192ab49475a8fa9d1",
7480
7943
  extractPath: "package/",
7481
7944
  archiveType: "tar.gz"
7482
7945
  },
7483
7946
  "darwin-arm64": {
7484
7947
  name: "rolldown",
7485
- version: "1.0.0-rc.9",
7948
+ version: "1.1.0",
7486
7949
  platform: "darwin",
7487
7950
  arch: "arm64",
7488
- url: "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz",
7489
- sha256: "19464bb79c19817892722bfd172874d6853fa07c800deac965cc3c3e7e6e0739",
7951
+ url: "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.0.tgz",
7952
+ sha256: "c798f79bbcc28079af101ca323c194f2ad6472da0ae80721cc3eaa370e753f6a",
7490
7953
  extractPath: "package/",
7491
7954
  archiveType: "tar.gz"
7492
7955
  },
7493
7956
  "win32-x64": {
7494
7957
  name: "rolldown",
7495
- version: "1.0.0-rc.9",
7958
+ version: "1.1.0",
7496
7959
  platform: "win32",
7497
7960
  arch: "x64",
7498
- url: "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz",
7499
- sha256: "e359891b2508f9dde392367f70e0e8ad3511a595cf2a6d5ee33115eed8de8e86",
7961
+ url: "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.0.tgz",
7962
+ sha256: "3457d231d8dcce922bf1cb8f696f04600378ce8e88c0898dba9999239d64dd4a",
7500
7963
  extractPath: "package/",
7501
7964
  archiveType: "tar.gz"
7502
7965
  },
7503
7966
  "win32-arm64": {
7504
7967
  name: "rolldown",
7505
- version: "1.0.0-rc.9",
7968
+ version: "1.1.0",
7506
7969
  platform: "win32",
7507
7970
  arch: "arm64",
7508
- url: "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz",
7509
- sha256: "e5473c970cdc559a392c8d4ffe10ef758712e7fd19e60f67bc1cb189b5f3260a",
7971
+ url: "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.0.tgz",
7972
+ sha256: "c8e9e9643f2e905ca0062d2e768c2966e48f0a47595c8c2b7658a085024c8ce3",
7510
7973
  extractPath: "package/",
7511
7974
  archiveType: "tar.gz"
7512
7975
  }
@@ -8243,6 +8706,21 @@ function updateSymlinkAtomic(installBase, component, version) {
8243
8706
  * 5. Update symlink (Unix) or service registration (Windows)
8244
8707
  * 6. Start service
8245
8708
  */
8709
+ /**
8710
+ * Resolve the target version for an npm-source upgrade (no --from/--url).
8711
+ *
8712
+ * The npm-source flow assumes the operator already ran
8713
+ * `npm install -g @kici-dev/<pkg>@<version>`, which overwrites the global
8714
+ * package in place. The running `kici-admin` binary is therefore the new
8715
+ * version, and `running` is its self-reported version. We default the target
8716
+ * to it, or validate an explicitly-passed --version matches — a mismatch means
8717
+ * the npm install did not actually update the global binary.
8718
+ */
8719
+ function resolveNpmSourceVersion(opts) {
8720
+ if (opts.running === "unknown" || opts.running.trim() === "") throw new Error("npm-source upgrade: could not determine the running package version. Pass --from <archive> / --url <url> + --version for an archive-based upgrade instead.");
8721
+ if (opts.requested && opts.requested !== opts.running) throw new Error(`npm-source upgrade: requested --version ${opts.requested} does not match the installed package version ${opts.running}. Run \`npm install -g @kici-dev/<pkg>@${opts.requested}\` first, then re-run the upgrade.`);
8722
+ return opts.running;
8723
+ }
8246
8724
  async function performVersionedUpgrade(component, opts) {
8247
8725
  try {
8248
8726
  const platform = detectPlatform(opts.platform);
@@ -8272,8 +8750,8 @@ async function performVersionedUpgrade(component, opts) {
8272
8750
  return;
8273
8751
  }
8274
8752
  if (!opts.from && !opts.url) {
8275
- console.error("Error: provide --from <archive-path> or --url <url> for the upgrade package");
8276
- process.exit(1);
8753
+ await performNpmSourceUpgrade(config, resolvedInstance, manager, opts);
8754
+ return;
8277
8755
  }
8278
8756
  if (!opts.version) {
8279
8757
  console.error("Error: --version is required to specify the target version");
@@ -8375,6 +8853,41 @@ async function performVersionedUpgrade(component, opts) {
8375
8853
  }
8376
8854
  }
8377
8855
  /**
8856
+ * npm-source upgrade (no --from/--url): the operator has already run
8857
+ * `npm install -g @kici-dev/<pkg>@<version>`, replacing the global package
8858
+ * in place. The systemd/launchd/Windows unit's ExecStart points at that global
8859
+ * package (see install.ts → import.meta.resolve), so a stop+start re-executes
8860
+ * the freshly-installed code. No archive, no versioned dir, no symlink.
8861
+ */
8862
+ async function performNpmSourceUpgrade(config, resolvedInstance, manager, opts) {
8863
+ const running = readKiciVersion();
8864
+ const version = resolveNpmSourceVersion({
8865
+ requested: opts.version,
8866
+ running
8867
+ });
8868
+ if (!await manager.isInstalled(config)) {
8869
+ console.error(`Error: service "${config.name}" is not installed`);
8870
+ process.exit(1);
8871
+ }
8872
+ if (!opts.yes) {
8873
+ console.log(`This will restart "${config.name}" onto the npm-installed version ${version}.`);
8874
+ console.log(" The service will be stopped briefly during the restart.");
8875
+ console.log("");
8876
+ if (!await confirm$2("Proceed with upgrade?")) {
8877
+ console.log("Upgrade cancelled.");
8878
+ return;
8879
+ }
8880
+ }
8881
+ console.log("Restarting service onto the npm-installed package...");
8882
+ if ((await manager.status(config)).state === "running") await manager.stop(config);
8883
+ await manager.start(config);
8884
+ writeManifest(resolvedInstance.instanceDir, {
8885
+ ...resolvedInstance.manifest,
8886
+ kiciVersion: version
8887
+ });
8888
+ console.log(`Upgrade complete: service "${config.name}" is now running version ${version}.`);
8889
+ }
8890
+ /**
8378
8891
  * Handle --rollback: switch symlink to the previous version and restart.
8379
8892
  */
8380
8893
  async function handleRollback(component, platform, installBase, config, manager, opts) {
@@ -9730,6 +10243,7 @@ const TOKEN_PREFIX = "kici_join_v1";
9730
10243
  const DEFAULT_EXPIRY_MS = 36e5;
9731
10244
  const TOKEN_ALREADY_USED_MESSAGE = "Join token has already been used. Generate a new token with: kici admin create-join-token";
9732
10245
  var JoinTokenManager = class {
10246
+ deps;
9733
10247
  constructor(deps) {
9734
10248
  this.deps = deps;
9735
10249
  }
@@ -9859,6 +10373,7 @@ const DEFAULT_EXPIRY_DAYS = 90;
9859
10373
  * Used by the coordinator to manage credentials for all cluster members.
9860
10374
  */
9861
10375
  var PeerCredentialStore = class {
10376
+ db;
9862
10377
  constructor(db) {
9863
10378
  this.db = db;
9864
10379
  }
@@ -11448,6 +11963,8 @@ var init_execution_steps = __esmMin((() => {
11448
11963
  enabled: true
11449
11964
  };
11450
11965
  ExecutionStepsAdapter = class {
11966
+ kdb;
11967
+ instanceId;
11451
11968
  db = "orchestrator";
11452
11969
  table = "execution_steps";
11453
11970
  tenantColumn = "routing_key";
@@ -11603,6 +12120,8 @@ var init_execution_jobs = __esmMin((() => {
11603
12120
  enabled: true
11604
12121
  };
11605
12122
  ExecutionJobsAdapter = class {
12123
+ kdb;
12124
+ instanceId;
11606
12125
  db = "orchestrator";
11607
12126
  table = "execution_jobs";
11608
12127
  tenantColumn = "routing_key";
@@ -11771,6 +12290,8 @@ var init_execution_runs = __esmMin((() => {
11771
12290
  enabled: true
11772
12291
  };
11773
12292
  ExecutionRunsAdapter = class {
12293
+ kdb;
12294
+ instanceId;
11774
12295
  db = "orchestrator";
11775
12296
  table = "execution_runs";
11776
12297
  tenantColumn = "routing_key";
@@ -12002,6 +12523,8 @@ var init_secret_audit_log = __esmMin((() => {
12002
12523
  enabled: true
12003
12524
  };
12004
12525
  SecretAuditLogAdapter = class {
12526
+ kdb;
12527
+ instanceId;
12005
12528
  db = "orchestrator";
12006
12529
  table = "secret_audit_log";
12007
12530
  tenantColumn = "routing_key";
@@ -12255,6 +12778,8 @@ var init_access_log = __esmMin((() => {
12255
12778
  enabled: true
12256
12779
  };
12257
12780
  AccessLogAdapter = class {
12781
+ kdb;
12782
+ instanceId;
12258
12783
  db = "orchestrator";
12259
12784
  table = "access_log";
12260
12785
  tenantColumn = "org_id";
@@ -12510,6 +13035,8 @@ var init_event_log = __esmMin((() => {
12510
13035
  enabled: true
12511
13036
  };
12512
13037
  EventLogAdapter = class {
13038
+ kdb;
13039
+ instanceId;
12513
13040
  db = "orchestrator";
12514
13041
  table = "event_log";
12515
13042
  tenantColumn = "routing_key";
@@ -14590,6 +15117,7 @@ var FirecrackerApiError = class extends Error {
14590
15117
  * the `socketPath` option for HTTP-over-Unix-socket communication.
14591
15118
  */
14592
15119
  var FirecrackerApi = class {
15120
+ socketPath;
14593
15121
  constructor(socketPath) {
14594
15122
  this.socketPath = socketPath;
14595
15123
  }
@@ -16577,6 +17105,7 @@ async function writeConfigFile(path, config) {
16577
17105
  await writeFile(path, stringify(config), "utf-8");
16578
17106
  }
16579
17107
  var JoinClient = class {
17108
+ options;
16580
17109
  constructor(options) {
16581
17110
  this.options = options;
16582
17111
  if (!options.platformUrl && !options.peerUrl) throw new Error("Either --platform or --peer must be specified");
@@ -16748,6 +17277,7 @@ function buildProgram() {
16748
17277
  registerConfigCommands(program, getClient);
16749
17278
  registerDbCommands(program, getClient);
16750
17279
  registerSourceCommands(program, getClient);
17280
+ registerRemoteSourceCommands(program);
16751
17281
  registerWorkflowCommands(program, getClient);
16752
17282
  registerRunsCommands(program, getClient);
16753
17283
  registerEventLogCommands(program, getClient);
@@ -16779,8 +17309,27 @@ function buildProgram() {
16779
17309
  function runCli(argv = process.argv) {
16780
17310
  buildProgram().parse(argv);
16781
17311
  }
16782
- if (resolve(process.argv[1] ?? "") === fileURLToPath(import.meta.url)) runCli();
17312
+ /**
17313
+ * Canonicalize a path through any symlinks, falling back to a plain `resolve`
17314
+ * when the target does not exist on disk. The entry-point guard compares the
17315
+ * invoked script path against this module's own path; both sides must be fully
17316
+ * canonicalized or a symlinked invocation path silently fails the match. The
17317
+ * canonical example is the light-package launcher running
17318
+ * `node /tmp/.../kici-admin.cjs` on macOS, where `/tmp` is a symlink to
17319
+ * `/private/tmp`: `process.argv[1]` keeps the `/tmp` form while the bundle's
17320
+ * `import.meta.url` resolves to the real `/private/tmp` path, so an
17321
+ * un-canonicalized comparison is always false and the CLI exits 0 without
17322
+ * running any command.
17323
+ */
17324
+ function canonicalize(p) {
17325
+ try {
17326
+ return realpathSync(resolve(p));
17327
+ } catch {
17328
+ return resolve(p);
17329
+ }
17330
+ }
17331
+ if (canonicalize(process.argv[1] ?? "") === canonicalize(fileURLToPath(import.meta.url))) runCli();
16783
17332
  //#endregion
16784
- export { buildProgram, runCli };
17333
+ export { buildProgram, canonicalize, runCli };
16785
17334
 
16786
17335
  //# sourceMappingURL=cli.js.map