@kici-dev/orchestrator 0.1.24 → 0.1.26

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 (61) hide show
  1. package/dist/app.d.ts +24 -0
  2. package/dist/audit/access-log.d.ts +4 -0
  3. package/dist/cancel/cancel-run.d.ts +2 -0
  4. package/dist/cli/api-client.d.ts +2 -0
  5. package/dist/cli/commands/attestations-list.d.ts +49 -0
  6. package/dist/cli/commands/attestations-retry.d.ts +28 -0
  7. package/dist/cli/commands/attestations.d.ts +2 -1
  8. package/dist/cli/commands/cluster.d.ts +3 -0
  9. package/dist/cli/local-github-ingress-url.d.ts +8 -0
  10. package/dist/cli.js +1021 -404
  11. package/dist/cluster/cluster-identity.d.ts +11 -0
  12. package/dist/cluster/reconcile-identity.d.ts +50 -0
  13. package/dist/cold-store/load-access-log-range.d.ts +4 -0
  14. package/dist/config.d.ts +2 -0
  15. package/dist/dashboard/attestation-filters.d.ts +62 -1
  16. package/dist/dashboard/handler.d.ts +24 -4
  17. package/dist/db/migrations/061_execution_runs_environment_id.d.ts +4 -0
  18. package/dist/db/migrations/062_execution_runs_agent_label.d.ts +4 -0
  19. package/dist/db/migrations/063_access_log_agent_label_index.d.ts +10 -0
  20. package/dist/db/migrations/064_execution_jobs_skipped_environments.d.ts +17 -0
  21. package/dist/db/migrations/065_pending_attestations.d.ts +22 -0
  22. package/dist/db/migrations/066_pending_attestations_rejected.d.ts +13 -0
  23. package/dist/db/types.d.ts +58 -0
  24. package/dist/environments/protection/satisfiability.d.ts +14 -9
  25. package/dist/events/circuit-breaker.d.ts +8 -5
  26. package/dist/events/event-router.d.ts +12 -4
  27. package/dist/events/event-store.d.ts +21 -3
  28. package/dist/events/trust-store.d.ts +6 -1
  29. package/dist/events/types.d.ts +6 -1
  30. package/dist/helpers/rate-limiter.d.ts +17 -2
  31. package/dist/index.js +2 -1
  32. package/dist/metrics/prometheus.d.ts +6 -0
  33. package/dist/pipeline/dispatch-matched-workflow.d.ts +6 -0
  34. package/dist/pipeline/manual-schedule.d.ts +1 -1
  35. package/dist/pipeline/process-webhook.d.ts +14 -1
  36. package/dist/pipeline/rerun.d.ts +1 -1
  37. package/dist/pipeline/test-pipeline.d.ts +6 -0
  38. package/dist/provenance/attestation-retrier.d.ts +92 -0
  39. package/dist/provenance/backfill-run.d.ts +41 -0
  40. package/dist/provenance/pending-attestations-repo.d.ts +64 -0
  41. package/dist/provenance/verify-at-ingest.d.ts +10 -3
  42. package/dist/reporting/execution-tracker.d.ts +15 -3
  43. package/dist/reporting/run-aggregator.d.ts +5 -1
  44. package/dist/routes/admin-access-log.d.ts +2 -1
  45. package/dist/routes/admin.d.ts +14 -0
  46. package/dist/routes/github-webhook.d.ts +37 -0
  47. package/dist/routes/health.d.ts +9 -0
  48. package/dist/routes/webhooks.d.ts +2 -4
  49. package/dist/server.js +3462 -1886
  50. package/dist/sources/source-store.d.ts +6 -0
  51. package/dist/stale-detector/workflow-deadline-detector.d.ts +25 -0
  52. package/dist/standalone.js +19335 -18258
  53. package/dist/webhook/dedup.d.ts +14 -0
  54. package/dist/ws/agent-handler.d.ts +23 -0
  55. package/dist/ws/oidc-token-relay.d.ts +31 -21
  56. package/dist/ws/orch-rpc.d.ts +9 -0
  57. package/dist/ws/platform-client.d.ts +18 -1
  58. package/dist/ws/test-relay-handlers.d.ts +2 -0
  59. package/installer-image-digests.json +3 -3
  60. package/package.json +5 -5
  61. package/sbom.spdx.json +55 -50
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import path, { dirname, join, resolve } from "node:path";
6
6
  import * as fs$1 from "node:fs";
7
7
  import fs, { chmodSync, closeSync, createReadStream, createWriteStream, existsSync, mkdtempSync, openSync, promises, readFileSync, realpathSync, statSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
8
8
  import { Command } from "commander";
9
- import { BaseColdStore, ChunkLru, addLogsToArchive, chunkObjectKey, clearDispatchQueueDirect, computeChunkId, computeMigrationsHash, createDb, createDbRole, createEnvironmentTemplateDirect, createLogger, createPool, createReadOnlyDbUser, createS3Client, 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";
9
+ import { BaseColdStore, ChunkLru, addLogsToArchive, chunkObjectKey, clearDispatchQueueDirect, computeChunkId, computeMigrationsHash, createDb, createDbRole, createEnvironmentTemplateDirect, createLogger, createPool, createReadOnlyDbUser, createS3Client, decodeChunk, deleteEnvironmentDirect, dropAndCreateDatabase, emitKiciEventDirect, encodeKeySegment, ensureDatabase, formatBytes, formatUptime, isSchemaCurrent, listEnvironmentsDirect, listExecutionRunsDirect, listQueueDirect, listRegistrationsDirect, maskDatabaseUrl, parseDatabaseUrl, parseManifest, prunePeerCredentialsDirect, purgeEnvironmentsDirect, 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, createHmac, hkdfSync, randomBytes, randomUUID } from "node:crypto";
11
11
  import { createInterface } from "node:readline";
12
12
  import { KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, PRIVILEGED_ROOT_LABEL, ScalerBackendType, ScalerEventType, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WS_MAX_PAYLOAD_BYTES, accessLogWarmSqlCase, attestationVerifyStatusSchema, getAccessLogColdDays, getSecretAuditLogColdDays, matcherSatisfiedBy, minAccessLogWarmDays, minSecretAuditLogWarmDays, parseHostPropertyAssignments, scalerAgentLabels, secretAuditLogWarmSqlCase } from "@kici-dev/engine";
@@ -28,6 +28,7 @@ import { CopyObjectCommand, DeleteObjectCommand, GetObjectCommand, HeadObjectCom
28
28
  import { Upload } from "@aws-sdk/lib-storage";
29
29
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
30
30
  import { verifyKiciBundle } from "@kici-dev/engine/provenance/verify";
31
+ import { decodeProtectedHeader } from "jose";
31
32
  import { ZipArchive } from "archiver";
32
33
  import { confirm, input, password, select } from "@inquirer/prompts";
33
34
  import JSZip from "jszip";
@@ -35,10 +36,18 @@ import { pipeline } from "node:stream/promises";
35
36
  import "pg";
36
37
  import { DASHBOARD_WRITE_OPERATIONS, DASHBOARD_WRITE_OPERATIONS_BY_NAME, DashboardWriteCategory, DashboardWriteSensitivity } from "@kici-dev/engine/protocol/dashboard-write-operations";
37
38
  import { CLUSTER_NAME_FORMAT_MESSAGE } from "@kici-dev/engine/protocol/cluster-name";
39
+ import { runIdempotentStep } from "@kici-dev/shared/idempotency";
38
40
  import { PassThrough } from "node:stream";
39
41
  import Docker from "dockerode";
40
42
  var __defProp = Object.defineProperty;
41
- var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
43
+ var __esmMin = (fn, res, err) => () => {
44
+ if (err) throw err[0];
45
+ try {
46
+ return fn && (res = fn(fn = 0)), res;
47
+ } catch (e) {
48
+ throw err = [e], e;
49
+ }
50
+ };
42
51
  var __exportAll = (all, no_symbols) => {
43
52
  let target = {};
44
53
  for (var name in all) __defProp(target, name, {
@@ -48,7 +57,7 @@ var __exportAll = (all, no_symbols) => {
48
57
  if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
49
58
  return target;
50
59
  };
51
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
60
+ var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
52
61
  //#endregion
53
62
  //#region src/cli/api-client.ts
54
63
  /**
@@ -438,6 +447,8 @@ var AdminApiClient = class {
438
447
  if (opts?.from) params.set("from", opts.from);
439
448
  if (opts?.to) params.set("to", opts.to);
440
449
  if (opts?.q) params.set("q", opts.q);
450
+ if (opts?.agentLabel) params.set("agentLabel", opts.agentLabel);
451
+ if (opts?.agentOnly) params.set("agentOnly", "true");
441
452
  if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
442
453
  if (opts?.cursor) params.set("cursor", opts.cursor);
443
454
  const qs = params.toString();
@@ -817,7 +828,7 @@ function registerAgentCommands(program, getClient) {
817
828
  if (opts.privilegedRoot) mandatorySet.add(PRIVILEGED_ROOT_LABEL);
818
829
  const mandatoryLabels = mandatorySet.size > 0 ? [...mandatorySet] : void 0;
819
830
  const baseLabels = opts.labels ? opts.labels.split(",").map((l) => l.trim()) : [];
820
- const labelSet = new Set([...baseLabels, ...mandatoryLabels ?? []]);
831
+ const labelSet = /* @__PURE__ */ new Set([...baseLabels, ...mandatoryLabels ?? []]);
821
832
  const labels = labelSet.size > 0 ? [...labelSet] : void 0;
822
833
  const result = await getClient().createAgentToken({
823
834
  labels,
@@ -1579,8 +1590,8 @@ var init_client = __esmMin((() => {
1579
1590
  //#region src/db/migrations/001_initial.ts
1580
1591
  init_client();
1581
1592
  var _001_initial_exports = /* @__PURE__ */ __exportAll({
1582
- down: () => down$59,
1583
- up: () => up$59
1593
+ down: () => down$65,
1594
+ up: () => up$65
1584
1595
  });
1585
1596
  /**
1586
1597
  * Squashed initial migration -- creates the complete Orchestrator database schema.
@@ -2272,7 +2283,7 @@ const DDL_STATEMENTS = [
2272
2283
  `ALTER TABLE ONLY public.held_runs
2273
2284
  ADD CONSTRAINT held_runs_environment_id_fkey FOREIGN KEY (environment_id) REFERENCES public.environments(id);`
2274
2285
  ];
2275
- async function up$59(db) {
2286
+ async function up$65(db) {
2276
2287
  for (const stmt of DDL_STATEMENTS) await sql.raw(stmt).execute(db);
2277
2288
  await sql`
2278
2289
  INSERT INTO cluster_meta (key, value)
@@ -2294,7 +2305,7 @@ async function up$59(db) {
2294
2305
  * Rollback drops everything created above. Uses CASCADE on table drops to cut
2295
2306
  * through the FK graph without relying on exact topological order.
2296
2307
  */
2297
- async function down$59(db) {
2308
+ async function down$65(db) {
2298
2309
  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);
2299
2310
  for (const table of [
2300
2311
  "workflow_registrations",
@@ -2341,8 +2352,8 @@ async function down$59(db) {
2341
2352
  //#endregion
2342
2353
  //#region src/db/migrations/002_config_versions_key_version.ts
2343
2354
  var _002_config_versions_key_version_exports = /* @__PURE__ */ __exportAll({
2344
- down: () => down$58,
2345
- up: () => up$58
2355
+ down: () => down$64,
2356
+ up: () => up$64
2346
2357
  });
2347
2358
  /**
2348
2359
  * Add key_version column to config_versions so that sensitive-field encryption
@@ -2356,13 +2367,13 @@ var _002_config_versions_key_version_exports = /* @__PURE__ */ __exportAll({
2356
2367
  * No index is needed: rotation does a full-table scan; reads are by
2357
2368
  * `version` primary key and never filter on `key_version`.
2358
2369
  */
2359
- async function up$58(db) {
2370
+ async function up$64(db) {
2360
2371
  await sql`
2361
2372
  ALTER TABLE public.config_versions
2362
2373
  ADD COLUMN key_version integer NOT NULL DEFAULT 1
2363
2374
  `.execute(db);
2364
2375
  }
2365
- async function down$58(db) {
2376
+ async function down$64(db) {
2366
2377
  await sql`
2367
2378
  ALTER TABLE public.config_versions
2368
2379
  DROP COLUMN key_version
@@ -2371,8 +2382,8 @@ async function down$58(db) {
2371
2382
  //#endregion
2372
2383
  //#region src/db/migrations/003_access_log.ts
2373
2384
  var _003_access_log_exports = /* @__PURE__ */ __exportAll({
2374
- down: () => down$57,
2375
- up: () => up$57
2385
+ down: () => down$63,
2386
+ up: () => up$63
2376
2387
  });
2377
2388
  /**
2378
2389
  * Access log: one row per read or orchestrator-admin mutation attributable
@@ -2396,7 +2407,7 @@ var _003_access_log_exports = /* @__PURE__ */ __exportAll({
2396
2407
  * Retention is TTL-based via expires_at; packages/orchestrator/src/queue/
2397
2408
  * cleanup.ts picks up the prune pass.
2398
2409
  */
2399
- async function up$57(db) {
2410
+ async function up$63(db) {
2400
2411
  await sql`
2401
2412
  CREATE TABLE public.access_log (
2402
2413
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -2434,28 +2445,28 @@ async function up$57(db) {
2434
2445
  ON public.access_log (actor_type, actor_id, created_at DESC)
2435
2446
  `.execute(db);
2436
2447
  }
2437
- async function down$57(db) {
2448
+ async function down$63(db) {
2438
2449
  await sql`DROP TABLE IF EXISTS public.access_log`.execute(db);
2439
2450
  }
2440
2451
  //#endregion
2441
2452
  //#region src/db/migrations/004_rename_bundle_to_source.ts
2442
2453
  var _004_rename_bundle_to_source_exports = /* @__PURE__ */ __exportAll({
2443
- down: () => down$56,
2444
- up: () => up$56
2454
+ down: () => down$62,
2455
+ up: () => up$62
2445
2456
  });
2446
- async function up$56(db) {
2457
+ async function up$62(db) {
2447
2458
  await db.schema.alterTable("dispatch_queue").renameColumn("bundle_url", "source_tar_url").execute();
2448
2459
  await db.schema.alterTable("dispatch_queue").renameColumn("bundle_hash", "source_tar_hash").execute();
2449
2460
  }
2450
- async function down$56(db) {
2461
+ async function down$62(db) {
2451
2462
  await db.schema.alterTable("dispatch_queue").renameColumn("source_tar_url", "bundle_url").execute();
2452
2463
  await db.schema.alterTable("dispatch_queue").renameColumn("source_tar_hash", "bundle_hash").execute();
2453
2464
  }
2454
2465
  //#endregion
2455
2466
  //#region src/db/migrations/005_cold_store_chunk_counter.ts
2456
2467
  var _005_cold_store_chunk_counter_exports = /* @__PURE__ */ __exportAll({
2457
- down: () => down$55,
2458
- up: () => up$55
2468
+ down: () => down$61,
2469
+ up: () => up$61
2459
2470
  });
2460
2471
  /**
2461
2472
  * Cold-store chunk counter table.
@@ -2472,7 +2483,7 @@ var _005_cold_store_chunk_counter_exports = /* @__PURE__ */ __exportAll({
2472
2483
  *
2473
2484
  * sections 5 and 8.
2474
2485
  */
2475
- async function up$55(db) {
2486
+ async function up$61(db) {
2476
2487
  await sql`
2477
2488
  CREATE TABLE public.cold_store_chunk_counts (
2478
2489
  db TEXT NOT NULL,
@@ -2490,14 +2501,14 @@ async function up$55(db) {
2490
2501
  ON public.cold_store_chunk_counts (db, table_name)
2491
2502
  `.execute(db);
2492
2503
  }
2493
- async function down$55(db) {
2504
+ async function down$61(db) {
2494
2505
  await sql`DROP TABLE IF EXISTS public.cold_store_chunk_counts`.execute(db);
2495
2506
  }
2496
2507
  //#endregion
2497
2508
  //#region src/db/migrations/006_runs_jobs_steps_archived_at.ts
2498
2509
  var _006_runs_jobs_steps_archived_at_exports = /* @__PURE__ */ __exportAll({
2499
- down: () => down$54,
2500
- up: () => up$54
2510
+ down: () => down$60,
2511
+ up: () => up$60
2501
2512
  });
2502
2513
  /**
2503
2514
  * `execution_runs` / `execution_jobs` / `execution_steps` cold-store
@@ -2533,7 +2544,7 @@ var _006_runs_jobs_steps_archived_at_exports = /* @__PURE__ */ __exportAll({
2533
2544
  * - `idx_execution_jobs_routing_key_created (routing_key, created_at)`
2534
2545
  * - `idx_execution_steps_routing_key_created (routing_key, created_at)`
2535
2546
  */
2536
- async function up$54(db) {
2547
+ async function up$60(db) {
2537
2548
  await sql`
2538
2549
  ALTER TABLE public.execution_runs
2539
2550
  ADD COLUMN archived_at TIMESTAMPTZ NULL,
@@ -2578,7 +2589,7 @@ async function up$54(db) {
2578
2589
  ON public.execution_steps (routing_key, created_at)
2579
2590
  `.execute(db);
2580
2591
  }
2581
- async function down$54(db) {
2592
+ async function down$60(db) {
2582
2593
  await sql`DROP INDEX IF EXISTS public.idx_execution_steps_routing_key_created`.execute(db);
2583
2594
  await sql`
2584
2595
  ALTER TABLE public.execution_steps
@@ -2603,8 +2614,8 @@ async function down$54(db) {
2603
2614
  //#endregion
2604
2615
  //#region src/db/migrations/007_audit_logs_archived_at.ts
2605
2616
  var _007_audit_logs_archived_at_exports = /* @__PURE__ */ __exportAll({
2606
- down: () => down$53,
2607
- up: () => up$53
2617
+ down: () => down$59,
2618
+ up: () => up$59
2608
2619
  });
2609
2620
  /**
2610
2621
  * `secret_audit_log` and `access_log` cold-store schema additions, plus
@@ -2644,7 +2655,7 @@ var _007_audit_logs_archived_at_exports = /* @__PURE__ */ __exportAll({
2644
2655
  * `down()` would not have meaningful retention bounds. Acceptable for
2645
2656
  * staging.
2646
2657
  */
2647
- async function up$53(db) {
2658
+ async function up$59(db) {
2648
2659
  await sql`
2649
2660
  ALTER TABLE public.secret_audit_log
2650
2661
  ADD COLUMN archived_at TIMESTAMPTZ NULL,
@@ -2665,7 +2676,7 @@ async function up$53(db) {
2665
2676
  DROP COLUMN IF EXISTS expires_at
2666
2677
  `.execute(db);
2667
2678
  }
2668
- async function down$53(db) {
2679
+ async function down$59(db) {
2669
2680
  await sql`
2670
2681
  ALTER TABLE public.access_log
2671
2682
  ADD COLUMN expires_at TIMESTAMPTZ NOT NULL DEFAULT (now() + INTERVAL '90 days')
@@ -2693,8 +2704,8 @@ async function down$53(db) {
2693
2704
  //#endregion
2694
2705
  //#region src/db/migrations/008_event_log_archived_at.ts
2695
2706
  var _008_event_log_archived_at_exports = /* @__PURE__ */ __exportAll({
2696
- down: () => down$52,
2697
- up: () => up$52
2707
+ down: () => down$58,
2708
+ up: () => up$58
2698
2709
  });
2699
2710
  /**
2700
2711
  * `event_log` cold-store schema additions plus removal of the
@@ -2732,7 +2743,7 @@ var _008_event_log_archived_at_exports = /* @__PURE__ */ __exportAll({
2732
2743
  * — best effort; rows inserted between `up()` and a hypothetical
2733
2744
  * `down()` would not have meaningful retention bounds.
2734
2745
  */
2735
- async function up$52(db) {
2746
+ async function up$58(db) {
2736
2747
  await sql`
2737
2748
  ALTER TABLE public.event_log
2738
2749
  ADD COLUMN archived_at TIMESTAMPTZ NULL,
@@ -2748,7 +2759,7 @@ async function up$52(db) {
2748
2759
  DROP COLUMN IF EXISTS expires_at
2749
2760
  `.execute(db);
2750
2761
  }
2751
- async function down$52(db) {
2762
+ async function down$58(db) {
2752
2763
  await sql`
2753
2764
  ALTER TABLE public.event_log
2754
2765
  ADD COLUMN expires_at TIMESTAMPTZ NOT NULL DEFAULT (now() + INTERVAL '30 days')
@@ -2767,8 +2778,8 @@ async function down$52(db) {
2767
2778
  //#endregion
2768
2779
  //#region src/db/migrations/009_access_log_trigram.ts
2769
2780
  var _009_access_log_trigram_exports = /* @__PURE__ */ __exportAll({
2770
- down: () => down$51,
2771
- up: () => up$51
2781
+ down: () => down$57,
2782
+ up: () => up$57
2772
2783
  });
2773
2784
  /**
2774
2785
  * Trigram (pg_trgm) index on access_log.error_message for the federated
@@ -2781,7 +2792,7 @@ var _009_access_log_trigram_exports = /* @__PURE__ */ __exportAll({
2781
2792
  * EXISTS` are both safe to re-run. No CONCURRENTLY because Kysely runs
2782
2793
  * migrations inside a transaction; the lock is brief on a sampled table.
2783
2794
  */
2784
- async function up$51(db) {
2795
+ async function up$57(db) {
2785
2796
  await sql`CREATE EXTENSION IF NOT EXISTS pg_trgm`.execute(db);
2786
2797
  await sql`
2787
2798
  CREATE INDEX IF NOT EXISTS access_log_error_message_trgm_idx
@@ -2790,14 +2801,14 @@ async function up$51(db) {
2790
2801
  WHERE error_message IS NOT NULL
2791
2802
  `.execute(db);
2792
2803
  }
2793
- async function down$51(db) {
2804
+ async function down$57(db) {
2794
2805
  await sql`DROP INDEX IF EXISTS public.access_log_error_message_trgm_idx`.execute(db);
2795
2806
  }
2796
2807
  //#endregion
2797
2808
  //#region src/db/migrations/010_cold_store_chunks.ts
2798
2809
  var _010_cold_store_chunks_exports = /* @__PURE__ */ __exportAll({
2799
- down: () => down$50,
2800
- up: () => up$50
2810
+ down: () => down$56,
2811
+ up: () => up$56
2801
2812
  });
2802
2813
  /**
2803
2814
  * Cold-store chunk index — Phase 2 (cold-store purge).
@@ -2829,7 +2840,7 @@ var _010_cold_store_chunks_exports = /* @__PURE__ */ __exportAll({
2829
2840
  * forever. Adapters that don't opt into per-bucket archival via
2830
2841
  * `coldTtlDays` don't insert here either.
2831
2842
  */
2832
- async function up$50(db) {
2843
+ async function up$56(db) {
2833
2844
  await sql`
2834
2845
  CREATE TABLE public.cold_store_chunks (
2835
2846
  db TEXT NOT NULL,
@@ -2856,14 +2867,14 @@ async function up$50(db) {
2856
2867
  ON public.cold_store_chunks (db, table_name, tenant_id, archived_at DESC)
2857
2868
  `.execute(db);
2858
2869
  }
2859
- async function down$50(db) {
2870
+ async function down$56(db) {
2860
2871
  await sql`DROP TABLE IF EXISTS public.cold_store_chunks`.execute(db);
2861
2872
  }
2862
2873
  //#endregion
2863
2874
  //#region src/db/migrations/011_drop_source_secrets_notify.ts
2864
2875
  var _011_drop_source_secrets_notify_exports = /* @__PURE__ */ __exportAll({
2865
- down: () => down$49,
2866
- up: () => up$49
2876
+ down: () => down$55,
2877
+ up: () => up$55
2867
2878
  });
2868
2879
  /**
2869
2880
  * Drop the `source_secrets_change_trigger` and the
@@ -2882,11 +2893,11 @@ var _011_drop_source_secrets_notify_exports = /* @__PURE__ */ __exportAll({
2882
2893
  * in `001_initial.ts`. They wake up no consumer until the `WebhookSecretManager`
2883
2894
  * is restored, so this migration is safe to roll back.
2884
2895
  */
2885
- async function up$49(db) {
2896
+ async function up$55(db) {
2886
2897
  await sql`DROP TRIGGER IF EXISTS source_secrets_change_trigger ON public.scoped_secrets`.execute(db);
2887
2898
  await sql`DROP FUNCTION IF EXISTS public.notify_source_secrets_change() CASCADE`.execute(db);
2888
2899
  }
2889
- async function down$49(db) {
2900
+ async function down$55(db) {
2890
2901
  await sql`
2891
2902
  CREATE OR REPLACE FUNCTION public.notify_source_secrets_change() RETURNS trigger
2892
2903
  LANGUAGE plpgsql
@@ -2925,8 +2936,8 @@ async function down$49(db) {
2925
2936
  //#endregion
2926
2937
  //#region src/db/migrations/012_peer_credentials_active_uniq.ts
2927
2938
  var _012_peer_credentials_active_uniq_exports = /* @__PURE__ */ __exportAll({
2928
- down: () => down$48,
2929
- up: () => up$48
2939
+ down: () => down$54,
2940
+ up: () => up$54
2930
2941
  });
2931
2942
  /**
2932
2943
  * Add a partial unique index on `peer_credentials (instance_id) WHERE
@@ -2952,7 +2963,7 @@ var _012_peer_credentials_active_uniq_exports = /* @__PURE__ */ __exportAll({
2952
2963
  * `down()` only drops the index; it does NOT undo the dedupe (there's no
2953
2964
  * safe way to recreate revoked rows, and the dedupe is monotonic).
2954
2965
  */
2955
- async function up$48(db) {
2966
+ async function up$54(db) {
2956
2967
  await sql`
2957
2968
  UPDATE public.peer_credentials
2958
2969
  SET revoked_at = NOW()
@@ -2970,14 +2981,14 @@ async function up$48(db) {
2970
2981
  WHERE revoked_at IS NULL
2971
2982
  `.execute(db);
2972
2983
  }
2973
- async function down$48(db) {
2984
+ async function down$54(db) {
2974
2985
  await sql`DROP INDEX IF EXISTS public.peer_credentials_active_uniq`.execute(db);
2975
2986
  }
2976
2987
  //#endregion
2977
2988
  //#region src/db/migrations/013_execution_log_bytes.ts
2978
2989
  var _013_execution_log_bytes_exports = /* @__PURE__ */ __exportAll({
2979
- down: () => down$47,
2980
- up: () => up$47
2990
+ down: () => down$53,
2991
+ up: () => up$53
2981
2992
  });
2982
2993
  /**
2983
2994
  * Add `log_bytes BIGINT NOT NULL DEFAULT 0` columns to `execution_runs` and
@@ -2996,7 +3007,7 @@ var _013_execution_log_bytes_exports = /* @__PURE__ */ __exportAll({
2996
3007
  *
2997
3008
  * Idempotent (`ADD COLUMN IF NOT EXISTS`).
2998
3009
  */
2999
- async function up$47(db) {
3010
+ async function up$53(db) {
3000
3011
  await sql`
3001
3012
  ALTER TABLE public.execution_runs
3002
3013
  ADD COLUMN IF NOT EXISTS log_bytes BIGINT NOT NULL DEFAULT 0
@@ -3006,15 +3017,15 @@ async function up$47(db) {
3006
3017
  ADD COLUMN IF NOT EXISTS log_bytes BIGINT NOT NULL DEFAULT 0
3007
3018
  `.execute(db);
3008
3019
  }
3009
- async function down$47(db) {
3020
+ async function down$53(db) {
3010
3021
  await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS log_bytes`.execute(db);
3011
3022
  await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS log_bytes`.execute(db);
3012
3023
  }
3013
3024
  //#endregion
3014
3025
  //#region src/db/migrations/014_kici_events_lease_retry.ts
3015
3026
  var _014_kici_events_lease_retry_exports = /* @__PURE__ */ __exportAll({
3016
- down: () => down$46,
3017
- up: () => up$46
3027
+ down: () => down$52,
3028
+ up: () => up$52
3018
3029
  });
3019
3030
  /**
3020
3031
  * Add lease + retry + DLQ columns to `kici_events` so the EventRouter can
@@ -3048,7 +3059,7 @@ var _014_kici_events_lease_retry_exports = /* @__PURE__ */ __exportAll({
3048
3059
  *
3049
3060
  * Idempotent (`ADD COLUMN IF NOT EXISTS` + `CREATE INDEX IF NOT EXISTS`).
3050
3061
  */
3051
- async function up$46(db) {
3062
+ async function up$52(db) {
3052
3063
  await sql`
3053
3064
  ALTER TABLE public.kici_events
3054
3065
  ADD COLUMN IF NOT EXISTS claimed_at TIMESTAMPTZ,
@@ -3079,7 +3090,7 @@ async function up$46(db) {
3079
3090
  WHERE dlq_at IS NOT NULL
3080
3091
  `.execute(db);
3081
3092
  }
3082
- async function down$46(db) {
3093
+ async function down$52(db) {
3083
3094
  await sql`DROP INDEX IF EXISTS public.idx_kici_events_dlq`.execute(db);
3084
3095
  await sql`DROP INDEX IF EXISTS public.idx_kici_events_lease_expired`.execute(db);
3085
3096
  await sql`DROP INDEX IF EXISTS public.idx_kici_events_retry_due`.execute(db);
@@ -3097,8 +3108,8 @@ async function down$46(db) {
3097
3108
  //#endregion
3098
3109
  //#region src/db/migrations/015_org_settings_customer_scoped.ts
3099
3110
  var _015_org_settings_customer_scoped_exports = /* @__PURE__ */ __exportAll({
3100
- down: () => down$45,
3101
- up: () => up$45
3111
+ down: () => down$51,
3112
+ up: () => up$51
3102
3113
  });
3103
3114
  /**
3104
3115
  * Org-scope `org_settings` and qualify each glob entry by source.
@@ -3126,7 +3137,7 @@ var _015_org_settings_customer_scoped_exports = /* @__PURE__ */ __exportAll({
3126
3137
  * Idempotent: a re-run on an already-migrated DB sees `customer_id` exists
3127
3138
  * and the list columns are already jsonb, so it is a no-op.
3128
3139
  */
3129
- async function up$45(db) {
3140
+ async function up$51(db) {
3130
3141
  if ((await sql`
3131
3142
  SELECT EXISTS (
3132
3143
  SELECT 1 FROM information_schema.columns
@@ -3251,7 +3262,7 @@ async function up$45(db) {
3251
3262
  await sql`DROP TABLE _org_settings_merged`.execute(db);
3252
3263
  await sql`DROP TABLE _org_settings_stage`.execute(db);
3253
3264
  }
3254
- async function down$45(db) {
3265
+ async function down$51(db) {
3255
3266
  if (!(await sql`
3256
3267
  SELECT EXISTS (
3257
3268
  SELECT 1 FROM information_schema.columns
@@ -3276,8 +3287,8 @@ async function down$45(db) {
3276
3287
  //#endregion
3277
3288
  //#region src/db/migrations/016_org_settings_allow_http_npm.ts
3278
3289
  var _016_org_settings_allow_http_npm_exports = /* @__PURE__ */ __exportAll({
3279
- down: () => down$44,
3280
- up: () => up$44
3290
+ down: () => down$50,
3291
+ up: () => up$50
3281
3292
  });
3282
3293
  /**
3283
3294
  * Add `org_settings.allow_http_npm_registries boolean NOT NULL DEFAULT false`.
@@ -3290,7 +3301,7 @@ var _016_org_settings_allow_http_npm_exports = /* @__PURE__ */ __exportAll({
3290
3301
  *
3291
3302
  * Idempotent: a re-run on a DB that already has the column is a no-op.
3292
3303
  */
3293
- async function up$44(db) {
3304
+ async function up$50(db) {
3294
3305
  if ((await sql`
3295
3306
  SELECT EXISTS (
3296
3307
  SELECT 1 FROM information_schema.columns
@@ -3304,7 +3315,7 @@ async function up$44(db) {
3304
3315
  ADD COLUMN allow_http_npm_registries boolean NOT NULL DEFAULT false
3305
3316
  `.execute(db);
3306
3317
  }
3307
- async function down$44(db) {
3318
+ async function down$50(db) {
3308
3319
  await sql`
3309
3320
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS allow_http_npm_registries
3310
3321
  `.execute(db);
@@ -3312,8 +3323,8 @@ async function down$44(db) {
3312
3323
  //#endregion
3313
3324
  //#region src/db/migrations/017_org_id_widen.ts
3314
3325
  var _017_org_id_widen_exports = /* @__PURE__ */ __exportAll({
3315
- down: () => down$43,
3316
- up: () => up$43
3326
+ down: () => down$49,
3327
+ up: () => up$49
3317
3328
  });
3318
3329
  /**
3319
3330
  * Widen every orchestrator-side `org_id varchar(12)` column to
@@ -3353,17 +3364,17 @@ const ORG_ID_TABLES$1 = [
3353
3364
  "held_runs",
3354
3365
  "scoped_secrets"
3355
3366
  ];
3356
- async function up$43(db) {
3367
+ async function up$49(db) {
3357
3368
  for (const table of ORG_ID_TABLES$1) await sql.raw(`ALTER TABLE public.${table} ALTER COLUMN org_id TYPE varchar(16)`).execute(db);
3358
3369
  }
3359
- async function down$43(db) {
3370
+ async function down$49(db) {
3360
3371
  for (const table of ORG_ID_TABLES$1) await sql.raw(`ALTER TABLE public.${table} ALTER COLUMN org_id TYPE varchar(12)`).execute(db);
3361
3372
  }
3362
3373
  //#endregion
3363
3374
  //#region src/db/migrations/018_org_id_prefix_backfill.ts
3364
3375
  var _018_org_id_prefix_backfill_exports = /* @__PURE__ */ __exportAll({
3365
- down: () => down$42,
3366
- up: () => up$42
3376
+ down: () => down$48,
3377
+ up: () => up$48
3367
3378
  });
3368
3379
  /**
3369
3380
  * Prefix every orchestrator-side tenant string with `org_` to align
@@ -3408,19 +3419,19 @@ const CUSTOMER_ID_TABLES = [
3408
3419
  "workflow_registrations",
3409
3420
  "org_settings"
3410
3421
  ];
3411
- async function up$42(db) {
3422
+ async function up$48(db) {
3412
3423
  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);
3413
3424
  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);
3414
3425
  }
3415
- async function down$42(db) {
3426
+ async function down$48(db) {
3416
3427
  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);
3417
3428
  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);
3418
3429
  }
3419
3430
  //#endregion
3420
3431
  //#region src/db/migrations/019_generic_sources_change_notify.ts
3421
3432
  var _019_generic_sources_change_notify_exports = /* @__PURE__ */ __exportAll({
3422
- down: () => down$41,
3423
- up: () => up$41
3433
+ down: () => down$47,
3434
+ up: () => up$47
3424
3435
  });
3425
3436
  /**
3426
3437
  * Add a Postgres trigger on `generic_webhook_sources` that emits
@@ -3443,7 +3454,7 @@ var _019_generic_sources_change_notify_exports = /* @__PURE__ */ __exportAll({
3443
3454
  * (`notify_sources_change()` + `sources_change_trigger`, defined in
3444
3455
  * `001_initial.ts`).
3445
3456
  */
3446
- async function up$41(db) {
3457
+ async function up$47(db) {
3447
3458
  await sql`
3448
3459
  CREATE FUNCTION public.notify_generic_sources_change() RETURNS trigger
3449
3460
  LANGUAGE plpgsql
@@ -3464,15 +3475,15 @@ async function up$41(db) {
3464
3475
  FOR EACH ROW EXECUTE FUNCTION public.notify_generic_sources_change()
3465
3476
  `.execute(db);
3466
3477
  }
3467
- async function down$41(db) {
3478
+ async function down$47(db) {
3468
3479
  await sql`DROP TRIGGER IF EXISTS generic_sources_change_trigger ON public.generic_webhook_sources`.execute(db);
3469
3480
  await sql`DROP FUNCTION IF EXISTS public.notify_generic_sources_change()`.execute(db);
3470
3481
  }
3471
3482
  //#endregion
3472
3483
  //#region src/db/migrations/020_org_settings_dashboard_write_policy.ts
3473
3484
  var _020_org_settings_dashboard_write_policy_exports = /* @__PURE__ */ __exportAll({
3474
- down: () => down$40,
3475
- up: () => up$40
3485
+ down: () => down$46,
3486
+ up: () => up$46
3476
3487
  });
3477
3488
  /**
3478
3489
  * Add `org_settings.dashboard_write_policy jsonb NOT NULL DEFAULT '{}'`.
@@ -3487,7 +3498,7 @@ var _020_org_settings_dashboard_write_policy_exports = /* @__PURE__ */ __exportA
3487
3498
  *
3488
3499
  * Idempotent: a re-run on a DB that already has the column is a no-op.
3489
3500
  */
3490
- async function up$40(db) {
3501
+ async function up$46(db) {
3491
3502
  if ((await sql`
3492
3503
  SELECT EXISTS (
3493
3504
  SELECT 1 FROM information_schema.columns
@@ -3501,7 +3512,7 @@ async function up$40(db) {
3501
3512
  ADD COLUMN dashboard_write_policy jsonb NOT NULL DEFAULT '{}'::jsonb
3502
3513
  `.execute(db);
3503
3514
  }
3504
- async function down$40(db) {
3515
+ async function down$46(db) {
3505
3516
  await sql`
3506
3517
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS dashboard_write_policy
3507
3518
  `.execute(db);
@@ -3509,8 +3520,8 @@ async function down$40(db) {
3509
3520
  //#endregion
3510
3521
  //#region src/db/migrations/021_check_run_tracking.ts
3511
3522
  var _021_check_run_tracking_exports = /* @__PURE__ */ __exportAll({
3512
- down: () => down$39,
3513
- up: () => up$39
3523
+ down: () => down$45,
3524
+ up: () => up$45
3514
3525
  });
3515
3526
  /**
3516
3527
  * Add `check_run_tracking` table for HA-safe check-run state persistence.
@@ -3534,7 +3545,7 @@ var _021_check_run_tracking_exports = /* @__PURE__ */ __exportAll({
3534
3545
  *
3535
3546
  * Idempotent: a re-run on a DB that already has the table is a no-op.
3536
3547
  */
3537
- async function up$39(db) {
3548
+ async function up$45(db) {
3538
3549
  if ((await sql`
3539
3550
  SELECT EXISTS (
3540
3551
  SELECT 1 FROM information_schema.tables
@@ -3565,14 +3576,14 @@ async function up$39(db) {
3565
3576
  WHERE run_id IS NOT NULL
3566
3577
  `.execute(db);
3567
3578
  }
3568
- async function down$39(db) {
3579
+ async function down$45(db) {
3569
3580
  await sql`DROP TABLE IF EXISTS public.check_run_tracking`.execute(db);
3570
3581
  }
3571
3582
  //#endregion
3572
3583
  //#region src/db/migrations/022_scaler_manager_state.ts
3573
3584
  var _022_scaler_manager_state_exports = /* @__PURE__ */ __exportAll({
3574
- down: () => down$38,
3575
- up: () => up$38
3585
+ down: () => down$44,
3586
+ up: () => up$44
3576
3587
  });
3577
3588
  /**
3578
3589
  * Add three tables persisting `ScalerManager` per-coord state:
@@ -3599,7 +3610,7 @@ var _022_scaler_manager_state_exports = /* @__PURE__ */ __exportAll({
3599
3610
  * Idempotent: a re-run on a DB that already has any of these tables
3600
3611
  * leaves the existing one alone.
3601
3612
  */
3602
- async function up$38(db) {
3613
+ async function up$44(db) {
3603
3614
  const tableExists = async (name) => {
3604
3615
  return (await sql`
3605
3616
  SELECT EXISTS (
@@ -3649,7 +3660,7 @@ async function up$38(db) {
3649
3660
  `.execute(db);
3650
3661
  }
3651
3662
  }
3652
- async function down$38(db) {
3663
+ async function down$44(db) {
3653
3664
  await sql`DROP TABLE IF EXISTS public.scaler_reservations`.execute(db);
3654
3665
  await sql`DROP TABLE IF EXISTS public.scaler_agent_jobs`.execute(db);
3655
3666
  await sql`DROP TABLE IF EXISTS public.scaler_spawning_agents`.execute(db);
@@ -3657,8 +3668,8 @@ async function down$38(db) {
3657
3668
  //#endregion
3658
3669
  //#region src/db/migrations/023_dispatch_queue_recovery_deadline.ts
3659
3670
  var _023_dispatch_queue_recovery_deadline_exports = /* @__PURE__ */ __exportAll({
3660
- down: () => down$37,
3661
- up: () => up$37
3671
+ down: () => down$43,
3672
+ up: () => up$43
3662
3673
  });
3663
3674
  /**
3664
3675
  * Add `dispatch_queue.recovery_deadline TIMESTAMPTZ` and
@@ -3682,7 +3693,7 @@ var _023_dispatch_queue_recovery_deadline_exports = /* @__PURE__ */ __exportAll(
3682
3693
  * Idempotent: re-running on a DB that already has either column is a
3683
3694
  * no-op.
3684
3695
  */
3685
- async function up$37(db) {
3696
+ async function up$43(db) {
3686
3697
  const colExists = async (name) => {
3687
3698
  return (await sql`
3688
3699
  SELECT EXISTS (
@@ -3707,7 +3718,7 @@ async function up$37(db) {
3707
3718
  WHERE recovery_deadline IS NOT NULL
3708
3719
  `.execute(db);
3709
3720
  }
3710
- async function down$37(db) {
3721
+ async function down$43(db) {
3711
3722
  await sql`DROP INDEX IF EXISTS public.idx_dispatch_queue_recovery_deadline`.execute(db);
3712
3723
  await sql`
3713
3724
  ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS recovery_agent_id
@@ -3719,8 +3730,8 @@ async function down$37(db) {
3719
3730
  //#endregion
3720
3731
  //#region src/db/migrations/024_dispatch_queue_provisioning_error.ts
3721
3732
  var _024_dispatch_queue_provisioning_error_exports = /* @__PURE__ */ __exportAll({
3722
- down: () => down$36,
3723
- up: () => up$36
3733
+ down: () => down$42,
3734
+ up: () => up$42
3724
3735
  });
3725
3736
  /**
3726
3737
  * Add `dispatch_queue.last_provisioning_error TEXT` recording the most
@@ -3736,7 +3747,7 @@ var _024_dispatch_queue_provisioning_error_exports = /* @__PURE__ */ __exportAll
3736
3747
  *
3737
3748
  * Idempotent: re-running on a DB that already has the column is a no-op.
3738
3749
  */
3739
- async function up$36(db) {
3750
+ async function up$42(db) {
3740
3751
  const colExists = async (name) => {
3741
3752
  return (await sql`
3742
3753
  SELECT EXISTS (
@@ -3752,7 +3763,7 @@ async function up$36(db) {
3752
3763
  ADD COLUMN last_provisioning_error TEXT
3753
3764
  `.execute(db);
3754
3765
  }
3755
- async function down$36(db) {
3766
+ async function down$42(db) {
3756
3767
  await sql`
3757
3768
  ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS last_provisioning_error
3758
3769
  `.execute(db);
@@ -3760,8 +3771,8 @@ async function down$36(db) {
3760
3771
  //#endregion
3761
3772
  //#region src/db/migrations/025_init_failure.ts
3762
3773
  var _025_init_failure_exports = /* @__PURE__ */ __exportAll({
3763
- down: () => down$35,
3764
- up: () => up$35
3774
+ down: () => down$41,
3775
+ up: () => up$41
3765
3776
  });
3766
3777
  /**
3767
3778
  * Add `init_failure jsonb` columns to `execution_runs` and `execution_jobs`.
@@ -3775,7 +3786,7 @@ var _025_init_failure_exports = /* @__PURE__ */ __exportAll({
3775
3786
  * Idempotent: re-running on a DB that already has either column is a no-op
3776
3787
  * for that column.
3777
3788
  */
3778
- async function up$35(db) {
3789
+ async function up$41(db) {
3779
3790
  const colExists = async (table, name) => {
3780
3791
  return (await sql`
3781
3792
  SELECT EXISTS (
@@ -3795,7 +3806,7 @@ async function up$35(db) {
3795
3806
  ADD COLUMN init_failure JSONB DEFAULT NULL
3796
3807
  `.execute(db);
3797
3808
  }
3798
- async function down$35(db) {
3809
+ async function down$41(db) {
3799
3810
  await sql`
3800
3811
  ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS init_failure
3801
3812
  `.execute(db);
@@ -3806,8 +3817,8 @@ async function down$35(db) {
3806
3817
  //#endregion
3807
3818
  //#region src/db/migrations/026_event_log_lockfile_corrupt.ts
3808
3819
  var _026_event_log_lockfile_corrupt_exports = /* @__PURE__ */ __exportAll({
3809
- down: () => down$34,
3810
- up: () => up$34
3820
+ down: () => down$40,
3821
+ up: () => up$40
3811
3822
  });
3812
3823
  /**
3813
3824
  * Extend the event_log.status CHECK constraint with 'lockfile_corrupt' so the
@@ -3816,7 +3827,7 @@ var _026_event_log_lockfile_corrupt_exports = /* @__PURE__ */ __exportAll({
3816
3827
  *
3817
3828
  * Idempotent: the DROP ... IF EXISTS / re-ADD pair re-runs cleanly.
3818
3829
  */
3819
- async function up$34(db) {
3830
+ async function up$40(db) {
3820
3831
  await sql`ALTER TABLE event_log DROP CONSTRAINT IF EXISTS event_log_status_check`.execute(db);
3821
3832
  await sql`
3822
3833
  ALTER TABLE event_log ADD CONSTRAINT event_log_status_check
@@ -3826,7 +3837,7 @@ async function up$34(db) {
3826
3837
  ])))
3827
3838
  `.execute(db);
3828
3839
  }
3829
- async function down$34(db) {
3840
+ async function down$40(db) {
3830
3841
  await sql`ALTER TABLE event_log DROP CONSTRAINT IF EXISTS event_log_status_check`.execute(db);
3831
3842
  await sql`
3832
3843
  ALTER TABLE event_log ADD CONSTRAINT event_log_status_check
@@ -3839,8 +3850,8 @@ async function down$34(db) {
3839
3850
  //#endregion
3840
3851
  //#region src/db/migrations/027_workflow_timeout.ts
3841
3852
  var _027_workflow_timeout_exports = /* @__PURE__ */ __exportAll({
3842
- down: () => down$33,
3843
- up: () => up$33
3853
+ down: () => down$39,
3854
+ up: () => up$39
3844
3855
  });
3845
3856
  /**
3846
3857
  * Add `workflow_timeout_ms integer` to `execution_runs`.
@@ -3858,13 +3869,13 @@ var _027_workflow_timeout_exports = /* @__PURE__ */ __exportAll({
3858
3869
  *
3859
3870
  * Idempotent: re-running on a DB that already has the column is a no-op.
3860
3871
  */
3861
- async function up$33(db) {
3872
+ async function up$39(db) {
3862
3873
  await sql`
3863
3874
  ALTER TABLE public.execution_runs
3864
3875
  ADD COLUMN IF NOT EXISTS workflow_timeout_ms INTEGER DEFAULT NULL
3865
3876
  `.execute(db);
3866
3877
  }
3867
- async function down$33(db) {
3878
+ async function down$39(db) {
3868
3879
  await sql`
3869
3880
  ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS workflow_timeout_ms
3870
3881
  `.execute(db);
@@ -3872,8 +3883,8 @@ async function down$33(db) {
3872
3883
  //#endregion
3873
3884
  //#region src/db/migrations/028_org_settings_user_cache.ts
3874
3885
  var _028_org_settings_user_cache_exports = /* @__PURE__ */ __exportAll({
3875
- down: () => down$32,
3876
- up: () => up$32
3886
+ down: () => down$38,
3887
+ up: () => up$38
3877
3888
  });
3878
3889
  /**
3879
3890
  * Add `org_settings.user_cache_quota_bytes bigint` and
@@ -3892,7 +3903,7 @@ var _028_org_settings_user_cache_exports = /* @__PURE__ */ __exportAll({
3892
3903
  *
3893
3904
  * Idempotent: a re-run on a DB that already has either column skips it.
3894
3905
  */
3895
- async function columnExists(db, column) {
3906
+ async function columnExists$1(db, column) {
3896
3907
  return (await sql`
3897
3908
  SELECT EXISTS (
3898
3909
  SELECT 1 FROM information_schema.columns
@@ -3902,17 +3913,17 @@ async function columnExists(db, column) {
3902
3913
  ) AS exists
3903
3914
  `.execute(db)).rows[0]?.exists ?? false;
3904
3915
  }
3905
- async function up$32(db) {
3906
- if (!await columnExists(db, "user_cache_quota_bytes")) await sql`
3916
+ async function up$38(db) {
3917
+ if (!await columnExists$1(db, "user_cache_quota_bytes")) await sql`
3907
3918
  ALTER TABLE public.org_settings
3908
3919
  ADD COLUMN user_cache_quota_bytes bigint
3909
3920
  `.execute(db);
3910
- if (!await columnExists(db, "user_cache_ttl_ms")) await sql`
3921
+ if (!await columnExists$1(db, "user_cache_ttl_ms")) await sql`
3911
3922
  ALTER TABLE public.org_settings
3912
3923
  ADD COLUMN user_cache_ttl_ms bigint
3913
3924
  `.execute(db);
3914
3925
  }
3915
- async function down$32(db) {
3926
+ async function down$38(db) {
3916
3927
  await sql`
3917
3928
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS user_cache_quota_bytes
3918
3929
  `.execute(db);
@@ -3923,8 +3934,8 @@ async function down$32(db) {
3923
3934
  //#endregion
3924
3935
  //#region src/db/migrations/029_dispatch_queue_attempts.ts
3925
3936
  var _029_dispatch_queue_attempts_exports = /* @__PURE__ */ __exportAll({
3926
- down: () => down$31,
3927
- up: () => up$31
3937
+ down: () => down$37,
3938
+ up: () => up$37
3928
3939
  });
3929
3940
  /**
3930
3941
  * Add `dispatch_queue.dispatch_attempts INT NOT NULL DEFAULT 0`.
@@ -3938,7 +3949,7 @@ var _029_dispatch_queue_attempts_exports = /* @__PURE__ */ __exportAll({
3938
3949
  *
3939
3950
  * Idempotent: re-running on a DB that already has the column is a no-op.
3940
3951
  */
3941
- async function up$31(db) {
3952
+ async function up$37(db) {
3942
3953
  if (!((await sql`
3943
3954
  SELECT EXISTS (
3944
3955
  SELECT 1 FROM information_schema.columns
@@ -3951,7 +3962,7 @@ async function up$31(db) {
3951
3962
  ADD COLUMN dispatch_attempts INT NOT NULL DEFAULT 0
3952
3963
  `.execute(db);
3953
3964
  }
3954
- async function down$31(db) {
3965
+ async function down$37(db) {
3955
3966
  await sql`
3956
3967
  ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS dispatch_attempts
3957
3968
  `.execute(db);
@@ -3959,8 +3970,8 @@ async function down$31(db) {
3959
3970
  //#endregion
3960
3971
  //#region src/db/migrations/030_held_runs_env_set_null.ts
3961
3972
  var _030_held_runs_env_set_null_exports = /* @__PURE__ */ __exportAll({
3962
- down: () => down$30,
3963
- up: () => up$30
3973
+ down: () => down$36,
3974
+ up: () => up$36
3964
3975
  });
3965
3976
  /**
3966
3977
  * held_runs.environment_id becomes nullable with ON DELETE SET NULL so
@@ -3971,14 +3982,14 @@ var _030_held_runs_env_set_null_exports = /* @__PURE__ */ __exportAll({
3971
3982
  * Idempotent: dropping the NOT NULL and the constraint are both no-ops on a
3972
3983
  * re-run, and the constraint is re-created with the SET NULL action.
3973
3984
  */
3974
- async function up$30(db) {
3985
+ async function up$36(db) {
3975
3986
  await sql`ALTER TABLE public.held_runs ALTER COLUMN environment_id DROP NOT NULL`.execute(db);
3976
3987
  await sql`ALTER TABLE public.held_runs DROP CONSTRAINT IF EXISTS held_runs_environment_id_fkey`.execute(db);
3977
3988
  await sql`ALTER TABLE public.held_runs
3978
3989
  ADD CONSTRAINT held_runs_environment_id_fkey
3979
3990
  FOREIGN KEY (environment_id) REFERENCES public.environments(id) ON DELETE SET NULL`.execute(db);
3980
3991
  }
3981
- async function down$30(db) {
3992
+ async function down$36(db) {
3982
3993
  await sql`ALTER TABLE public.held_runs DROP CONSTRAINT IF EXISTS held_runs_environment_id_fkey`.execute(db);
3983
3994
  await sql`ALTER TABLE public.held_runs
3984
3995
  ADD CONSTRAINT held_runs_environment_id_fkey
@@ -3988,8 +3999,8 @@ async function down$30(db) {
3988
3999
  //#endregion
3989
4000
  //#region src/db/migrations/031_dispatch_queue_ack_deadline.ts
3990
4001
  var _031_dispatch_queue_ack_deadline_exports = /* @__PURE__ */ __exportAll({
3991
- down: () => down$29,
3992
- up: () => up$29
4002
+ down: () => down$35,
4003
+ up: () => up$35
3993
4004
  });
3994
4005
  /**
3995
4006
  * Add `dispatch_queue.ack_deadline TIMESTAMPTZ` and
@@ -4006,7 +4017,7 @@ var _031_dispatch_queue_ack_deadline_exports = /* @__PURE__ */ __exportAll({
4006
4017
  *
4007
4018
  * Idempotent: re-running on a DB that already has either column is a no-op.
4008
4019
  */
4009
- async function up$29(db) {
4020
+ async function up$35(db) {
4010
4021
  const colExists = async (name) => {
4011
4022
  return (await sql`
4012
4023
  SELECT EXISTS (
@@ -4031,7 +4042,7 @@ async function up$29(db) {
4031
4042
  WHERE ack_deadline IS NOT NULL
4032
4043
  `.execute(db);
4033
4044
  }
4034
- async function down$29(db) {
4045
+ async function down$35(db) {
4035
4046
  await sql`DROP INDEX IF EXISTS public.idx_dispatch_queue_ack_deadline`.execute(db);
4036
4047
  await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS ack_agent_id`.execute(db);
4037
4048
  await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS ack_deadline`.execute(db);
@@ -4039,8 +4050,8 @@ async function down$29(db) {
4039
4050
  //#endregion
4040
4051
  //#region src/db/migrations/032_org_settings_dispatch_ack_timeout.ts
4041
4052
  var _032_org_settings_dispatch_ack_timeout_exports = /* @__PURE__ */ __exportAll({
4042
- down: () => down$28,
4043
- up: () => up$28
4053
+ down: () => down$34,
4054
+ up: () => up$34
4044
4055
  });
4045
4056
  /**
4046
4057
  * Add `org_settings.dispatch_ack_timeout_ms BIGINT` (nullable).
@@ -4052,7 +4063,7 @@ var _032_org_settings_dispatch_ack_timeout_exports = /* @__PURE__ */ __exportAll
4052
4063
  *
4053
4064
  * Idempotent: a re-run on a DB that already has the column is a no-op.
4054
4065
  */
4055
- async function up$28(db) {
4066
+ async function up$34(db) {
4056
4067
  if ((await sql`
4057
4068
  SELECT EXISTS (
4058
4069
  SELECT 1 FROM information_schema.columns
@@ -4066,7 +4077,7 @@ async function up$28(db) {
4066
4077
  ADD COLUMN dispatch_ack_timeout_ms BIGINT
4067
4078
  `.execute(db);
4068
4079
  }
4069
- async function down$28(db) {
4080
+ async function down$34(db) {
4070
4081
  await sql`
4071
4082
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS dispatch_ack_timeout_ms
4072
4083
  `.execute(db);
@@ -4074,8 +4085,8 @@ async function down$28(db) {
4074
4085
  //#endregion
4075
4086
  //#region src/db/migrations/033_org_settings_approval.ts
4076
4087
  var _033_org_settings_approval_exports = /* @__PURE__ */ __exportAll({
4077
- down: () => down$27,
4078
- up: () => up$27
4088
+ down: () => down$33,
4089
+ up: () => up$33
4079
4090
  });
4080
4091
  /**
4081
4092
  * Add the two approval-policy columns to `org_settings`:
@@ -4092,7 +4103,7 @@ var _033_org_settings_approval_exports = /* @__PURE__ */ __exportAll({
4092
4103
  * and the orchestrator admin route. Idempotent: a re-run on a DB that already
4093
4104
  * has the columns is a no-op (each column is guarded independently).
4094
4105
  */
4095
- async function up$27(db) {
4106
+ async function up$33(db) {
4096
4107
  const colExists = async (column) => {
4097
4108
  return (await sql`
4098
4109
  SELECT EXISTS (
@@ -4112,7 +4123,7 @@ async function up$27(db) {
4112
4123
  ADD COLUMN allow_self_approval BOOLEAN NOT NULL DEFAULT true
4113
4124
  `.execute(db);
4114
4125
  }
4115
- async function down$27(db) {
4126
+ async function down$33(db) {
4116
4127
  await sql`
4117
4128
  ALTER TABLE public.org_settings DROP COLUMN IF EXISTS approval_expiry_seconds
4118
4129
  `.execute(db);
@@ -4123,8 +4134,8 @@ async function down$27(db) {
4123
4134
  //#endregion
4124
4135
  //#region src/db/migrations/034_held_runs_generalize.ts
4125
4136
  var _034_held_runs_generalize_exports = /* @__PURE__ */ __exportAll({
4126
- down: () => down$26,
4127
- up: () => up$26
4137
+ down: () => down$32,
4138
+ up: () => up$32
4128
4139
  });
4129
4140
  /**
4130
4141
  * Generalize `held_runs` from an environment-only hold into the unified
@@ -4146,7 +4157,7 @@ var _034_held_runs_generalize_exports = /* @__PURE__ */ __exportAll({
4146
4157
  * New `held_run_approvals` table: one row per approver decision, FK to
4147
4158
  * `held_runs.id` (uuid) with ON DELETE CASCADE.
4148
4159
  */
4149
- async function up$26(db) {
4160
+ async function up$32(db) {
4150
4161
  const colExists = async (column) => {
4151
4162
  return (await sql`
4152
4163
  SELECT EXISTS (
@@ -4179,7 +4190,7 @@ async function up$26(db) {
4179
4190
  ON public.held_run_approvals USING btree (held_run_id)
4180
4191
  `.execute(db);
4181
4192
  }
4182
- async function down$26(db) {
4193
+ async function down$32(db) {
4183
4194
  await sql`DROP TABLE IF EXISTS public.held_run_approvals`.execute(db);
4184
4195
  await sql`ALTER TABLE public.held_runs DROP COLUMN IF EXISTS approval_requirement`.execute(db);
4185
4196
  await sql`ALTER TABLE public.held_runs DROP COLUMN IF EXISTS trigger_source`.execute(db);
@@ -4189,8 +4200,8 @@ async function down$26(db) {
4189
4200
  //#endregion
4190
4201
  //#region src/db/migrations/035_pending_workflow_contexts.ts
4191
4202
  var _035_pending_workflow_contexts_exports = /* @__PURE__ */ __exportAll({
4192
- down: () => down$25,
4193
- up: () => up$25
4203
+ down: () => down$31,
4204
+ up: () => up$31
4194
4205
  });
4195
4206
  /**
4196
4207
  * Pending workflow dispatch context — backs resume of a workflow whose install
@@ -4199,7 +4210,7 @@ var _035_pending_workflow_contexts_exports = /* @__PURE__ */ __exportAll({
4199
4210
  * wait-timer expiry, concurrency slot free). The row is deleted once the resume
4200
4211
  * dispatch has been kicked off.
4201
4212
  */
4202
- async function up$25(db) {
4213
+ async function up$31(db) {
4203
4214
  await sql`
4204
4215
  CREATE TABLE IF NOT EXISTS public.pending_workflow_contexts (
4205
4216
  run_id text PRIMARY KEY,
@@ -4209,14 +4220,14 @@ async function up$25(db) {
4209
4220
  )
4210
4221
  `.execute(db);
4211
4222
  }
4212
- async function down$25(db) {
4223
+ async function down$31(db) {
4213
4224
  await sql`DROP TABLE IF EXISTS public.pending_workflow_contexts`.execute(db);
4214
4225
  }
4215
4226
  //#endregion
4216
4227
  //#region src/db/migrations/036_attestations.ts
4217
4228
  var _036_attestations_exports = /* @__PURE__ */ __exportAll({
4218
- down: () => down$24,
4219
- up: () => up$24
4229
+ down: () => down$30,
4230
+ up: () => up$30
4220
4231
  });
4221
4232
  /**
4222
4233
  * Add the `attestations` table for build-provenance bundles.
@@ -4229,7 +4240,7 @@ var _036_attestations_exports = /* @__PURE__ */ __exportAll({
4229
4240
  *
4230
4241
  * Idempotent: a re-run on a DB that already has the table is a no-op.
4231
4242
  */
4232
- async function up$24(db) {
4243
+ async function up$30(db) {
4233
4244
  if ((await sql`
4234
4245
  SELECT EXISTS (
4235
4246
  SELECT 1 FROM information_schema.tables
@@ -4255,14 +4266,14 @@ async function up$24(db) {
4255
4266
  ON public.attestations (run_id, job_id)
4256
4267
  `.execute(db);
4257
4268
  }
4258
- async function down$24(db) {
4269
+ async function down$30(db) {
4259
4270
  await sql`DROP TABLE IF EXISTS public.attestations`.execute(db);
4260
4271
  }
4261
4272
  //#endregion
4262
4273
  //#region src/db/migrations/037_generic_sources_provider_type_local.ts
4263
4274
  var _037_generic_sources_provider_type_local_exports = /* @__PURE__ */ __exportAll({
4264
- down: () => down$23,
4265
- up: () => up$23
4275
+ down: () => down$29,
4276
+ up: () => up$29
4266
4277
  });
4267
4278
  /**
4268
4279
  * Replace the `generic_webhook_sources.provider_type` CHECK constraint so it
@@ -4279,7 +4290,7 @@ var _037_generic_sources_provider_type_local_exports = /* @__PURE__ */ __exportA
4279
4290
  * Idempotent: the constraint is dropped IF EXISTS and recreated; the data
4280
4291
  * backfill is a plain UPDATE that is a no-op once no `'internal'` rows remain.
4281
4292
  */
4282
- async function up$23(db) {
4293
+ async function up$29(db) {
4283
4294
  await sql`
4284
4295
  ALTER TABLE public.generic_webhook_sources
4285
4296
  DROP CONSTRAINT IF EXISTS generic_webhook_sources_provider_type_check
@@ -4295,7 +4306,7 @@ async function up$23(db) {
4295
4306
  CHECK (provider_type = ANY (ARRAY['generic'::text, 'local'::text]))
4296
4307
  `.execute(db);
4297
4308
  }
4298
- async function down$23(db) {
4309
+ async function down$29(db) {
4299
4310
  await sql`
4300
4311
  ALTER TABLE public.generic_webhook_sources
4301
4312
  DROP CONSTRAINT IF EXISTS generic_webhook_sources_provider_type_check
@@ -4314,8 +4325,8 @@ async function down$23(db) {
4314
4325
  //#endregion
4315
4326
  //#region src/db/migrations/038_remote_sources.ts
4316
4327
  var _038_remote_sources_exports = /* @__PURE__ */ __exportAll({
4317
- down: () => down$22,
4318
- up: () => up$22
4328
+ down: () => down$28,
4329
+ up: () => up$28
4319
4330
  });
4320
4331
  /**
4321
4332
  * `remote_sources` anchors a Platform-relayed `kici run remote` to its real
@@ -4327,7 +4338,7 @@ var _038_remote_sources_exports = /* @__PURE__ */ __exportAll({
4327
4338
  *
4328
4339
  * Idempotent: a re-run on a DB that already has the table is a no-op.
4329
4340
  */
4330
- async function up$22(db) {
4341
+ async function up$28(db) {
4331
4342
  if ((await sql`
4332
4343
  SELECT EXISTS (
4333
4344
  SELECT 1 FROM information_schema.tables
@@ -4346,14 +4357,14 @@ async function up$22(db) {
4346
4357
  )
4347
4358
  `.execute(db);
4348
4359
  }
4349
- async function down$22(db) {
4360
+ async function down$28(db) {
4350
4361
  await sql`DROP TABLE IF EXISTS public.remote_sources`.execute(db);
4351
4362
  }
4352
4363
  //#endregion
4353
4364
  //#region src/db/migrations/039_host_roster.ts
4354
4365
  var _039_host_roster_exports = /* @__PURE__ */ __exportAll({
4355
- down: () => down$21,
4356
- up: () => up$21
4366
+ down: () => down$27,
4367
+ up: () => up$27
4357
4368
  });
4358
4369
  /**
4359
4370
  * `host_roster` is KiCI's declared inventory: one durable row per agent the
@@ -4370,7 +4381,7 @@ var _039_host_roster_exports = /* @__PURE__ */ __exportAll({
4370
4381
  *
4371
4382
  * Idempotent: a re-run on a DB that already has the table is a no-op.
4372
4383
  */
4373
- async function up$21(db) {
4384
+ async function up$27(db) {
4374
4385
  if ((await sql`
4375
4386
  SELECT EXISTS (
4376
4387
  SELECT 1 FROM information_schema.tables
@@ -4400,14 +4411,14 @@ async function up$21(db) {
4400
4411
  await sql`CREATE INDEX idx_host_roster_reap
4401
4412
  ON public.host_roster (lifecycle_class, last_seen)`.execute(db);
4402
4413
  }
4403
- async function down$21(db) {
4414
+ async function down$27(db) {
4404
4415
  await sql`DROP TABLE IF EXISTS public.host_roster`.execute(db);
4405
4416
  }
4406
4417
  //#endregion
4407
4418
  //#region src/db/migrations/040_runsonall_pin.ts
4408
4419
  var _040_runsonall_pin_exports = /* @__PURE__ */ __exportAll({
4409
- down: () => down$20,
4410
- up: () => up$20
4420
+ down: () => down$26,
4421
+ up: () => up$26
4411
4422
  });
4412
4423
  /**
4413
4424
  * Add the `runsOnAll` host fan-out columns:
@@ -4424,7 +4435,7 @@ var _040_runsonall_pin_exports = /* @__PURE__ */ __exportAll({
4424
4435
  *
4425
4436
  * Idempotent: re-running on a DB that already has any column is a no-op.
4426
4437
  */
4427
- async function colExists$9(db, table, name) {
4438
+ async function colExists$11(db, table, name) {
4428
4439
  return (await sql`
4429
4440
  SELECT EXISTS (
4430
4441
  SELECT 1 FROM information_schema.columns
@@ -4434,18 +4445,18 @@ async function colExists$9(db, table, name) {
4434
4445
  ) AS exists
4435
4446
  `.execute(db)).rows[0]?.exists ?? false;
4436
4447
  }
4437
- async function up$20(db) {
4438
- if (!await colExists$9(db, "dispatch_queue", "pinned_agent_id")) await sql`ALTER TABLE public.dispatch_queue ADD COLUMN pinned_agent_id TEXT`.execute(db);
4439
- if (!await colExists$9(db, "execution_jobs", "base_job_name")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN base_job_name TEXT`.execute(db);
4440
- if (!await colExists$9(db, "execution_jobs", "variant_kind")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN variant_kind TEXT`.execute(db);
4441
- if (!await colExists$9(db, "execution_jobs", "variant_label")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN variant_label TEXT`.execute(db);
4448
+ async function up$26(db) {
4449
+ if (!await colExists$11(db, "dispatch_queue", "pinned_agent_id")) await sql`ALTER TABLE public.dispatch_queue ADD COLUMN pinned_agent_id TEXT`.execute(db);
4450
+ if (!await colExists$11(db, "execution_jobs", "base_job_name")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN base_job_name TEXT`.execute(db);
4451
+ if (!await colExists$11(db, "execution_jobs", "variant_kind")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN variant_kind TEXT`.execute(db);
4452
+ if (!await colExists$11(db, "execution_jobs", "variant_label")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN variant_label TEXT`.execute(db);
4442
4453
  await sql`
4443
4454
  CREATE INDEX IF NOT EXISTS idx_dispatch_queue_pinned_agent
4444
4455
  ON public.dispatch_queue (pinned_agent_id)
4445
4456
  WHERE pinned_agent_id IS NOT NULL
4446
4457
  `.execute(db);
4447
4458
  }
4448
- async function down$20(db) {
4459
+ async function down$26(db) {
4449
4460
  await sql`DROP INDEX IF EXISTS public.idx_dispatch_queue_pinned_agent`.execute(db);
4450
4461
  await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS pinned_agent_id`.execute(db);
4451
4462
  await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS variant_label`.execute(db);
@@ -4455,8 +4466,8 @@ async function down$20(db) {
4455
4466
  //#endregion
4456
4467
  //#region src/db/migrations/041_wave_gated.ts
4457
4468
  var _041_wave_gated_exports = /* @__PURE__ */ __exportAll({
4458
- down: () => down$19,
4459
- up: () => up$19
4469
+ down: () => down$25,
4470
+ up: () => up$25
4460
4471
  });
4461
4472
  /**
4462
4473
  * Add the rolling fan-out wave-gate columns:
@@ -4476,7 +4487,7 @@ var _041_wave_gated_exports = /* @__PURE__ */ __exportAll({
4476
4487
  *
4477
4488
  * Idempotent: re-running on a DB that already has the columns is a no-op.
4478
4489
  */
4479
- async function colExists$8(db, table, name) {
4490
+ async function colExists$10(db, table, name) {
4480
4491
  return (await sql`
4481
4492
  SELECT EXISTS (
4482
4493
  SELECT 1 FROM information_schema.columns
@@ -4486,16 +4497,16 @@ async function colExists$8(db, table, name) {
4486
4497
  ) AS exists
4487
4498
  `.execute(db)).rows[0]?.exists ?? false;
4488
4499
  }
4489
- async function up$19(db) {
4490
- if (!await colExists$8(db, "execution_jobs", "wave_gated")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN wave_gated boolean NOT NULL DEFAULT false`.execute(db);
4491
- if (!await colExists$8(db, "execution_jobs", "wave_max_parallel")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN wave_max_parallel integer`.execute(db);
4492
- if (!await colExists$8(db, "execution_jobs", "wave_fail_fast")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN wave_fail_fast boolean`.execute(db);
4500
+ async function up$25(db) {
4501
+ if (!await colExists$10(db, "execution_jobs", "wave_gated")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN wave_gated boolean NOT NULL DEFAULT false`.execute(db);
4502
+ if (!await colExists$10(db, "execution_jobs", "wave_max_parallel")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN wave_max_parallel integer`.execute(db);
4503
+ if (!await colExists$10(db, "execution_jobs", "wave_fail_fast")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN wave_fail_fast boolean`.execute(db);
4493
4504
  await sql`
4494
4505
  CREATE INDEX IF NOT EXISTS idx_execution_jobs_wave
4495
4506
  ON public.execution_jobs (run_id, base_job_name, wave_gated)
4496
4507
  `.execute(db);
4497
4508
  }
4498
- async function down$19(db) {
4509
+ async function down$25(db) {
4499
4510
  await sql`DROP INDEX IF EXISTS public.idx_execution_jobs_wave`.execute(db);
4500
4511
  await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS wave_fail_fast`.execute(db);
4501
4512
  await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS wave_max_parallel`.execute(db);
@@ -4504,8 +4515,8 @@ async function down$19(db) {
4504
4515
  //#endregion
4505
4516
  //#region src/db/migrations/042_dispatch_queue_patterns.ts
4506
4517
  var _042_dispatch_queue_patterns_exports = /* @__PURE__ */ __exportAll({
4507
- down: () => down$18,
4508
- up: () => up$18
4518
+ down: () => down$24,
4519
+ up: () => up$24
4509
4520
  });
4510
4521
  /**
4511
4522
  * Add pattern columns to dispatch_queue. Exact labels stay in runs_on_labels /
@@ -4521,7 +4532,7 @@ var _042_dispatch_queue_patterns_exports = /* @__PURE__ */ __exportAll({
4521
4532
  *
4522
4533
  * Idempotent: re-running on a DB that already has the columns is a no-op.
4523
4534
  */
4524
- async function colExists$7(db, table, name) {
4535
+ async function colExists$9(db, table, name) {
4525
4536
  return (await sql`
4526
4537
  SELECT EXISTS (
4527
4538
  SELECT 1 FROM information_schema.columns
@@ -4531,19 +4542,19 @@ async function colExists$7(db, table, name) {
4531
4542
  ) AS exists
4532
4543
  `.execute(db)).rows[0]?.exists ?? false;
4533
4544
  }
4534
- async function up$18(db) {
4535
- if (!await colExists$7(db, "dispatch_queue", "runs_on_patterns")) await sql`ALTER TABLE public.dispatch_queue ADD COLUMN runs_on_patterns jsonb NOT NULL DEFAULT '[]'::jsonb`.execute(db);
4536
- if (!await colExists$7(db, "dispatch_queue", "exclude_patterns")) await sql`ALTER TABLE public.dispatch_queue ADD COLUMN exclude_patterns jsonb NOT NULL DEFAULT '[]'::jsonb`.execute(db);
4545
+ async function up$24(db) {
4546
+ if (!await colExists$9(db, "dispatch_queue", "runs_on_patterns")) await sql`ALTER TABLE public.dispatch_queue ADD COLUMN runs_on_patterns jsonb NOT NULL DEFAULT '[]'::jsonb`.execute(db);
4547
+ if (!await colExists$9(db, "dispatch_queue", "exclude_patterns")) await sql`ALTER TABLE public.dispatch_queue ADD COLUMN exclude_patterns jsonb NOT NULL DEFAULT '[]'::jsonb`.execute(db);
4537
4548
  }
4538
- async function down$18(db) {
4549
+ async function down$24(db) {
4539
4550
  await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS exclude_patterns`.execute(db);
4540
4551
  await sql`ALTER TABLE public.dispatch_queue DROP COLUMN IF EXISTS runs_on_patterns`.execute(db);
4541
4552
  }
4542
4553
  //#endregion
4543
4554
  //#region src/db/migrations/043_rerouted_to_peer.ts
4544
4555
  var _043_rerouted_to_peer_exports = /* @__PURE__ */ __exportAll({
4545
- down: () => down$17,
4546
- up: () => up$17
4556
+ down: () => down$23,
4557
+ up: () => up$23
4547
4558
  });
4548
4559
  /**
4549
4560
  * Add a nullable `rerouted_to_peer text` marker to execution_jobs. Non-null
@@ -4554,7 +4565,7 @@ var _043_rerouted_to_peer_exports = /* @__PURE__ */ __exportAll({
4554
4565
  *
4555
4566
  * Idempotent: re-running on a DB that already has the column is a no-op.
4556
4567
  */
4557
- async function colExists$6(db, table, name) {
4568
+ async function colExists$8(db, table, name) {
4558
4569
  return (await sql`
4559
4570
  SELECT EXISTS (
4560
4571
  SELECT 1 FROM information_schema.columns
@@ -4564,17 +4575,17 @@ async function colExists$6(db, table, name) {
4564
4575
  ) AS exists
4565
4576
  `.execute(db)).rows[0]?.exists ?? false;
4566
4577
  }
4567
- async function up$17(db) {
4568
- if (!await colExists$6(db, "execution_jobs", "rerouted_to_peer")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN rerouted_to_peer text`.execute(db);
4578
+ async function up$23(db) {
4579
+ if (!await colExists$8(db, "execution_jobs", "rerouted_to_peer")) await sql`ALTER TABLE public.execution_jobs ADD COLUMN rerouted_to_peer text`.execute(db);
4569
4580
  }
4570
- async function down$17(db) {
4581
+ async function down$23(db) {
4571
4582
  await sql`ALTER TABLE public.execution_jobs DROP COLUMN IF EXISTS rerouted_to_peer`.execute(db);
4572
4583
  }
4573
4584
  //#endregion
4574
4585
  //#region src/db/migrations/044_check_mode.ts
4575
4586
  var _044_check_mode_exports = /* @__PURE__ */ __exportAll({
4576
- down: () => down$16,
4577
- up: () => up$16
4587
+ down: () => down$22,
4588
+ up: () => up$22
4578
4589
  });
4579
4590
  /**
4580
4591
  * Add idempotent check-mode columns:
@@ -4591,7 +4602,7 @@ var _044_check_mode_exports = /* @__PURE__ */ __exportAll({
4591
4602
  * All nullable. Idempotent: re-running on a DB that already has the columns is
4592
4603
  * a no-op.
4593
4604
  */
4594
- async function colExists$5(db, table, name) {
4605
+ async function colExists$7(db, table, name) {
4595
4606
  return (await sql`
4596
4607
  SELECT EXISTS (
4597
4608
  SELECT 1 FROM information_schema.columns
@@ -4601,13 +4612,13 @@ async function colExists$5(db, table, name) {
4601
4612
  ) AS exists
4602
4613
  `.execute(db)).rows[0]?.exists ?? false;
4603
4614
  }
4604
- async function up$16(db) {
4605
- if (!await colExists$5(db, "execution_runs", "check_mode")) await sql`ALTER TABLE public.execution_runs ADD COLUMN check_mode text`.execute(db);
4606
- if (!await colExists$5(db, "execution_steps", "check_outcome")) await sql`ALTER TABLE public.execution_steps ADD COLUMN check_outcome text`.execute(db);
4607
- if (!await colExists$5(db, "execution_steps", "drift_summary")) await sql`ALTER TABLE public.execution_steps ADD COLUMN drift_summary text`.execute(db);
4608
- if (!await colExists$5(db, "execution_steps", "drift")) await sql`ALTER TABLE public.execution_steps ADD COLUMN drift jsonb`.execute(db);
4615
+ async function up$22(db) {
4616
+ if (!await colExists$7(db, "execution_runs", "check_mode")) await sql`ALTER TABLE public.execution_runs ADD COLUMN check_mode text`.execute(db);
4617
+ if (!await colExists$7(db, "execution_steps", "check_outcome")) await sql`ALTER TABLE public.execution_steps ADD COLUMN check_outcome text`.execute(db);
4618
+ if (!await colExists$7(db, "execution_steps", "drift_summary")) await sql`ALTER TABLE public.execution_steps ADD COLUMN drift_summary text`.execute(db);
4619
+ if (!await colExists$7(db, "execution_steps", "drift")) await sql`ALTER TABLE public.execution_steps ADD COLUMN drift jsonb`.execute(db);
4609
4620
  }
4610
- async function down$16(db) {
4621
+ async function down$22(db) {
4611
4622
  await sql`ALTER TABLE public.execution_steps DROP COLUMN IF EXISTS drift`.execute(db);
4612
4623
  await sql`ALTER TABLE public.execution_steps DROP COLUMN IF EXISTS drift_summary`.execute(db);
4613
4624
  await sql`ALTER TABLE public.execution_steps DROP COLUMN IF EXISTS check_outcome`.execute(db);
@@ -4616,8 +4627,8 @@ async function down$16(db) {
4616
4627
  //#endregion
4617
4628
  //#region src/db/migrations/045_host_properties.ts
4618
4629
  var _045_host_properties_exports = /* @__PURE__ */ __exportAll({
4619
- down: () => down$15,
4620
- up: () => up$15
4630
+ down: () => down$21,
4631
+ up: () => up$21
4621
4632
  });
4622
4633
  /**
4623
4634
  * Add the typed host-vars dimension to the host roster:
@@ -4631,18 +4642,18 @@ var _045_host_properties_exports = /* @__PURE__ */ __exportAll({
4631
4642
  * Idempotent (`ADD COLUMN IF NOT EXISTS`): re-running on a DB that already has
4632
4643
  * the column is a no-op. Staging data is preserved (additive column).
4633
4644
  */
4634
- async function up$15(db) {
4645
+ async function up$21(db) {
4635
4646
  await sql`ALTER TABLE public.host_roster
4636
4647
  ADD COLUMN IF NOT EXISTS host_properties jsonb NOT NULL DEFAULT '{}'::jsonb`.execute(db);
4637
4648
  }
4638
- async function down$15(db) {
4649
+ async function down$21(db) {
4639
4650
  await sql`ALTER TABLE public.host_roster DROP COLUMN IF EXISTS host_properties`.execute(db);
4640
4651
  }
4641
4652
  //#endregion
4642
4653
  //#region src/db/migrations/046_join_token_consumed_by_instance.ts
4643
4654
  var _046_join_token_consumed_by_instance_exports = /* @__PURE__ */ __exportAll({
4644
- down: () => down$14,
4645
- up: () => up$14
4655
+ down: () => down$20,
4656
+ up: () => up$20
4646
4657
  });
4647
4658
  /**
4648
4659
  * Record the joining peer's instanceId at first consumption of a join token:
@@ -4657,18 +4668,18 @@ var _046_join_token_consumed_by_instance_exports = /* @__PURE__ */ __exportAll({
4657
4668
  * Idempotent (`ADD COLUMN IF NOT EXISTS`): re-running on a DB that already has
4658
4669
  * the column is a no-op. Staging data is preserved (additive nullable column).
4659
4670
  */
4660
- async function up$14(db) {
4671
+ async function up$20(db) {
4661
4672
  await sql`ALTER TABLE public.join_tokens
4662
4673
  ADD COLUMN IF NOT EXISTS consumed_by_instance text`.execute(db);
4663
4674
  }
4664
- async function down$14(db) {
4675
+ async function down$20(db) {
4665
4676
  await sql`ALTER TABLE public.join_tokens DROP COLUMN IF EXISTS consumed_by_instance`.execute(db);
4666
4677
  }
4667
4678
  //#endregion
4668
4679
  //#region src/db/migrations/047_needs_run_on.ts
4669
4680
  var _047_needs_run_on_exports = /* @__PURE__ */ __exportAll({
4670
- down: () => down$13,
4671
- up: () => up$13
4681
+ down: () => down$19,
4682
+ up: () => up$19
4672
4683
  });
4673
4684
  /**
4674
4685
  * Migrate `execution_job_needs` from the binary `if_failed` policy to a
@@ -4693,7 +4704,7 @@ const ALL_TERMINAL_JSON = JSON.stringify([
4693
4704
  "drift_dropped"
4694
4705
  ]);
4695
4706
  const SUCCESS_ONLY_JSON = JSON.stringify(["success"]);
4696
- async function colExists$4(db, table, name) {
4707
+ async function colExists$6(db, table, name) {
4697
4708
  return (await sql`
4698
4709
  SELECT EXISTS (
4699
4710
  SELECT 1 FROM information_schema.columns
@@ -4704,12 +4715,12 @@ async function colExists$4(db, table, name) {
4704
4715
  `.execute(db)).rows[0]?.exists ?? false;
4705
4716
  }
4706
4717
  const SUCCESS_ONLY_DEFAULT = `'${SUCCESS_ONLY_JSON}'`;
4707
- async function up$13(db) {
4708
- if (!await colExists$4(db, "execution_job_needs", "run_on")) await sql`
4718
+ async function up$19(db) {
4719
+ if (!await colExists$6(db, "execution_job_needs", "run_on")) await sql`
4709
4720
  ALTER TABLE public.execution_job_needs
4710
4721
  ADD COLUMN run_on text NOT NULL DEFAULT ${sql.raw(SUCCESS_ONLY_DEFAULT)}
4711
4722
  `.execute(db);
4712
- if (await colExists$4(db, "execution_job_needs", "if_failed")) {
4723
+ if (await colExists$6(db, "execution_job_needs", "if_failed")) {
4713
4724
  await sql`
4714
4725
  UPDATE public.execution_job_needs
4715
4726
  SET run_on = ${ALL_TERMINAL_JSON}
@@ -4723,12 +4734,12 @@ async function up$13(db) {
4723
4734
  await sql`ALTER TABLE public.execution_job_needs DROP COLUMN if_failed`.execute(db);
4724
4735
  }
4725
4736
  }
4726
- async function down$13(db) {
4727
- if (!await colExists$4(db, "execution_job_needs", "if_failed")) await sql`
4737
+ async function down$19(db) {
4738
+ if (!await colExists$6(db, "execution_job_needs", "if_failed")) await sql`
4728
4739
  ALTER TABLE public.execution_job_needs
4729
4740
  ADD COLUMN if_failed text NOT NULL DEFAULT 'skip'
4730
4741
  `.execute(db);
4731
- if (await colExists$4(db, "execution_job_needs", "run_on")) {
4742
+ if (await colExists$6(db, "execution_job_needs", "run_on")) {
4732
4743
  await sql`
4733
4744
  UPDATE public.execution_job_needs
4734
4745
  SET if_failed = CASE WHEN run_on = ${SUCCESS_ONLY_JSON} THEN 'skip' ELSE 'run' END
@@ -4739,8 +4750,8 @@ async function down$13(db) {
4739
4750
  //#endregion
4740
4751
  //#region src/db/migrations/048_host_reboot_pending.ts
4741
4752
  var _048_host_reboot_pending_exports = /* @__PURE__ */ __exportAll({
4742
- down: () => down$12,
4743
- up: () => up$12
4753
+ down: () => down$18,
4754
+ up: () => up$18
4744
4755
  });
4745
4756
  /**
4746
4757
  * Add `host_roster.reboot_pending_until timestamptz NULL` — the persisted
@@ -4757,18 +4768,18 @@ var _048_host_reboot_pending_exports = /* @__PURE__ */ __exportAll({
4757
4768
  * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
4758
4769
  * preserved.
4759
4770
  */
4760
- async function up$12(db) {
4771
+ async function up$18(db) {
4761
4772
  await sql`ALTER TABLE public.host_roster
4762
4773
  ADD COLUMN IF NOT EXISTS reboot_pending_until timestamptz`.execute(db);
4763
4774
  }
4764
- async function down$12(db) {
4775
+ async function down$18(db) {
4765
4776
  await sql`ALTER TABLE public.host_roster DROP COLUMN IF EXISTS reboot_pending_until`.execute(db);
4766
4777
  }
4767
4778
  //#endregion
4768
4779
  //#region src/db/migrations/049_held_runs_payload.ts
4769
4780
  var _049_held_runs_payload_exports = /* @__PURE__ */ __exportAll({
4770
- down: () => down$11,
4771
- up: () => up$11
4781
+ down: () => down$17,
4782
+ up: () => up$17
4772
4783
  });
4773
4784
  /**
4774
4785
  * Add `held_runs.payload jsonb NULL` — the drift payload captured when a
@@ -4780,18 +4791,18 @@ var _049_held_runs_payload_exports = /* @__PURE__ */ __exportAll({
4780
4791
  * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
4781
4792
  * preserved.
4782
4793
  */
4783
- async function up$11(db) {
4794
+ async function up$17(db) {
4784
4795
  await sql`ALTER TABLE public.held_runs
4785
4796
  ADD COLUMN IF NOT EXISTS payload jsonb`.execute(db);
4786
4797
  }
4787
- async function down$11(db) {
4798
+ async function down$17(db) {
4788
4799
  await sql`ALTER TABLE public.held_runs DROP COLUMN IF EXISTS payload`.execute(db);
4789
4800
  }
4790
4801
  //#endregion
4791
4802
  //#region src/db/migrations/050_sources_slug.ts
4792
4803
  var _050_sources_slug_exports = /* @__PURE__ */ __exportAll({
4793
- down: () => down$10,
4794
- up: () => up$10
4804
+ down: () => down$16,
4805
+ up: () => up$16
4795
4806
  });
4796
4807
  /**
4797
4808
  * Add `sources.slug TEXT NULL` — the GitHub App slug (the URL-safe identifier
@@ -4806,18 +4817,18 @@ var _050_sources_slug_exports = /* @__PURE__ */ __exportAll({
4806
4817
  * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
4807
4818
  * preserved.
4808
4819
  */
4809
- async function up$10(db) {
4820
+ async function up$16(db) {
4810
4821
  await sql`ALTER TABLE public.sources
4811
4822
  ADD COLUMN IF NOT EXISTS slug text`.execute(db);
4812
4823
  }
4813
- async function down$10(db) {
4824
+ async function down$16(db) {
4814
4825
  await sql`ALTER TABLE public.sources DROP COLUMN IF EXISTS slug`.execute(db);
4815
4826
  }
4816
4827
  //#endregion
4817
4828
  //#region src/db/migrations/051_binding_host_pattern.ts
4818
4829
  var _051_binding_host_pattern_exports = /* @__PURE__ */ __exportAll({
4819
- down: () => down$9,
4820
- up: () => up$9
4830
+ down: () => down$15,
4831
+ up: () => up$15
4821
4832
  });
4822
4833
  /**
4823
4834
  * Add a per-host dimension to environment secret bindings:
@@ -4833,7 +4844,7 @@ var _051_binding_host_pattern_exports = /* @__PURE__ */ __exportAll({
4833
4844
  * Idempotent: re-running on a DB that already has the column / index is a no-op.
4834
4845
  * Additive — staging data is preserved.
4835
4846
  */
4836
- async function up$9(db) {
4847
+ async function up$15(db) {
4837
4848
  await sql`ALTER TABLE public.environment_bindings
4838
4849
  ADD COLUMN IF NOT EXISTS host_pattern text NOT NULL DEFAULT '**'`.execute(db);
4839
4850
  await sql`ALTER TABLE public.environment_bindings
@@ -4841,7 +4852,7 @@ async function up$9(db) {
4841
4852
  await sql`CREATE UNIQUE INDEX IF NOT EXISTS environment_bindings_env_scope_host_uniq
4842
4853
  ON public.environment_bindings (environment_id, scope_pattern, host_pattern)`.execute(db);
4843
4854
  }
4844
- async function down$9(db) {
4855
+ async function down$15(db) {
4845
4856
  await sql`DROP INDEX IF EXISTS public.environment_bindings_env_scope_host_uniq`.execute(db);
4846
4857
  await sql`ALTER TABLE public.environment_bindings
4847
4858
  DROP COLUMN IF EXISTS host_pattern`.execute(db);
@@ -4851,8 +4862,8 @@ async function down$9(db) {
4851
4862
  //#endregion
4852
4863
  //#region src/db/migrations/052_host_reach_metadata.ts
4853
4864
  var _052_host_reach_metadata_exports = /* @__PURE__ */ __exportAll({
4854
- down: () => down$8,
4855
- up: () => up$8
4865
+ down: () => down$14,
4866
+ up: () => up$14
4856
4867
  });
4857
4868
  /**
4858
4869
  * Add pre-agent reach metadata to the host roster so a declared host (no agent
@@ -4869,14 +4880,14 @@ var _052_host_reach_metadata_exports = /* @__PURE__ */ __exportAll({
4869
4880
  * behaves exactly as before. Idempotent (`ADD COLUMN IF NOT EXISTS`); additive,
4870
4881
  * so staging data is preserved.
4871
4882
  */
4872
- async function up$8(db) {
4883
+ async function up$14(db) {
4873
4884
  await sql`ALTER TABLE public.host_roster
4874
4885
  ADD COLUMN IF NOT EXISTS address text,
4875
4886
  ADD COLUMN IF NOT EXISTS ssh_user text,
4876
4887
  ADD COLUMN IF NOT EXISTS ssh_port integer,
4877
4888
  ADD COLUMN IF NOT EXISTS ssh_key_secret text`.execute(db);
4878
4889
  }
4879
- async function down$8(db) {
4890
+ async function down$14(db) {
4880
4891
  await sql`ALTER TABLE public.host_roster
4881
4892
  DROP COLUMN IF EXISTS address,
4882
4893
  DROP COLUMN IF EXISTS ssh_user,
@@ -4886,8 +4897,8 @@ async function down$8(db) {
4886
4897
  //#endregion
4887
4898
  //#region src/db/migrations/053_agent_token_single_use.ts
4888
4899
  var _053_agent_token_single_use_exports = /* @__PURE__ */ __exportAll({
4889
- down: () => down$7,
4890
- up: () => up$7
4900
+ down: () => down$13,
4901
+ up: () => up$13
4891
4902
  });
4892
4903
  /**
4893
4904
  * Add a single-use marker to agent tokens for the bootstrap (init-runner)
@@ -4902,19 +4913,19 @@ var _053_agent_token_single_use_exports = /* @__PURE__ */ __exportAll({
4902
4913
  * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
4903
4914
  * preserved.
4904
4915
  */
4905
- async function up$7(db) {
4916
+ async function up$13(db) {
4906
4917
  await sql`ALTER TABLE public.agent_tokens
4907
4918
  ADD COLUMN IF NOT EXISTS consumed_at timestamptz`.execute(db);
4908
4919
  }
4909
- async function down$7(db) {
4920
+ async function down$13(db) {
4910
4921
  await sql`ALTER TABLE public.agent_tokens
4911
4922
  DROP COLUMN IF EXISTS consumed_at`.execute(db);
4912
4923
  }
4913
4924
  //#endregion
4914
4925
  //#region src/db/migrations/054_local_working_tree.ts
4915
4926
  var _054_local_working_tree_exports = /* @__PURE__ */ __exportAll({
4916
- down: () => down$6,
4917
- up: () => up$6
4927
+ down: () => down$12,
4928
+ up: () => up$12
4918
4929
  });
4919
4930
  /**
4920
4931
  * Mark runs that executed an uploaded local working tree (`kici run remote`):
@@ -4927,19 +4938,19 @@ var _054_local_working_tree_exports = /* @__PURE__ */ __exportAll({
4927
4938
  * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive with a default, so staging
4928
4939
  * data is preserved.
4929
4940
  */
4930
- async function up$6(db) {
4941
+ async function up$12(db) {
4931
4942
  await sql`ALTER TABLE public.execution_runs
4932
4943
  ADD COLUMN IF NOT EXISTS local_working_tree boolean NOT NULL DEFAULT false`.execute(db);
4933
4944
  }
4934
- async function down$6(db) {
4945
+ async function down$12(db) {
4935
4946
  await sql`ALTER TABLE public.execution_runs
4936
4947
  DROP COLUMN IF EXISTS local_working_tree`.execute(db);
4937
4948
  }
4938
4949
  //#endregion
4939
4950
  //#region src/db/migrations/055_agent_token_mandatory_labels.ts
4940
4951
  var _055_agent_token_mandatory_labels_exports = /* @__PURE__ */ __exportAll({
4941
- down: () => down$5,
4942
- up: () => up$5
4952
+ down: () => down$11,
4953
+ up: () => up$11
4943
4954
  });
4944
4955
  /**
4945
4956
  * Add a token-bound mandatory-label taint to agent tokens:
@@ -4955,19 +4966,19 @@ var _055_agent_token_mandatory_labels_exports = /* @__PURE__ */ __exportAll({
4955
4966
  * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
4956
4967
  * preserved.
4957
4968
  */
4958
- async function up$5(db) {
4969
+ async function up$11(db) {
4959
4970
  await sql`ALTER TABLE public.agent_tokens
4960
4971
  ADD COLUMN IF NOT EXISTS mandatory_labels text`.execute(db);
4961
4972
  }
4962
- async function down$5(db) {
4973
+ async function down$11(db) {
4963
4974
  await sql`ALTER TABLE public.agent_tokens
4964
4975
  DROP COLUMN IF EXISTS mandatory_labels`.execute(db);
4965
4976
  }
4966
4977
  //#endregion
4967
4978
  //#region src/db/migrations/056_execution_jobs_environments.ts
4968
4979
  var _056_execution_jobs_environments_exports = /* @__PURE__ */ __exportAll({
4969
- down: () => down$4,
4970
- up: () => up$4
4980
+ down: () => down$10,
4981
+ up: () => up$10
4971
4982
  });
4972
4983
  /**
4973
4984
  * Add a per-job bound deployment-environment list to `execution_jobs`:
@@ -4982,19 +4993,19 @@ var _056_execution_jobs_environments_exports = /* @__PURE__ */ __exportAll({
4982
4993
  * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
4983
4994
  * preserved.
4984
4995
  */
4985
- async function up$4(db) {
4996
+ async function up$10(db) {
4986
4997
  await sql`ALTER TABLE public.execution_jobs
4987
4998
  ADD COLUMN IF NOT EXISTS environments text`.execute(db);
4988
4999
  }
4989
- async function down$4(db) {
5000
+ async function down$10(db) {
4990
5001
  await sql`ALTER TABLE public.execution_jobs
4991
5002
  DROP COLUMN IF EXISTS environments`.execute(db);
4992
5003
  }
4993
5004
  //#endregion
4994
5005
  //#region src/db/migrations/057_step_concurrency.ts
4995
5006
  var _057_step_concurrency_exports = /* @__PURE__ */ __exportAll({
4996
- down: () => down$3,
4997
- up: () => up$3
5007
+ down: () => down$9,
5008
+ up: () => up$9
4998
5009
  });
4999
5010
  /**
5000
5011
  * Add parallel step-group concurrency columns to `execution_steps`:
@@ -5007,6 +5018,75 @@ var _057_step_concurrency_exports = /* @__PURE__ */ __exportAll({
5007
5018
  * Both nullable, no backfill. Idempotent: re-running on a DB that already has the
5008
5019
  * columns is a no-op.
5009
5020
  */
5021
+ async function colExists$5(db, table, name) {
5022
+ return (await sql`
5023
+ SELECT EXISTS (
5024
+ SELECT 1 FROM information_schema.columns
5025
+ WHERE table_schema = 'public'
5026
+ AND table_name = ${table}
5027
+ AND column_name = ${name}
5028
+ ) AS exists
5029
+ `.execute(db)).rows[0]?.exists ?? false;
5030
+ }
5031
+ async function up$9(db) {
5032
+ if (!await colExists$5(db, "execution_steps", "concurrency_kind")) await sql`ALTER TABLE public.execution_steps ADD COLUMN concurrency_kind text`.execute(db);
5033
+ if (!await colExists$5(db, "execution_steps", "group_id")) await sql`ALTER TABLE public.execution_steps ADD COLUMN group_id text`.execute(db);
5034
+ }
5035
+ async function down$9(db) {
5036
+ await sql`ALTER TABLE public.execution_steps DROP COLUMN IF EXISTS group_id`.execute(db);
5037
+ await sql`ALTER TABLE public.execution_steps DROP COLUMN IF EXISTS concurrency_kind`.execute(db);
5038
+ }
5039
+ //#endregion
5040
+ //#region src/db/migrations/058_access_log_agent_label.ts
5041
+ var _058_access_log_agent_label_exports = /* @__PURE__ */ __exportAll({
5042
+ down: () => down$8,
5043
+ up: () => up$8
5044
+ });
5045
+ /**
5046
+ * Add `access_log.agent_label text` — the human-set name of the agent that
5047
+ * performed the action, when the actor authenticated with an agent-kind PAT.
5048
+ *
5049
+ * Queryable column (not just baked into `actor_meta`) so an operator can filter
5050
+ * the access log by agent. NULL for ordinary human / API-key / system actors.
5051
+ * Nullable, no backfill. Idempotent.
5052
+ */
5053
+ async function colExists$4(db, table, name) {
5054
+ return (await sql`
5055
+ SELECT EXISTS (
5056
+ SELECT 1 FROM information_schema.columns
5057
+ WHERE table_schema = 'public'
5058
+ AND table_name = ${table}
5059
+ AND column_name = ${name}
5060
+ ) AS exists
5061
+ `.execute(db)).rows[0]?.exists ?? false;
5062
+ }
5063
+ async function up$8(db) {
5064
+ if (!await colExists$4(db, "access_log", "agent_label")) await sql`ALTER TABLE public.access_log ADD COLUMN agent_label text`.execute(db);
5065
+ }
5066
+ async function down$8(db) {
5067
+ await sql`ALTER TABLE public.access_log DROP COLUMN IF EXISTS agent_label`.execute(db);
5068
+ }
5069
+ //#endregion
5070
+ //#region src/db/migrations/059_attestation_verdict.ts
5071
+ var _059_attestation_verdict_exports = /* @__PURE__ */ __exportAll({
5072
+ down: () => down$7,
5073
+ up: () => up$7
5074
+ });
5075
+ /**
5076
+ * Add server-side verification verdict columns to `attestations`, plus the
5077
+ * lookup indexes the org-wide attestations browser queries against.
5078
+ *
5079
+ * The orchestrator verifies each provenance bundle at ingest and records the
5080
+ * verdict here so the dashboard list shows trustworthy badges with no per-row
5081
+ * bundle fetch:
5082
+ * - `verify_status` — one of `verified` / `failed` / `unverifiable` / `pending`
5083
+ * (mirrors `attestationVerifyStatusSchema.enum.*` in `@kici-dev/engine`).
5084
+ * `DEFAULT 'pending'` covers rows written before a verdict is computed.
5085
+ * - `verify_reason` — first failure code from `verifyKiciBundle` (nullable).
5086
+ * - `verified_at` — when the verdict was recorded (nullable).
5087
+ *
5088
+ * Idempotent: guarded on the `verify_status` column's existence.
5089
+ */
5010
5090
  async function colExists$3(db, table, name) {
5011
5091
  return (await sql`
5012
5092
  SELECT EXISTS (
@@ -5017,142 +5097,329 @@ async function colExists$3(db, table, name) {
5017
5097
  ) AS exists
5018
5098
  `.execute(db)).rows[0]?.exists ?? false;
5019
5099
  }
5100
+ async function up$7(db) {
5101
+ if (await colExists$3(db, "attestations", "verify_status")) return;
5102
+ await sql`
5103
+ ALTER TABLE public.attestations
5104
+ ADD COLUMN verify_status TEXT NOT NULL DEFAULT 'pending',
5105
+ ADD COLUMN verify_reason TEXT,
5106
+ ADD COLUMN verified_at TIMESTAMPTZ
5107
+ `.execute(db);
5108
+ await sql`CREATE INDEX idx_attestations_subject_digest ON public.attestations (subject_digest)`.execute(db);
5109
+ await sql`CREATE INDEX idx_attestations_subject_name ON public.attestations (subject_name)`.execute(db);
5110
+ await sql`CREATE INDEX idx_attestations_verify_status ON public.attestations (verify_status)`.execute(db);
5111
+ await sql`CREATE INDEX idx_attestations_created_at ON public.attestations (created_at)`.execute(db);
5112
+ }
5113
+ async function down$7(db) {
5114
+ await sql`DROP INDEX IF EXISTS idx_attestations_created_at`.execute(db);
5115
+ await sql`DROP INDEX IF EXISTS idx_attestations_verify_status`.execute(db);
5116
+ await sql`DROP INDEX IF EXISTS idx_attestations_subject_name`.execute(db);
5117
+ await sql`DROP INDEX IF EXISTS idx_attestations_subject_digest`.execute(db);
5118
+ await sql`
5119
+ ALTER TABLE public.attestations
5120
+ DROP COLUMN IF EXISTS verify_status,
5121
+ DROP COLUMN IF EXISTS verify_reason,
5122
+ DROP COLUMN IF EXISTS verified_at
5123
+ `.execute(db);
5124
+ }
5125
+ //#endregion
5126
+ //#region src/db/migrations/060_run_trigger_actor.ts
5127
+ var _060_run_trigger_actor_exports = /* @__PURE__ */ __exportAll({
5128
+ down: () => down$6,
5129
+ up: () => up$6
5130
+ });
5131
+ /**
5132
+ * Add the triggering-actor columns to `execution_runs`:
5133
+ *
5134
+ * - `trigger_actor_provider` — the origin provider of the actor identity
5135
+ * (`github` today; provider-generic so GitLab/Bitbucket extend later).
5136
+ * - `trigger_actor_username` — the provider login of the person who triggered
5137
+ * the run (e.g. the GitHub pusher / PR author).
5138
+ * - `trigger_actor_user_id` — the immutable provider user id (mirrors
5139
+ * `identity_links.provider_user_id`), preferred over the mutable username
5140
+ * when resolving the actor to a KiCI user.
5141
+ *
5142
+ * Captured for ALL event types (push/tag/PR/comment), unlike the PR-only
5143
+ * `contributor_username`. Independently useful for a dashboard "triggered by
5144
+ * @x" and audit, beyond actor notifications. All nullable, no backfill,
5145
+ * idempotent.
5146
+ */
5147
+ async function colExists$2(db, table, name) {
5148
+ return (await sql`
5149
+ SELECT EXISTS (
5150
+ SELECT 1 FROM information_schema.columns
5151
+ WHERE table_schema = 'public'
5152
+ AND table_name = ${table}
5153
+ AND column_name = ${name}
5154
+ ) AS exists
5155
+ `.execute(db)).rows[0]?.exists ?? false;
5156
+ }
5157
+ async function up$6(db) {
5158
+ if (!await colExists$2(db, "execution_runs", "trigger_actor_provider")) await sql`ALTER TABLE public.execution_runs ADD COLUMN trigger_actor_provider text`.execute(db);
5159
+ if (!await colExists$2(db, "execution_runs", "trigger_actor_username")) await sql`ALTER TABLE public.execution_runs ADD COLUMN trigger_actor_username text`.execute(db);
5160
+ if (!await colExists$2(db, "execution_runs", "trigger_actor_user_id")) await sql`ALTER TABLE public.execution_runs ADD COLUMN trigger_actor_user_id text`.execute(db);
5161
+ }
5162
+ async function down$6(db) {
5163
+ await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS trigger_actor_provider`.execute(db);
5164
+ await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS trigger_actor_username`.execute(db);
5165
+ await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS trigger_actor_user_id`.execute(db);
5166
+ }
5167
+ //#endregion
5168
+ //#region src/db/migrations/061_execution_runs_environment_id.ts
5169
+ var _061_execution_runs_environment_id_exports = /* @__PURE__ */ __exportAll({
5170
+ down: () => down$5,
5171
+ up: () => up$5
5172
+ });
5173
+ /**
5174
+ * Add `environment_id` to `execution_runs` so environment run-history can be
5175
+ * queried by the matched environment's id.
5176
+ *
5177
+ * The `environment` column stores the job-declared environment name. For a
5178
+ * glob environment that is the concrete instance string (e.g. `review/PR-123`),
5179
+ * which never equals the environment row's label, so a name-keyed history query
5180
+ * always misses. Recording the matched environment id lets the History tab key
5181
+ * off a stable id instead. The `environment` name column is untouched (it
5182
+ * doubles as the concurrency-group key and the concrete-instance label).
5183
+ *
5184
+ * `ON DELETE SET NULL`: deleting an environment must not delete its run rows.
5185
+ *
5186
+ * Backfill: `execution_runs` has no `org_id`, so a name match is only safe when
5187
+ * exactly one fixed environment carries that name across the whole table. That
5188
+ * backfills 100% of fixed-env history on a single-tenant orchestrator and the
5189
+ * unambiguous majority on a shared one, never mis-attributing across tenants.
5190
+ * Glob-named rows stay null (they never appeared in history before — that is
5191
+ * the bug being fixed — so nothing regresses).
5192
+ *
5193
+ * Idempotent: guarded on the `environment_id` column's existence; the index and
5194
+ * backfill use `IF NOT EXISTS` / `WHERE environment_id IS NULL` so re-running is
5195
+ * a no-op.
5196
+ */
5197
+ async function colExists$1(db, table, name) {
5198
+ return (await sql`
5199
+ SELECT EXISTS (
5200
+ SELECT 1 FROM information_schema.columns
5201
+ WHERE table_schema = 'public'
5202
+ AND table_name = ${table}
5203
+ AND column_name = ${name}
5204
+ ) AS exists
5205
+ `.execute(db)).rows[0]?.exists ?? false;
5206
+ }
5207
+ async function up$5(db) {
5208
+ if (!await colExists$1(db, "execution_runs", "environment_id")) await sql`
5209
+ ALTER TABLE public.execution_runs
5210
+ ADD COLUMN environment_id uuid
5211
+ REFERENCES public.environments(id) ON DELETE SET NULL
5212
+ `.execute(db);
5213
+ await sql`
5214
+ CREATE INDEX IF NOT EXISTS idx_execution_runs_environment_id
5215
+ ON public.execution_runs (environment_id)
5216
+ `.execute(db);
5217
+ await sql`
5218
+ UPDATE public.execution_runs er
5219
+ SET environment_id = e.id
5220
+ FROM public.environments e
5221
+ WHERE er.environment_id IS NULL
5222
+ AND er.environment IS NOT NULL
5223
+ AND e.type = 'fixed'
5224
+ AND e.name = er.environment
5225
+ AND (SELECT count(*) FROM public.environments e2
5226
+ WHERE e2.type = 'fixed' AND e2.name = er.environment) = 1
5227
+ `.execute(db);
5228
+ }
5229
+ async function down$5(db) {
5230
+ await sql`DROP INDEX IF EXISTS idx_execution_runs_environment_id`.execute(db);
5231
+ await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS environment_id`.execute(db);
5232
+ }
5233
+ //#endregion
5234
+ //#region src/db/migrations/062_execution_runs_agent_label.ts
5235
+ var _062_execution_runs_agent_label_exports = /* @__PURE__ */ __exportAll({
5236
+ down: () => down$4,
5237
+ up: () => up$4
5238
+ });
5239
+ /**
5240
+ * Add the agent-provenance columns to `execution_runs`:
5241
+ *
5242
+ * - `triggered_by_agent_label` — the human-set agent name when the run was
5243
+ * triggered through an agent-kind credential (user agent PAT or org agent
5244
+ * API key) via the developer MCP server.
5245
+ * - `cancelled_by_agent_label` — likewise for a cancellation driven through an
5246
+ * agent credential.
5247
+ *
5248
+ * The label is provenance only (the credential still inherits its principal's
5249
+ * permissions). NULL for ordinary human / webhook / system-driven runs.
5250
+ * Nullable, no backfill, idempotent.
5251
+ */
5252
+ async function colExists(db, table, name) {
5253
+ return (await sql`
5254
+ SELECT EXISTS (
5255
+ SELECT 1 FROM information_schema.columns
5256
+ WHERE table_schema = 'public'
5257
+ AND table_name = ${table}
5258
+ AND column_name = ${name}
5259
+ ) AS exists
5260
+ `.execute(db)).rows[0]?.exists ?? false;
5261
+ }
5262
+ async function up$4(db) {
5263
+ if (!await colExists(db, "execution_runs", "triggered_by_agent_label")) await sql`ALTER TABLE public.execution_runs ADD COLUMN triggered_by_agent_label text`.execute(db);
5264
+ if (!await colExists(db, "execution_runs", "cancelled_by_agent_label")) await sql`ALTER TABLE public.execution_runs ADD COLUMN cancelled_by_agent_label text`.execute(db);
5265
+ }
5266
+ async function down$4(db) {
5267
+ await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS triggered_by_agent_label`.execute(db);
5268
+ await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS cancelled_by_agent_label`.execute(db);
5269
+ }
5270
+ //#endregion
5271
+ //#region src/db/migrations/063_access_log_agent_label_index.ts
5272
+ var _063_access_log_agent_label_index_exports = /* @__PURE__ */ __exportAll({
5273
+ down: () => down$3,
5274
+ up: () => up$3
5275
+ });
5276
+ /**
5277
+ * Index `access_log.agent_label` (column added by migration 058) so the
5278
+ * operator-facing agent filter (`kici-admin access-log list --agent-label`,
5279
+ * the dashboard "agent name" filter) does an indexed exact-match lookup
5280
+ * instead of a scan. Idempotent.
5281
+ */
5020
5282
  async function up$3(db) {
5021
- if (!await colExists$3(db, "execution_steps", "concurrency_kind")) await sql`ALTER TABLE public.execution_steps ADD COLUMN concurrency_kind text`.execute(db);
5022
- if (!await colExists$3(db, "execution_steps", "group_id")) await sql`ALTER TABLE public.execution_steps ADD COLUMN group_id text`.execute(db);
5283
+ await sql`CREATE INDEX IF NOT EXISTS access_log_agent_label_idx ON public.access_log (agent_label)`.execute(db);
5023
5284
  }
5024
5285
  async function down$3(db) {
5025
- await sql`ALTER TABLE public.execution_steps DROP COLUMN IF EXISTS group_id`.execute(db);
5026
- await sql`ALTER TABLE public.execution_steps DROP COLUMN IF EXISTS concurrency_kind`.execute(db);
5286
+ await sql`DROP INDEX IF EXISTS access_log_agent_label_idx`.execute(db);
5027
5287
  }
5028
5288
  //#endregion
5029
- //#region src/db/migrations/058_access_log_agent_label.ts
5030
- var _058_access_log_agent_label_exports = /* @__PURE__ */ __exportAll({
5289
+ //#region src/db/migrations/064_execution_jobs_skipped_environments.ts
5290
+ var _064_execution_jobs_skipped_environments_exports = /* @__PURE__ */ __exportAll({
5031
5291
  down: () => down$2,
5032
5292
  up: () => up$2
5033
5293
  });
5034
5294
  /**
5035
- * Add `access_log.agent_label text` — the human-set name of the agent that
5036
- * performed the action, when the actor authenticated with an agent-kind PAT.
5295
+ * Add the test-run skipped-environment columns to `execution_jobs`:
5037
5296
  *
5038
- * Queryable column (not just baked into `actor_meta`) so an operator can filter
5039
- * the access log by agent. NULL for ordinary human / API-key / system actors.
5040
- * Nullable, no backfill. Idempotent.
5297
+ * - `execution_jobs.skipped_environments text NULL` a JSON-encoded `string[]`
5298
+ * of the bound environment names dropped on a test/local run because they
5299
+ * disallow local execution (`allowLocalExecution=false`) or are unconfigured.
5300
+ * NULL = nothing skipped.
5301
+ * - `execution_jobs.env_warning text NULL` — the user-visible warning naming the
5302
+ * skipped environments, surfaced on the dashboard run view. NULL = no warning.
5303
+ *
5304
+ * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
5305
+ * preserved.
5041
5306
  */
5042
- async function colExists$2(db, table, name) {
5043
- return (await sql`
5044
- SELECT EXISTS (
5045
- SELECT 1 FROM information_schema.columns
5046
- WHERE table_schema = 'public'
5047
- AND table_name = ${table}
5048
- AND column_name = ${name}
5049
- ) AS exists
5050
- `.execute(db)).rows[0]?.exists ?? false;
5051
- }
5052
5307
  async function up$2(db) {
5053
- if (!await colExists$2(db, "access_log", "agent_label")) await sql`ALTER TABLE public.access_log ADD COLUMN agent_label text`.execute(db);
5308
+ await sql`ALTER TABLE public.execution_jobs
5309
+ ADD COLUMN IF NOT EXISTS skipped_environments text`.execute(db);
5310
+ await sql`ALTER TABLE public.execution_jobs
5311
+ ADD COLUMN IF NOT EXISTS env_warning text`.execute(db);
5054
5312
  }
5055
5313
  async function down$2(db) {
5056
- await sql`ALTER TABLE public.access_log DROP COLUMN IF EXISTS agent_label`.execute(db);
5314
+ await sql`ALTER TABLE public.execution_jobs
5315
+ DROP COLUMN IF EXISTS skipped_environments`.execute(db);
5316
+ await sql`ALTER TABLE public.execution_jobs
5317
+ DROP COLUMN IF EXISTS env_warning`.execute(db);
5057
5318
  }
5058
5319
  //#endregion
5059
- //#region src/db/migrations/059_attestation_verdict.ts
5060
- var _059_attestation_verdict_exports = /* @__PURE__ */ __exportAll({
5320
+ //#region src/db/migrations/065_pending_attestations.ts
5321
+ var _065_pending_attestations_exports = /* @__PURE__ */ __exportAll({
5061
5322
  down: () => down$1,
5062
5323
  up: () => up$1
5063
5324
  });
5064
5325
  /**
5065
- * Add server-side verification verdict columns to `attestations`, plus the
5066
- * lookup indexes the org-wide attestations browser queries against.
5326
+ * Add the `pending_attestations` deferred-attestation outbox and an idempotency
5327
+ * unique index on `attestations`.
5067
5328
  *
5068
- * The orchestrator verifies each provenance bundle at ingest and records the
5069
- * verdict here so the dashboard list shows trustworthy badges with no per-row
5070
- * bundle fetch:
5071
- * - `verify_status` one of `verified` / `failed` / `unverifiable` / `pending`
5072
- * (mirrors `attestationVerifyStatusSchema.enum.*` in `@kici-dev/engine`).
5073
- * `DEFAULT 'pending'` covers rows written before a verdict is computed.
5074
- * - `verify_reason` first failure code from `verifyKiciBundle` (nullable).
5075
- * - `verified_at` — when the verdict was recorded (nullable).
5329
+ * When a build's provenance mint fails transiently, the agent freezes and
5330
+ * DSSE-signs the statement at build time; the orchestrator records the frozen
5331
+ * envelope here instead of failing the job. A Raft-leader-only retrier later
5332
+ * mints the deferred token, attaches it, uploads the bundle, and records one
5333
+ * `attestations` row — deleting the pending row. The unique index on
5334
+ * `attestations (run_id, job_id, subject_digest)` makes that fulfilment
5335
+ * idempotent across a cluster (ON CONFLICT DO NOTHING).
5076
5336
  *
5077
- * Idempotent: guarded on the `verify_status` column's existence.
5337
+ * `origin_kind` holds the non-`live` `AttestationOrigin` values
5338
+ * (`deferred` / `offline-backfill`); the `created_at` default anchors the true
5339
+ * build time so temporal honesty survives a later mint.
5340
+ *
5341
+ * Idempotent: guarded on table existence.
5078
5342
  */
5079
- async function colExists$1(db, table, name) {
5080
- return (await sql`
5343
+ async function up$1(db) {
5344
+ if ((await sql`
5081
5345
  SELECT EXISTS (
5082
- SELECT 1 FROM information_schema.columns
5083
- WHERE table_schema = 'public'
5084
- AND table_name = ${table}
5085
- AND column_name = ${name}
5346
+ SELECT 1 FROM information_schema.tables
5347
+ WHERE table_schema = 'public' AND table_name = 'pending_attestations'
5086
5348
  ) AS exists
5087
- `.execute(db)).rows[0]?.exists ?? false;
5088
- }
5089
- async function up$1(db) {
5090
- if (await colExists$1(db, "attestations", "verify_status")) return;
5349
+ `.execute(db)).rows[0]?.exists) return;
5091
5350
  await sql`
5092
- ALTER TABLE public.attestations
5093
- ADD COLUMN verify_status TEXT NOT NULL DEFAULT 'pending',
5094
- ADD COLUMN verify_reason TEXT,
5095
- ADD COLUMN verified_at TIMESTAMPTZ
5351
+ CREATE TABLE public.pending_attestations (
5352
+ id TEXT PRIMARY KEY,
5353
+ run_id TEXT NOT NULL,
5354
+ job_id TEXT NOT NULL,
5355
+ subject_name TEXT NOT NULL,
5356
+ subject_digest TEXT NOT NULL,
5357
+ audience TEXT NOT NULL,
5358
+ dsse_envelope JSONB NOT NULL,
5359
+ public_key JSONB NOT NULL,
5360
+ media_type TEXT NOT NULL,
5361
+ statement_hash TEXT NOT NULL,
5362
+ origin_kind TEXT NOT NULL,
5363
+ attempt_count INTEGER NOT NULL DEFAULT 0,
5364
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
5365
+ last_attempt_at TIMESTAMPTZ,
5366
+ last_error TEXT
5367
+ )
5096
5368
  `.execute(db);
5097
- await sql`CREATE INDEX idx_attestations_subject_digest ON public.attestations (subject_digest)`.execute(db);
5098
- await sql`CREATE INDEX idx_attestations_subject_name ON public.attestations (subject_name)`.execute(db);
5099
- await sql`CREATE INDEX idx_attestations_verify_status ON public.attestations (verify_status)`.execute(db);
5100
- await sql`CREATE INDEX idx_attestations_created_at ON public.attestations (created_at)`.execute(db);
5101
- }
5102
- async function down$1(db) {
5103
- await sql`DROP INDEX IF EXISTS idx_attestations_created_at`.execute(db);
5104
- await sql`DROP INDEX IF EXISTS idx_attestations_verify_status`.execute(db);
5105
- await sql`DROP INDEX IF EXISTS idx_attestations_subject_name`.execute(db);
5106
- await sql`DROP INDEX IF EXISTS idx_attestations_subject_digest`.execute(db);
5107
5369
  await sql`
5108
- ALTER TABLE public.attestations
5109
- DROP COLUMN IF EXISTS verify_status,
5110
- DROP COLUMN IF EXISTS verify_reason,
5111
- DROP COLUMN IF EXISTS verified_at
5370
+ CREATE UNIQUE INDEX uq_pending_attestations_run_job_subject
5371
+ ON public.pending_attestations (run_id, job_id, subject_digest)
5372
+ `.execute(db);
5373
+ await sql`
5374
+ CREATE INDEX idx_pending_attestations_created_at
5375
+ ON public.pending_attestations (created_at)
5376
+ `.execute(db);
5377
+ await sql`
5378
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_attestations_run_job_subject
5379
+ ON public.attestations (run_id, job_id, subject_digest)
5112
5380
  `.execute(db);
5113
5381
  }
5382
+ async function down$1(db) {
5383
+ await sql`DROP INDEX IF EXISTS uq_attestations_run_job_subject`.execute(db);
5384
+ await sql`DROP TABLE IF EXISTS public.pending_attestations`.execute(db);
5385
+ }
5114
5386
  //#endregion
5115
- //#region src/db/migrations/060_run_trigger_actor.ts
5116
- var _060_run_trigger_actor_exports = /* @__PURE__ */ __exportAll({
5387
+ //#region src/db/migrations/066_pending_attestations_rejected.ts
5388
+ var _066_pending_attestations_rejected_exports = /* @__PURE__ */ __exportAll({
5117
5389
  down: () => down,
5118
5390
  up: () => up
5119
5391
  });
5120
5392
  /**
5121
- * Add the triggering-actor columns to `execution_runs`:
5122
- *
5123
- * - `trigger_actor_provider` the origin provider of the actor identity
5124
- * (`github` today; provider-generic so GitLab/Bitbucket extend later).
5125
- * - `trigger_actor_username` the provider login of the person who triggered
5126
- * the run (e.g. the GitHub pusher / PR author).
5127
- * - `trigger_actor_user_id` — the immutable provider user id (mirrors
5128
- * `identity_links.provider_user_id`), preferred over the mutable username
5129
- * when resolving the actor to a KiCI user.
5393
+ * Add `rejected_at` to `pending_attestations`: the terminal-rejection marker for
5394
+ * a deferred attestation the Platform definitively cannot mint (run/job absent).
5395
+ * NULL while the row is still pending a later mint; set once, the row is skipped
5396
+ * by the retrier and drops out of the pending-attestations gauge. Re-armed via
5397
+ * `kici-admin attestations retry --include-rejected`.
5130
5398
  *
5131
- * Captured for ALL event types (push/tag/PR/comment), unlike the PR-only
5132
- * `contributor_username`. Independently useful for a dashboard "triggered by
5133
- * @x" and audit, beyond actor notifications. All nullable, no backfill,
5134
- * idempotent.
5399
+ * Idempotent: guarded on column existence.
5135
5400
  */
5136
- async function colExists(db, table, name) {
5401
+ async function up(db) {
5402
+ if (await columnExists(db, "pending_attestations", "rejected_at")) return;
5403
+ await sql`
5404
+ ALTER TABLE public.pending_attestations
5405
+ ADD COLUMN rejected_at TIMESTAMPTZ
5406
+ `.execute(db);
5407
+ }
5408
+ async function down(db) {
5409
+ if (!await columnExists(db, "pending_attestations", "rejected_at")) return;
5410
+ await sql`
5411
+ ALTER TABLE public.pending_attestations
5412
+ DROP COLUMN rejected_at
5413
+ `.execute(db);
5414
+ }
5415
+ async function columnExists(db, table, column) {
5137
5416
  return (await sql`
5138
5417
  SELECT EXISTS (
5139
5418
  SELECT 1 FROM information_schema.columns
5140
- WHERE table_schema = 'public'
5141
- AND table_name = ${table}
5142
- AND column_name = ${name}
5419
+ WHERE table_schema = 'public' AND table_name = ${table} AND column_name = ${column}
5143
5420
  ) AS exists
5144
5421
  `.execute(db)).rows[0]?.exists ?? false;
5145
5422
  }
5146
- async function up(db) {
5147
- if (!await colExists(db, "execution_runs", "trigger_actor_provider")) await sql`ALTER TABLE public.execution_runs ADD COLUMN trigger_actor_provider text`.execute(db);
5148
- if (!await colExists(db, "execution_runs", "trigger_actor_username")) await sql`ALTER TABLE public.execution_runs ADD COLUMN trigger_actor_username text`.execute(db);
5149
- if (!await colExists(db, "execution_runs", "trigger_actor_user_id")) await sql`ALTER TABLE public.execution_runs ADD COLUMN trigger_actor_user_id text`.execute(db);
5150
- }
5151
- async function down(db) {
5152
- await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS trigger_actor_provider`.execute(db);
5153
- await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS trigger_actor_username`.execute(db);
5154
- await sql`ALTER TABLE public.execution_runs DROP COLUMN IF EXISTS trigger_actor_user_id`.execute(db);
5155
- }
5156
5423
  //#endregion
5157
5424
  //#region src/db/migration-provider.ts
5158
5425
  function createMigrationProvider() {
@@ -5217,7 +5484,13 @@ function createMigrationProvider() {
5217
5484
  "057_step_concurrency": _057_step_concurrency_exports,
5218
5485
  "058_access_log_agent_label": _058_access_log_agent_label_exports,
5219
5486
  "059_attestation_verdict": _059_attestation_verdict_exports,
5220
- "060_run_trigger_actor": _060_run_trigger_actor_exports
5487
+ "060_run_trigger_actor": _060_run_trigger_actor_exports,
5488
+ "061_execution_runs_environment_id": _061_execution_runs_environment_id_exports,
5489
+ "062_execution_runs_agent_label": _062_execution_runs_agent_label_exports,
5490
+ "063_access_log_agent_label_index": _063_access_log_agent_label_index_exports,
5491
+ "064_execution_jobs_skipped_environments": _064_execution_jobs_skipped_environments_exports,
5492
+ "065_pending_attestations": _065_pending_attestations_exports,
5493
+ "066_pending_attestations_rejected": _066_pending_attestations_rejected_exports
5221
5494
  };
5222
5495
  } };
5223
5496
  }
@@ -5229,7 +5502,7 @@ function createMigrationProvider() {
5229
5502
  * Uses pg_advisory_lock to ensure only one orchestrator instance runs
5230
5503
  * migrations at a time, preventing conflicts in multi-instance deployments.
5231
5504
  */
5232
- const logger$12 = createLogger({ prefix: "migrate" });
5505
+ const logger$13 = createLogger({ prefix: "migrate" });
5233
5506
  const ADVISORY_LOCK_KEY = 543210001;
5234
5507
  /**
5235
5508
  * Run all pending migrations with an advisory lock for concurrency safety.
@@ -5241,21 +5514,21 @@ const ADVISORY_LOCK_KEY = 543210001;
5241
5514
  async function runMigrations(opts) {
5242
5515
  const client = await opts.pool.connect();
5243
5516
  try {
5244
- logger$12.info("Acquiring migration lock...");
5517
+ logger$13.info("Acquiring migration lock...");
5245
5518
  await client.query("SELECT pg_advisory_lock($1)", [ADVISORY_LOCK_KEY]);
5246
- logger$12.info("Migration lock acquired");
5519
+ logger$13.info("Migration lock acquired");
5247
5520
  const migrator = new Migrator({
5248
5521
  db: opts.db,
5249
5522
  provider: createMigrationProvider()
5250
5523
  });
5251
5524
  const start = Date.now();
5252
5525
  const { results, error } = await migrator.migrateToLatest();
5253
- for (const result of results ?? []) if (result.status === "Success") logger$12.info(`${result.migrationName} ... OK (${Date.now() - start}ms)`);
5254
- else if (result.status === "Error") logger$12.error(`${result.migrationName} ... FAILED`);
5526
+ for (const result of results ?? []) if (result.status === "Success") logger$13.info(`${result.migrationName} ... OK (${Date.now() - start}ms)`);
5527
+ else if (result.status === "Error") logger$13.error(`${result.migrationName} ... FAILED`);
5255
5528
  if (error) throw error;
5256
5529
  const applied = (results ?? []).filter((r) => r.status === "Success");
5257
- if (applied.length === 0) logger$12.info("Database schema is up to date");
5258
- else logger$12.info(`Applied ${applied.length} migration(s)`);
5530
+ if (applied.length === 0) logger$13.info("Database schema is up to date");
5531
+ else logger$13.info(`Applied ${applied.length} migration(s)`);
5259
5532
  const hash = await computeMigrationsHash(createMigrationProvider());
5260
5533
  await storeMigrationContentHash(opts.pool, hash);
5261
5534
  return results ?? [];
@@ -5284,7 +5557,7 @@ async function runMigrations(opts) {
5284
5557
  * as `kici-admin peer create-token` — open a pool from KICI_DATABASE_URL /
5285
5558
  * DATABASE_URL / --database-url.
5286
5559
  */
5287
- function resolveDatabaseUrl$3(explicit) {
5560
+ function resolveDatabaseUrl$4(explicit) {
5288
5561
  const url = explicit ?? process.env.KICI_DATABASE_URL;
5289
5562
  if (!url) throw new Error("Database URL required. Pass --database-url or set KICI_DATABASE_URL.");
5290
5563
  return url;
@@ -5331,7 +5604,7 @@ function registerDbCommands(program, getClient) {
5331
5604
  });
5332
5605
  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) => {
5333
5606
  try {
5334
- const url = resolveDatabaseUrl$3(opts.databaseUrl);
5607
+ const url = resolveDatabaseUrl$4(opts.databaseUrl);
5335
5608
  const { dbName } = parseDatabaseUrl(url);
5336
5609
  if (!opts.yes) {
5337
5610
  if (!await confirmInteractive$2(`About to DROP + RECREATE database "${dbName}". Type "${dbName}" to confirm: `, dbName)) {
@@ -5362,7 +5635,7 @@ function registerDbCommands(program, getClient) {
5362
5635
  });
5363
5636
  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) => {
5364
5637
  try {
5365
- const baseUrl = resolveDatabaseUrl$3(opts.databaseUrl);
5638
+ const baseUrl = resolveDatabaseUrl$4(opts.databaseUrl);
5366
5639
  const url = new URL(baseUrl);
5367
5640
  url.pathname = `/${name}`;
5368
5641
  const targetUrl = url.toString();
@@ -5381,7 +5654,7 @@ function registerDbCommands(program, getClient) {
5381
5654
  });
5382
5655
  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) => {
5383
5656
  try {
5384
- const { adminUrl } = parseDatabaseUrl(resolveDatabaseUrl$3(opts.databaseUrl));
5657
+ const { adminUrl } = parseDatabaseUrl(resolveDatabaseUrl$4(opts.databaseUrl));
5385
5658
  logInvocation(`create-role ${opts.user}`, adminUrl);
5386
5659
  const outcome = await createDbRole(adminUrl, {
5387
5660
  username: opts.user,
@@ -5396,7 +5669,7 @@ function registerDbCommands(program, getClient) {
5396
5669
  });
5397
5670
  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) => {
5398
5671
  try {
5399
- const url = resolveDatabaseUrl$3(opts.databaseUrl);
5672
+ const url = resolveDatabaseUrl$4(opts.databaseUrl);
5400
5673
  const { dbName } = parseDatabaseUrl(url);
5401
5674
  logInvocation(`create-readonly-user ${opts.user}`, url);
5402
5675
  const outcome = await createReadOnlyDbUser(url, {
@@ -5411,7 +5684,7 @@ function registerDbCommands(program, getClient) {
5411
5684
  });
5412
5685
  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) => {
5413
5686
  try {
5414
- const pool = createPool(resolveDatabaseUrl$3(opts.databaseUrl));
5687
+ const pool = createPool(resolveDatabaseUrl$4(opts.databaseUrl));
5415
5688
  try {
5416
5689
  const status = await isSchemaCurrent(pool, createMigrationProvider());
5417
5690
  if (opts.json) process.stdout.write(JSON.stringify(status) + "\n");
@@ -5428,7 +5701,7 @@ function registerDbCommands(program, getClient) {
5428
5701
  });
5429
5702
  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) => {
5430
5703
  try {
5431
- const url = resolveDatabaseUrl$3(opts.databaseUrl);
5704
+ const url = resolveDatabaseUrl$4(opts.databaseUrl);
5432
5705
  const { dbName } = parseDatabaseUrl(url);
5433
5706
  const pool = createPool(url);
5434
5707
  try {
@@ -5458,7 +5731,7 @@ function registerDbCommands(program, getClient) {
5458
5731
  });
5459
5732
  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) => {
5460
5733
  try {
5461
- const url = resolveDatabaseUrl$3(opts.databaseUrl);
5734
+ const url = resolveDatabaseUrl$4(opts.databaseUrl);
5462
5735
  const { dbName } = parseDatabaseUrl(url);
5463
5736
  logInvocation(`reindex ${dbName} (${opts.reason})`, url);
5464
5737
  const pool = createPool(url);
@@ -5475,7 +5748,7 @@ function registerDbCommands(program, getClient) {
5475
5748
  });
5476
5749
  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) => {
5477
5750
  try {
5478
- const url = resolveDatabaseUrl$3(opts.databaseUrl);
5751
+ const url = resolveDatabaseUrl$4(opts.databaseUrl);
5479
5752
  const { dbName } = parseDatabaseUrl(url);
5480
5753
  logInvocation(`refresh-collation-version ${dbName} (${opts.reason})`, url);
5481
5754
  const pool = createPool(url);
@@ -6302,7 +6575,7 @@ function safeParse(value) {
6302
6575
  */
6303
6576
  function webhookNoteReason(note) {
6304
6577
  switch (note) {
6305
- case "github-ingress-platform-only": return "this orchestrator runs in independent mode";
6578
+ case "no-public-url": return "KICI_WEBHOOK_PUBLIC_URL is not set on the orchestrator";
6306
6579
  case "platform-no-public-url": return "the Platform has no public webhook URL configured";
6307
6580
  case "platform-unavailable": return "could not reach the Platform";
6308
6581
  default: return "webhook URL could not be determined";
@@ -6311,7 +6584,7 @@ function webhookNoteReason(note) {
6311
6584
  /** Actionable next-step hint paired with {@link webhookNoteReason}. */
6312
6585
  function webhookNoteHint(note) {
6313
6586
  switch (note) {
6314
- case "github-ingress-platform-only": return "GitHub App ingress is served by the KiCI Platform; find the URL in the dashboard under Sources.";
6587
+ case "no-public-url": return "Set KICI_WEBHOOK_PUBLIC_URL to the orchestrator's public base, then re-run; paste the printed URL into your GitHub App or repo webhook.";
6315
6588
  case "platform-unavailable": return "Retry once the orchestrator reconnects, or find the URL in the dashboard under Sources.";
6316
6589
  default: return "Find the webhook URL in the dashboard under Sources.";
6317
6590
  }
@@ -6913,7 +7186,7 @@ async function getRemoteSource(db, orgId) {
6913
7186
  //#endregion
6914
7187
  //#region src/cli/commands/remote-source.ts
6915
7188
  init_client();
6916
- function resolveDatabaseUrl$2(explicit) {
7189
+ function resolveDatabaseUrl$3(explicit) {
6917
7190
  const url = explicit ?? process.env.KICI_DATABASE_URL;
6918
7191
  if (!url) throw new Error("Database URL required. Pass --database-url or set KICI_DATABASE_URL.");
6919
7192
  return url;
@@ -6922,7 +7195,7 @@ function registerRemoteSourceCommands(program) {
6922
7195
  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) => {
6923
7196
  let url;
6924
7197
  try {
6925
- url = resolveDatabaseUrl$2(opts.databaseUrl);
7198
+ url = resolveDatabaseUrl$3(opts.databaseUrl);
6926
7199
  } catch (err) {
6927
7200
  console.error(`Error: ${toErrorMessage(err)}`);
6928
7201
  process.exit(1);
@@ -7459,6 +7732,16 @@ function registerRunsCommands(program, getClient) {
7459
7732
  }
7460
7733
  });
7461
7734
  }
7735
+ createLogger({ prefix: "cluster-identity" });
7736
+ /**
7737
+ * Build the S3 key for the cluster identity sentinel under an optional prefix.
7738
+ * Strips any trailing slash from the prefix so callers can pass either form.
7739
+ */
7740
+ function clusterSentinelKey(prefix) {
7741
+ if (!prefix) return ".kici-cluster-id";
7742
+ const trimmed = prefix.replace(/\/+$/, "");
7743
+ return trimmed.length === 0 ? ".kici-cluster-id" : `${trimmed}/.kici-cluster-id`;
7744
+ }
7462
7745
  //#endregion
7463
7746
  //#region src/config.ts
7464
7747
  /**
@@ -7593,6 +7876,15 @@ const baseSchema = z.object({
7593
7876
  * `event.attempts <= N`. Ignored unless `KICI_TEST_MODE=1`.
7594
7877
  */
7595
7878
  testEventFailFirstN: z.string().optional(),
7879
+ /**
7880
+ * **Test-only.** When set (and `KICI_TEST_MODE=1`), the orchestrator forces
7881
+ * the *initial* agent provenance mint to fail transiently (defer) for any job
7882
+ * whose requested OIDC `audience` equals this value, so an E2E can exercise
7883
+ * the deferred-attestation retry + per-run serve path with a REAL run. The
7884
+ * retrier's re-mint uses a different call site and is unaffected. Ignored
7885
+ * unless `KICI_TEST_MODE=1`. Production deployments leave this unset.
7886
+ */
7887
+ testMintDeferAudience: z.string().optional(),
7596
7888
  eventLogMaxPayloadBytes: z.coerce.number().default(5 * 1024 * 1024),
7597
7889
  logLevel: z.enum([
7598
7890
  "debug",
@@ -7782,6 +8074,7 @@ const envDef = defineEnv({
7782
8074
  eventRouterRetryScanIntervalMs: "KICI_EVENT_ROUTER_RETRY_SCAN_INTERVAL_MS",
7783
8075
  testMode: "KICI_TEST_MODE",
7784
8076
  testEventFailFirstN: "KICI_TEST_EVENT_FAIL_FIRST_N",
8077
+ testMintDeferAudience: "KICI_TEST_MINT_DEFER_AUDIENCE",
7785
8078
  eventLogMaxPayloadBytes: "KICI_EVENT_LOG_MAX_PAYLOAD_BYTES",
7786
8079
  logLevel: "KICI_LOG_LEVEL",
7787
8080
  nodeEnv: "NODE_ENV",
@@ -8508,11 +8801,18 @@ function createProvenanceTrustRoot(opts = {}) {
8508
8801
  /**
8509
8802
  * Compute the verification verdict for a stored provenance bundle at ingest.
8510
8803
  *
8804
+ * Reads the bundle, selects the signing-key set by the identity token's own
8805
+ * `kid` (so a kid-miss triggers the trust root's single refetch — a token
8806
+ * minted with a freshly-rotated key still verifies against a briefly-stale
8807
+ * cached JWKS), then verifies offline.
8808
+ *
8511
8809
  * Fail-closed: a missing trust root, unfetchable JWKS, missing storage, or an
8512
8810
  * unreadable bundle all yield `unverifiable` (never silently `verified`). A
8513
- * bundle that verifies false yields `failed` with the first failure code. Any
8514
- * thrown error is caught and recorded as `unverifiable` — verification never
8515
- * fails the upload.
8811
+ * signing key that is absent from the published JWKS even after the refetch
8812
+ * yields `unverifiable` (`signing_key_not_published`) "couldn't verify", not
8813
+ * "proved bad". A bundle that verifies false yields `failed` with the first
8814
+ * failure code. Any thrown error is caught and recorded as `unverifiable` —
8815
+ * verification never fails the upload.
8516
8816
  */
8517
8817
  async function computeAttestationVerdict(opts) {
8518
8818
  const { trustRoot, storage, storageKey } = opts;
@@ -8529,20 +8829,33 @@ async function computeAttestationVerdict(opts) {
8529
8829
  verifyReason: "no_storage",
8530
8830
  verifiedAt: null
8531
8831
  };
8532
- const jwks = await trustRoot.getJwks();
8832
+ const raw = await storage.get(storageKey);
8833
+ if (!raw) return {
8834
+ verifyStatus: status.unverifiable,
8835
+ verifyReason: "bundle_unreadable",
8836
+ verifiedAt: null
8837
+ };
8838
+ const bundle = JSON.parse(raw.toString("utf8"));
8839
+ const idToken = bundle?.verificationMaterial?.identityToken;
8840
+ let kid;
8841
+ if (typeof idToken === "string") try {
8842
+ kid = decodeProtectedHeader(idToken).kid;
8843
+ } catch {
8844
+ kid = void 0;
8845
+ }
8846
+ const jwks = await trustRoot.getJwks(kid);
8533
8847
  if (!jwks) return {
8534
8848
  verifyStatus: status.unverifiable,
8535
8849
  verifyReason: "jwks_fetch_failed",
8536
8850
  verifiedAt: null
8537
8851
  };
8538
- const raw = await storage.get(storageKey);
8539
- if (!raw) return {
8852
+ if (kid && !jwks.keys.some((k) => k.kid === kid)) return {
8540
8853
  verifyStatus: status.unverifiable,
8541
- verifyReason: "bundle_unreadable",
8854
+ verifyReason: "signing_key_not_published",
8542
8855
  verifiedAt: null
8543
8856
  };
8544
8857
  const result = await verifyKiciBundle({
8545
- bundle: JSON.parse(raw.toString("utf8")),
8858
+ bundle,
8546
8859
  jwks,
8547
8860
  expectedIssuer: issuer
8548
8861
  });
@@ -8607,6 +8920,58 @@ async function reverifyAttestations(db, trustRoot, storage, opts) {
8607
8920
  };
8608
8921
  }
8609
8922
  //#endregion
8923
+ //#region src/cli/commands/attestations-retry.ts
8924
+ /**
8925
+ * Post the retry request through the injected transport (the admin API POST)
8926
+ * and return the counts. Validates that the scope is unambiguous. When
8927
+ * `includeRejected` is set, previously terminally-rejected rows are re-armed
8928
+ * and re-attempted in the same drain.
8929
+ */
8930
+ async function runAttestationRetry(post, opts) {
8931
+ const base = opts.includeRejected ? { includeRejected: true } : {};
8932
+ if (!opts.runId && !opts.allPending) return post(base);
8933
+ return post(opts.runId ? {
8934
+ ...base,
8935
+ runId: opts.runId
8936
+ } : base);
8937
+ }
8938
+ //#endregion
8939
+ //#region src/cli/commands/attestations-list.ts
8940
+ /**
8941
+ * Map the injected DB read into the CLI envelope. `limit` / `runId` / `jobId`
8942
+ * are honored by the injected `read` fn (the real one is a Kysely query); this
8943
+ * runner owns only the row→envelope projection, so it stays pure and testable.
8944
+ */
8945
+ async function runAttestationList(read, opts) {
8946
+ return { attestations: (await read(opts)).map((r) => ({
8947
+ id: r.id,
8948
+ runId: r.run_id,
8949
+ jobId: r.job_id,
8950
+ subjectName: r.subject_name,
8951
+ verifyStatus: r.verify_status,
8952
+ createdAt: r.created_at.toISOString()
8953
+ })) };
8954
+ }
8955
+ /**
8956
+ * Build the real DB read: a Kysely query over `attestations`, newest first,
8957
+ * with optional run/job filters and a row cap.
8958
+ */
8959
+ function readAttestations(db) {
8960
+ return async ({ limit, runId, jobId }) => {
8961
+ let query = db.selectFrom("attestations").select([
8962
+ "id",
8963
+ "run_id",
8964
+ "job_id",
8965
+ "subject_name",
8966
+ "verify_status",
8967
+ "created_at"
8968
+ ]).orderBy("created_at", "desc").limit(limit);
8969
+ if (runId) query = query.where("run_id", "=", runId);
8970
+ if (jobId) query = query.where("job_id", "=", jobId);
8971
+ return query.execute();
8972
+ };
8973
+ }
8974
+ //#endregion
8610
8975
  //#region src/cli/commands/attestations.ts
8611
8976
  /**
8612
8977
  * `kici-admin attestations` — orchestrator-DB-direct provenance maintenance.
@@ -8620,7 +8985,7 @@ async function reverifyAttestations(db, trustRoot, storage, opts) {
8620
8985
  */
8621
8986
  init_client();
8622
8987
  const logger$8 = createLogger({ prefix: "kici-admin-attestations" });
8623
- function resolveDatabaseUrl$1(explicit) {
8988
+ function resolveDatabaseUrl$2(explicit) {
8624
8989
  const url = explicit ?? process.env.KICI_DATABASE_URL;
8625
8990
  if (!url) throw new Error("Database URL required. Pass --database-url or set KICI_DATABASE_URL.");
8626
8991
  return url;
@@ -8658,11 +9023,34 @@ async function confirmInteractive$1(prompt) {
8658
9023
  rl.close();
8659
9024
  }
8660
9025
  }
8661
- function registerAttestationsCommands(program) {
8662
- program.command("attestations").description("Provenance-attestation maintenance (orchestrator DB)").command("reverify").description("Recompute stored attestation verdicts (verify-at-ingest backfill)").option("--all", "Re-evaluate every attestation (default: only pending/unverifiable)").option("--database-url <url>", "Orchestrator DB URL (else KICI_DATABASE_URL)").option("--yes", "Skip the --all confirmation prompt").action(async (opts) => {
9026
+ /** Render attestation rows as a compact human table (stderr note when empty). */
9027
+ function printAttestationTable(rows) {
9028
+ if (rows.length === 0) {
9029
+ process.stderr.write("No attestations found.\n");
9030
+ return;
9031
+ }
9032
+ for (const r of rows) console.log(`${r.id} ${r.verifyStatus.padEnd(12)} run=${r.runId} job=${r.jobId} ${r.subjectName} ${r.createdAt}`);
9033
+ }
9034
+ function registerAttestationsCommands(program, getClient) {
9035
+ const attestations = program.command("attestations").description("Provenance-attestation maintenance (orchestrator DB)");
9036
+ attestations.command("retry").description("Mint deferred attestations now (drains the pending-attestations outbox)").option("--run-id <id>", "Scope to a single run (else drains every pending attestation)").option("--all-pending", "Drain every pending attestation (default when --run-id is absent)").option("--include-rejected", "Also re-attempt rows previously marked terminally rejected (re-arm)").action(async (opts) => {
9037
+ try {
9038
+ const result = await runAttestationRetry((body) => getClient().post("/api/v1/admin/attestations/retry", body), {
9039
+ runId: opts.runId,
9040
+ allPending: opts.allPending,
9041
+ includeRejected: opts.includeRejected
9042
+ });
9043
+ logger$8.info("attestations retry complete", result);
9044
+ process.stderr.write(`Minted ${result.minted} deferred attestation(s); ${result.stillPending} still pending; ${result.rejected} rejected.\n`);
9045
+ } catch (err) {
9046
+ console.error(`Error: ${toErrorMessage(err)}`);
9047
+ process.exit(1);
9048
+ }
9049
+ });
9050
+ attestations.command("reverify").description("Recompute stored attestation verdicts (verify-at-ingest backfill)").option("--all", "Re-evaluate every attestation (default: only pending/unverifiable)").option("--database-url <url>", "Orchestrator DB URL (else KICI_DATABASE_URL)").option("--yes", "Skip the --all confirmation prompt").action(async (opts) => {
8663
9051
  try {
8664
9052
  const config = loadConfig();
8665
- const url = resolveDatabaseUrl$1(opts.databaseUrl ?? (config.databaseUrl || void 0));
9053
+ const url = resolveDatabaseUrl$2(opts.databaseUrl ?? (config.databaseUrl || void 0));
8666
9054
  if (opts.all && !opts.yes) {
8667
9055
  if (!await confirmInteractive$1("Re-evaluate the verdict of EVERY attestation (including already-verified)? [y/N] ")) {
8668
9056
  console.error("Aborted.");
@@ -8689,6 +9077,29 @@ function registerAttestationsCommands(program) {
8689
9077
  process.exit(1);
8690
9078
  }
8691
9079
  });
9080
+ attestations.command("list").description("List provenance attestations from the orchestrator DB (newest first)").option("--run-id <id>", "Filter to a single run").option("--job-id <id>", "Filter to a single job").option("--limit <n>", "Max results (default 20, max 100)", "20").option("--json", "Emit the raw JSON envelope instead of a table").option("--database-url <url>", "Orchestrator DB URL (else KICI_DATABASE_URL)").action(async (opts) => {
9081
+ try {
9082
+ const url = resolveDatabaseUrl$2(opts.databaseUrl);
9083
+ const limit = Math.min(Math.max(Number.parseInt(opts.limit, 10) || 20, 1), 100);
9084
+ const pool = createPool(url);
9085
+ const db = createDb$1(pool);
9086
+ try {
9087
+ const result = await runAttestationList(readAttestations(db), {
9088
+ limit,
9089
+ runId: opts.runId,
9090
+ jobId: opts.jobId
9091
+ });
9092
+ if (opts.json) console.log(JSON.stringify(result, null, 2));
9093
+ else printAttestationTable(result.attestations);
9094
+ } finally {
9095
+ await db.destroy();
9096
+ await pool.end().catch(() => {});
9097
+ }
9098
+ } catch (err) {
9099
+ console.error(`Error: ${toErrorMessage(err)}`);
9100
+ process.exit(1);
9101
+ }
9102
+ });
8692
9103
  }
8693
9104
  //#endregion
8694
9105
  //#region src/cli/commands/event-log.ts
@@ -8817,7 +9228,7 @@ function renderTable(headers, rows) {
8817
9228
  }
8818
9229
  function registerAccessLogCommands(program, getClient) {
8819
9230
  const accessLog = program.command("access-log").description("Inspect the read / admin-mutation access log");
8820
- accessLog.command("list").description("List access-log rows (dogfooded via /api/v1/admin/access-log)").option("--org-id <orgId>", "Filter by org/tenant ID").option("--actor-type <t>", "Filter by actor type (user|api_key|service_account|platform_operator|system)").option("--actor-id <id>", "Filter by actor id (zsub, keyId, service_account id, ...)").option("--action <action>", "Filter by dotted action (e.g. run.detail.read, run.cancel)").option("--source <s>", "Filter by source (platform_proxy|admin_http|admin_cli)").option("--outcome <o>", "Filter by outcome (allowed|denied|error)").option("--target-type <t>", "Filter by target type (run|step|event_log|secret_scope|...)").option("--target-id <id>", "Filter by target id").option("--from <ts>", "ISO timestamp lower bound (inclusive)").option("--to <ts>", "ISO timestamp upper bound (exclusive)").option("--q <text>", "Filter by substring of error_message (trigram-indexed full-text search)").option("--limit <n>", "Max results (default 50, max 200)", "50").option("--cursor <c>", "Opaque cursor from a previous nextCursor").option("--json", "Emit raw JSON instead of a table").action(async (opts) => {
9231
+ accessLog.command("list").description("List access-log rows (dogfooded via /api/v1/admin/access-log)").option("--org-id <orgId>", "Filter by org/tenant ID").option("--actor-type <t>", "Filter by actor type (user|api_key|service_account|platform_operator|system)").option("--actor-id <id>", "Filter by actor id (zsub, keyId, service_account id, ...)").option("--action <action>", "Filter by dotted action (e.g. run.detail.read, run.cancel)").option("--source <s>", "Filter by source (platform_proxy|admin_http|admin_cli)").option("--outcome <o>", "Filter by outcome (allowed|denied|error)").option("--target-type <t>", "Filter by target type (run|step|event_log|secret_scope|...)").option("--target-id <id>", "Filter by target id").option("--from <ts>", "ISO timestamp lower bound (inclusive)").option("--to <ts>", "ISO timestamp upper bound (exclusive)").option("--q <text>", "Filter by substring of error_message (trigram-indexed full-text search)").option("--agent-label <label>", "Filter by exact agent label").option("--agent-only", "Only agent-attributed rows").option("--limit <n>", "Max results (default 50, max 200)", "50").option("--cursor <c>", "Opaque cursor from a previous nextCursor").option("--json", "Emit raw JSON instead of a table").action(async (opts) => {
8821
9232
  try {
8822
9233
  const response = await getClient().listAccessLog({
8823
9234
  orgId: opts.orgId,
@@ -8831,6 +9242,8 @@ function registerAccessLogCommands(program, getClient) {
8831
9242
  from: opts.from,
8832
9243
  to: opts.to,
8833
9244
  q: opts.q,
9245
+ agentLabel: opts.agentLabel,
9246
+ agentOnly: opts.agentOnly,
8834
9247
  limit: parseInt(opts.limit, 10),
8835
9248
  cursor: opts.cursor
8836
9249
  });
@@ -8845,6 +9258,7 @@ function registerAccessLogCommands(program, getClient) {
8845
9258
  const headers = [
8846
9259
  "created_at",
8847
9260
  "actor",
9261
+ "agent",
8848
9262
  "action",
8849
9263
  "source",
8850
9264
  "outcome",
@@ -8854,6 +9268,7 @@ function registerAccessLogCommands(program, getClient) {
8854
9268
  const rows = response.items.map((r) => [
8855
9269
  r.createdAt,
8856
9270
  `${r.actorType}:${r.actorId}`,
9271
+ r.agentLabel ?? "",
8857
9272
  r.action,
8858
9273
  r.source,
8859
9274
  r.outcome,
@@ -8880,6 +9295,7 @@ function registerAccessLogCommands(program, getClient) {
8880
9295
  console.log(` Org: ${response.orgId ?? "(none)"}`);
8881
9296
  if (response.routingKey) console.log(` Routing key: ${response.routingKey}`);
8882
9297
  console.log(` Actor: ${response.actorType}:${response.actorId}`);
9298
+ console.log(` Agent label: ${response.agentLabel ?? "-"}`);
8883
9299
  if (response.actorMeta) console.log(` Actor meta: ${JSON.stringify(response.actorMeta)}`);
8884
9300
  console.log(` Action: ${response.action}`);
8885
9301
  console.log(` Source: ${response.source}`);
@@ -10642,61 +11058,61 @@ var init_registry = __esmMin((() => {
10642
11058
  rolldown: {
10643
11059
  "linux-x64": {
10644
11060
  name: "rolldown",
10645
- version: "1.1.1",
11061
+ version: "1.1.3",
10646
11062
  platform: "linux",
10647
11063
  arch: "x64",
10648
- url: "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.1.tgz",
10649
- sha256: "55bf455e4d6c5df19636c40b434718891a574214bbf09a50e2e7b21b9f640d56",
11064
+ url: "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz",
11065
+ sha256: "7759bc6da0140899e447345cb2467f5a4fb2c73d9cbac94a341b7a69e67f5cff",
10650
11066
  extractPath: "package/",
10651
11067
  archiveType: "tar.gz"
10652
11068
  },
10653
11069
  "linux-arm64": {
10654
11070
  name: "rolldown",
10655
- version: "1.1.1",
11071
+ version: "1.1.3",
10656
11072
  platform: "linux",
10657
11073
  arch: "arm64",
10658
- url: "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.1.tgz",
10659
- sha256: "af458cfd2ce237a5161bdfdcef960e710c394b966537e1d1374840fb7fc78308",
11074
+ url: "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz",
11075
+ sha256: "a1ab47becdde8e8d8f9176a0bdc938e6e76bf52c668ffb5287ea8b515048bb99",
10660
11076
  extractPath: "package/",
10661
11077
  archiveType: "tar.gz"
10662
11078
  },
10663
11079
  "darwin-x64": {
10664
11080
  name: "rolldown",
10665
- version: "1.1.1",
11081
+ version: "1.1.3",
10666
11082
  platform: "darwin",
10667
11083
  arch: "x64",
10668
- url: "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.1.tgz",
10669
- sha256: "31bd12457aa41646dcee674d7fa73ec2c4625ef5d34d0ef6c6823cad811de3e2",
11084
+ url: "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz",
11085
+ sha256: "eabb0140217ba45fc0d1de1a7cd59ba475a9c0cd2c207c9364fb58328478b878",
10670
11086
  extractPath: "package/",
10671
11087
  archiveType: "tar.gz"
10672
11088
  },
10673
11089
  "darwin-arm64": {
10674
11090
  name: "rolldown",
10675
- version: "1.1.1",
11091
+ version: "1.1.3",
10676
11092
  platform: "darwin",
10677
11093
  arch: "arm64",
10678
- url: "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.1.tgz",
10679
- sha256: "8399e7a87640ffe485ba16289f6c3a7ab69b25d09b43e5896441372bcf6a02f9",
11094
+ url: "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz",
11095
+ sha256: "ddc152faf8328a44ee846c99cf945dbac45541ce2a6785f6b0707ad1c0ffe1f3",
10680
11096
  extractPath: "package/",
10681
11097
  archiveType: "tar.gz"
10682
11098
  },
10683
11099
  "win32-x64": {
10684
11100
  name: "rolldown",
10685
- version: "1.1.1",
11101
+ version: "1.1.3",
10686
11102
  platform: "win32",
10687
11103
  arch: "x64",
10688
- url: "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.1.tgz",
10689
- sha256: "2b2c34d530db0e2cf15cdbe328822d5d385b19ee3b5aba55771587027b9afa17",
11104
+ url: "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz",
11105
+ sha256: "adc567f5a423c6e0db071272f7021b1a6d118368b7608eff22b8e61c4c7a6745",
10690
11106
  extractPath: "package/",
10691
11107
  archiveType: "tar.gz"
10692
11108
  },
10693
11109
  "win32-arm64": {
10694
11110
  name: "rolldown",
10695
- version: "1.1.1",
11111
+ version: "1.1.3",
10696
11112
  platform: "win32",
10697
11113
  arch: "arm64",
10698
- url: "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.1.tgz",
10699
- sha256: "d7431cb75d0bed42dd8794b89c3d73ac7129a36465ac020519726f2a9dca1d7c",
11114
+ url: "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz",
11115
+ sha256: "9d055f11eb04e60821013b30ce7d011491bcddf0169f59c7254499bb537b7da8",
10700
11116
  extractPath: "package/",
10701
11117
  archiveType: "tar.gz"
10702
11118
  }
@@ -14472,6 +14888,191 @@ function registerClusterNameCommands(program, getClient) {
14472
14888
  });
14473
14889
  }
14474
14890
  //#endregion
14891
+ //#region src/cluster/reconcile-identity.ts
14892
+ /**
14893
+ * Reconcile the orchestrator's cluster identity between the DB
14894
+ * (`cluster_meta.cluster_id`) and the durable S3 sentinel
14895
+ * (`<prefix>/.kici-cluster-id`).
14896
+ *
14897
+ * Used by `kici-admin cluster reconcile-identity` and the staging deploy's
14898
+ * pre-orchestrator-start self-heal step. Talks DB + S3 directly (never the
14899
+ * orchestrator HTTP admin API) so it works while the orchestrator process is
14900
+ * down — which is exactly when a "Cluster identity mismatch" boot failure needs
14901
+ * fixing. Default direction restores the DB FROM the durable sentinel (the
14902
+ * cross-restart / peer anchor); `sentinel-from-db` reverses it.
14903
+ */
14904
+ async function makeS3Client(s3) {
14905
+ const { S3Client } = await import("@aws-sdk/client-s3");
14906
+ return new S3Client({
14907
+ region: s3.region ?? "us-east-1",
14908
+ credentials: {
14909
+ accessKeyId: s3.accessKeyId,
14910
+ secretAccessKey: s3.secretAccessKey
14911
+ },
14912
+ ...s3.endpoint ? { endpoint: s3.endpoint } : {},
14913
+ ...s3.forcePathStyle ? { forcePathStyle: true } : {}
14914
+ });
14915
+ }
14916
+ async function readClusterIdFromDb(databaseUrl) {
14917
+ const pool = createPool(databaseUrl);
14918
+ try {
14919
+ return (await pool.query(`SELECT value FROM cluster_meta WHERE key = 'cluster_id'`)).rows[0]?.value ?? null;
14920
+ } finally {
14921
+ await pool.end();
14922
+ }
14923
+ }
14924
+ async function writeClusterIdToDb(databaseUrl, clusterId) {
14925
+ const pool = createPool(databaseUrl);
14926
+ try {
14927
+ await pool.query(`UPDATE cluster_meta SET value = $1 WHERE key = 'cluster_id'`, [clusterId]);
14928
+ } finally {
14929
+ await pool.end();
14930
+ }
14931
+ }
14932
+ async function readSentinel(s3) {
14933
+ const { GetObjectCommand } = await import("@aws-sdk/client-s3");
14934
+ const client = await makeS3Client(s3);
14935
+ const key = clusterSentinelKey(s3.prefix);
14936
+ try {
14937
+ return (await (await client.send(new GetObjectCommand({
14938
+ Bucket: s3.bucket,
14939
+ Key: key
14940
+ }))).Body?.transformToString())?.trim() ?? null;
14941
+ } catch (err) {
14942
+ const e = err;
14943
+ if (e?.name === "NoSuchKey" || e?.$metadata?.httpStatusCode === 404) return null;
14944
+ throw err;
14945
+ }
14946
+ }
14947
+ async function writeSentinel(s3, clusterId) {
14948
+ const { PutObjectCommand } = await import("@aws-sdk/client-s3");
14949
+ await (await makeS3Client(s3)).send(new PutObjectCommand({
14950
+ Bucket: s3.bucket,
14951
+ Key: clusterSentinelKey(s3.prefix),
14952
+ Body: clusterId,
14953
+ ContentType: "text/plain"
14954
+ }));
14955
+ }
14956
+ /**
14957
+ * Indirection object for the DB + S3 reads/writes, so the unit test can stub
14958
+ * the I/O without an ESM partial-mock dance. `buildReconcileStep` calls every
14959
+ * reader/writer through this object; the test overrides its members directly.
14960
+ */
14961
+ const reconcileIo = {
14962
+ readClusterIdFromDb,
14963
+ writeClusterIdToDb,
14964
+ readSentinel,
14965
+ writeSentinel
14966
+ };
14967
+ /**
14968
+ * Build the idempotent step that reconciles the cluster identity in the given
14969
+ * direction. `check()` returns drift when the two sides disagree (or null when
14970
+ * in sync); `apply()` performs the single write that brings them into
14971
+ * agreement. Run it through `runIdempotentStep` (`@kici-dev/core/idempotency`).
14972
+ */
14973
+ function buildReconcileStep(deps) {
14974
+ const { databaseUrl, s3, direction } = deps;
14975
+ return {
14976
+ name: `cluster reconcile-identity (${direction})`,
14977
+ check: async () => {
14978
+ const dbClusterId = await reconcileIo.readClusterIdFromDb(databaseUrl);
14979
+ const sentinelClusterId = await reconcileIo.readSentinel(s3);
14980
+ if (dbClusterId === null) throw new Error("cluster_meta.cluster_id is missing — run orchestrator migrations first.");
14981
+ if (direction === "db-from-sentinel") {
14982
+ if (sentinelClusterId === null) throw new Error("No S3 sentinel to restore the DB from. Use --adopt-db to write the sentinel from the DB instead.");
14983
+ if (dbClusterId === sentinelClusterId) return null;
14984
+ } else if (sentinelClusterId === dbClusterId) return null;
14985
+ return {
14986
+ dbClusterId,
14987
+ sentinelClusterId,
14988
+ direction
14989
+ };
14990
+ },
14991
+ summarize: (drift) => drift.direction === "db-from-sentinel" ? `Rewrite DB cluster_id ${drift.dbClusterId} -> ${drift.sentinelClusterId} (from S3 sentinel s3://${s3.bucket}/${clusterSentinelKey(s3.prefix)}).` : `Rewrite S3 sentinel ${drift.sentinelClusterId ?? "(absent)"} -> ${drift.dbClusterId} (from DB cluster_meta.cluster_id).`,
14992
+ apply: async (drift) => {
14993
+ if (drift.direction === "db-from-sentinel") await reconcileIo.writeClusterIdToDb(databaseUrl, drift.sentinelClusterId);
14994
+ else await reconcileIo.writeSentinel(s3, drift.dbClusterId);
14995
+ }
14996
+ };
14997
+ }
14998
+ //#endregion
14999
+ //#region src/cli/commands/cluster.ts
15000
+ /**
15001
+ * Cluster recovery commands for kici-admin.
15002
+ *
15003
+ * Subcommand namespace: `kici-admin cluster reconcile-identity`.
15004
+ *
15005
+ * Reconciles the orchestrator's cluster identity between the DB
15006
+ * (`cluster_meta.cluster_id`) and the durable S3 sentinel
15007
+ * (`<prefix>/.kici-cluster-id`) when they diverge and the orchestrator refuses
15008
+ * to start with a "Cluster identity mismatch" error.
15009
+ *
15010
+ * DB + S3 direct (NOT the orchestrator HTTP admin API), so it works while the
15011
+ * orchestrator process is crash-looping — which is exactly when it is needed.
15012
+ * Default direction restores the DB FROM the durable sentinel (the
15013
+ * cross-restart / peer anchor); --adopt-db reverses it.
15014
+ */
15015
+ function resolveDatabaseUrl$1(explicit) {
15016
+ const url = explicit ?? process.env.KICI_DATABASE_URL;
15017
+ if (!url) throw new Error("Database URL required. Pass --database-url or set KICI_DATABASE_URL.");
15018
+ return url;
15019
+ }
15020
+ function resolveS3(opts) {
15021
+ const bucket = opts.bucket ?? process.env.KICI_STORAGE_BUCKET;
15022
+ const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
15023
+ const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
15024
+ if (!bucket) throw new Error("S3 bucket required. Pass --bucket or set KICI_STORAGE_BUCKET.");
15025
+ if (!accessKeyId || !secretAccessKey) throw new Error("AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set for the S3 sentinel.");
15026
+ return {
15027
+ bucket,
15028
+ prefix: opts.prefix ?? process.env.KICI_STORAGE_PREFIX ?? "",
15029
+ region: opts.region ?? process.env.KICI_STORAGE_REGION,
15030
+ endpoint: opts.endpoint ?? process.env.KICI_STORAGE_ENDPOINT,
15031
+ forcePathStyle: opts.forcePathStyle ?? process.env.KICI_STORAGE_FORCE_PATH_STYLE === "true",
15032
+ accessKeyId,
15033
+ secretAccessKey
15034
+ };
15035
+ }
15036
+ async function cliConfirm(message) {
15037
+ if (!process.stdin.isTTY) return false;
15038
+ const rl = createInterface({
15039
+ input: process.stdin,
15040
+ output: process.stderr
15041
+ });
15042
+ try {
15043
+ const answer = await new Promise((resolve) => {
15044
+ rl.question(`${message}\nProceed? [y/N] `, resolve);
15045
+ });
15046
+ return /^y(es)?$/i.test(answer.trim());
15047
+ } finally {
15048
+ rl.close();
15049
+ }
15050
+ }
15051
+ function registerClusterCommands(program) {
15052
+ program.command("cluster").description("Cluster identity recovery (DB <-> S3 sentinel reconcile).").command("reconcile-identity").description("Reconcile cluster_meta.cluster_id with the S3 sentinel. Default restores the DB from the sentinel.").option("--database-url <url>", "Orchestrator DB URL (else KICI_DATABASE_URL)").option("--bucket <bucket>", "S3 bucket (else KICI_STORAGE_BUCKET)").option("--prefix <prefix>", "Storage prefix (else KICI_STORAGE_PREFIX, default empty = bucket root)").option("--region <region>", "S3 region (else KICI_STORAGE_REGION)").option("--endpoint <url>", "S3 endpoint (else KICI_STORAGE_ENDPOINT)").option("--force-path-style", "Use S3 path-style addressing").option("--adopt-db", "Reverse direction: rewrite the sentinel from the DB cluster_id").option("--dry-run", "Report drift and exit without changing anything").option("--yes", "Skip confirmation and apply on drift").action(async (opts) => {
15053
+ try {
15054
+ const result = await runIdempotentStep(buildReconcileStep({
15055
+ databaseUrl: resolveDatabaseUrl$1(opts.databaseUrl),
15056
+ s3: resolveS3(opts),
15057
+ direction: opts.adoptDb ? "sentinel-from-db" : "db-from-sentinel"
15058
+ }), {
15059
+ confirm: cliConfirm,
15060
+ yes: opts.yes,
15061
+ dryRun: opts.dryRun,
15062
+ log: (line) => console.log(line)
15063
+ });
15064
+ if (result.outcome === "applied") console.log("Cluster identity reconciled. Restart the orchestrator to verify it boots clean.");
15065
+ else if (result.outcome === "declined") {
15066
+ console.log("No changes made (declined).");
15067
+ process.exit(1);
15068
+ }
15069
+ } catch (err) {
15070
+ console.error(`Error: ${toErrorMessage(err)}`);
15071
+ process.exit(1);
15072
+ }
15073
+ });
15074
+ }
15075
+ //#endregion
14475
15076
  //#region src/cli/commands/maintenance.ts
14476
15077
  /**
14477
15078
  * Maintenance CLI commands for kici-admin.
@@ -14777,6 +15378,21 @@ function registerEnvironmentCommands(program, getClient) {
14777
15378
  process.exit(1);
14778
15379
  }
14779
15380
  });
15381
+ env.command("purge").description("Delete all environments (and held runs) for an org — direct-DB break-glass / warm-start reset").option("--database-url <url>", "Use direct DB access (or KICI_DATABASE_URL)").option("--org <id>", "Restrict purge to a single org (omit to purge all orgs)").option("--json", "Emit JSON output").action(async (opts) => {
15382
+ try {
15383
+ const dbUrl = resolveDirectDbUrl$4(opts.databaseUrl);
15384
+ if (!dbUrl) {
15385
+ console.error("Error: --database-url or KICI_DATABASE_URL is required (direct-DB only)");
15386
+ process.exit(1);
15387
+ }
15388
+ const result = await purgeEnvironmentsDirect(dbUrl, opts.org);
15389
+ if (opts.json) console.log(JSON.stringify(result));
15390
+ else console.log(`environment purge: environmentsDeleted=${result.environmentsDeleted} heldRunsDeleted=${result.heldRunsDeleted}` + (opts.org ? ` (org ${opts.org})` : " (all orgs)"));
15391
+ } catch (err) {
15392
+ console.error(`Error: ${toErrorMessage(err)}`);
15393
+ process.exit(1);
15394
+ }
15395
+ });
14780
15396
  env.command("create-template").description("Create or update an environment template + its seed variables").requiredOption("--org <id>", "Org ID").requiredOption("--template <name>", "Template name").option("--type <t>", "Environment type (defaults to \"template\")", "template").option("--branch-restrictions <json>", "JSON array of allowed branches").option("--required-reviewers <csv>", "CSV of required reviewer user IDs").option("--wait-timer <seconds>", "Wait timer (seconds)").option("--hold-expiry <seconds>", "Hold expiry TTL (seconds)").option("--minimum-trust <level>", "Minimum trust (known|trusted)").option("--variables <json>", "JSON object of env variables to seed (e.g. '{\"K\":\"V\"}')").option("--database-url <url>", "Use direct DB access instead of HTTP (offline mode)").option("--json", "Emit JSON output").action(async (opts) => {
14781
15397
  try {
14782
15398
  const branchRestrictions = parseJsonOption(opts.branchRestrictions, "--branch-restrictions");
@@ -17598,7 +18214,7 @@ function parseRuleHandles(nftOutput, identifier) {
17598
18214
  const execFileP$1 = promisify(execFile);
17599
18215
  const logger$4 = createLogger({ prefix: "fc-host-network" });
17600
18216
  /** Privileged binaries that need `sudo -n` on a non-root orchestrator host. */
17601
- const PRIVILEGED_BINS = new Set([
18217
+ const PRIVILEGED_BINS = /* @__PURE__ */ new Set([
17602
18218
  "ip",
17603
18219
  "nft",
17604
18220
  "sysctl"
@@ -20663,7 +21279,7 @@ function buildProgram() {
20663
21279
  registerRemoteSourceCommands(program);
20664
21280
  registerWorkflowCommands(program, getClient);
20665
21281
  registerRunsCommands(program, getClient);
20666
- registerAttestationsCommands(program);
21282
+ registerAttestationsCommands(program, getClient);
20667
21283
  registerEventLogCommands(program, getClient);
20668
21284
  registerAccessLogCommands(program, getClient);
20669
21285
  registerBackendCommands(program, getClient);
@@ -20678,6 +21294,7 @@ function buildProgram() {
20678
21294
  registerHostCommands(program);
20679
21295
  registerOrgSettingsCommands(program, getClient);
20680
21296
  registerClusterNameCommands(program, getClient);
21297
+ registerClusterCommands(program);
20681
21298
  registerMaintenanceCommands(program, getClient);
20682
21299
  registerEnvironmentCommands(program, getClient);
20683
21300
  registerVariableCommands(program, getClient);