@kici-dev/orchestrator 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/dist/agent/dispatcher.d.ts +10 -1
  2. package/dist/agent/host-roster.d.ts +8 -3
  3. package/dist/agent/registry.d.ts +60 -5
  4. package/dist/app.d.ts +10 -5
  5. package/dist/cache/dep-cache.d.ts +14 -6
  6. package/dist/cache/global-eval-round-cache.d.ts +1 -1
  7. package/dist/cache/index.d.ts +1 -1
  8. package/dist/cache/source-cache.d.ts +92 -15
  9. package/dist/cancel/cancel-run.d.ts +20 -2
  10. package/dist/cancel/sweep-stuck-cancelling.d.ts +30 -0
  11. package/dist/cli/api-client.d.ts +10 -0
  12. package/dist/cli/commands/agent-service/logs.d.ts +1 -1
  13. package/dist/cli/commands/agent-service/restart.d.ts +1 -1
  14. package/dist/cli/commands/agent-service/start.d.ts +1 -1
  15. package/dist/cli/commands/agent-service/status.d.ts +1 -1
  16. package/dist/cli/commands/agent-service/stop.d.ts +1 -1
  17. package/dist/cli/commands/agent-service/uninstall.d.ts +1 -1
  18. package/dist/cli/commands/agent.d.ts +25 -0
  19. package/dist/cli/commands/config.d.ts +16 -0
  20. package/dist/cli/commands/db-backup.d.ts +46 -0
  21. package/dist/cli/commands/held-run.d.ts +9 -0
  22. package/dist/cli/commands/orchestrator-service/drain.d.ts +2 -19
  23. package/dist/cli/commands/orchestrator-service/index.d.ts +9 -1
  24. package/dist/cli/commands/orchestrator-service/install-env.d.ts +43 -2
  25. package/dist/cli/commands/orchestrator-service/logs.d.ts +1 -1
  26. package/dist/cli/commands/orchestrator-service/restart.d.ts +1 -1
  27. package/dist/cli/commands/orchestrator-service/start.d.ts +1 -1
  28. package/dist/cli/commands/orchestrator-service/status.d.ts +17 -1
  29. package/dist/cli/commands/orchestrator-service/stop.d.ts +1 -1
  30. package/dist/cli/commands/orchestrator-service/uninstall.d.ts +1 -1
  31. package/dist/cli/commands/orchestrator-service/upgrade.d.ts +2 -1
  32. package/dist/cli/commands/rotate.d.ts +1 -1
  33. package/dist/cli/commands/shared/env-file-mode.d.ts +16 -0
  34. package/dist/cli/commands/shared/upgrade-hooks.d.ts +32 -0
  35. package/dist/cli/commands/shared/upgrade-safety.d.ts +81 -0
  36. package/dist/cli/commands/shared/versioned-upgrade.d.ts +67 -12
  37. package/dist/cli/commands/source.d.ts +1 -0
  38. package/dist/cli/commands/token.d.ts +4 -0
  39. package/dist/cli/commands/trust-policy.d.ts +57 -1
  40. package/dist/cli/join.d.ts +1 -1
  41. package/dist/cli/service/backup-timer.d.ts +108 -0
  42. package/dist/cli/service/compose-path.d.ts +11 -0
  43. package/dist/cli/service/compose.d.ts +22 -0
  44. package/dist/cli/service/deploy-env.d.ts +10 -11
  45. package/dist/cli/service/env-assignment.d.ts +24 -0
  46. package/dist/cli/service/index.d.ts +2 -2
  47. package/dist/cli/service/instance/resolve.d.ts +71 -7
  48. package/dist/cli/service/instance/types.d.ts +15 -0
  49. package/dist/cli/service/launchd.d.ts +7 -0
  50. package/dist/cli/service/systemd.d.ts +1 -0
  51. package/dist/cli/service/types.d.ts +36 -0
  52. package/dist/cli/service/windows.d.ts +16 -0
  53. package/dist/cli/wizard/orchestrator-wizard.d.ts +12 -3
  54. package/dist/cli/wizard/prompts.d.ts +7 -0
  55. package/dist/cli.js +12525 -9298
  56. package/dist/cluster/cluster-settings-reader.d.ts +2 -2
  57. package/dist/cluster/coordinator.d.ts +11 -0
  58. package/dist/cluster/instance-heartbeat.d.ts +96 -0
  59. package/dist/cluster/join-client.d.ts +55 -5
  60. package/dist/cluster/join-handler.d.ts +1 -0
  61. package/dist/cluster/orphan-recovery.d.ts +16 -0
  62. package/dist/cluster/peer-registry.d.ts +10 -0
  63. package/dist/concurrency/dispatch-next-queued.d.ts +1 -13
  64. package/dist/concurrency/index.d.ts +1 -2
  65. package/dist/concurrency/queue-manager.d.ts +63 -18
  66. package/dist/config/env-overlay.d.ts +3 -2
  67. package/dist/config.d.ts +24 -0
  68. package/dist/contexts/release-queued-holds.d.ts +23 -1
  69. package/dist/dashboard/attestation-filters.d.ts +2 -0
  70. package/dist/dashboard/handler.d.ts +22 -2
  71. package/dist/db/deferred-indexes.d.ts +72 -0
  72. package/dist/db/migration-test-harness.d.ts +22 -0
  73. package/dist/db/migrations/131_execution_jobs_git_credentials.d.ts +30 -0
  74. package/dist/db/migrations/132_execution_runs_trigger_event.d.ts +23 -0
  75. package/dist/db/migrations/133_dispatch_queue_source_tar_digest.d.ts +23 -0
  76. package/dist/db/migrations/134_dispatch_queue_ownership.d.ts +4 -0
  77. package/dist/db/migrations/135_cluster_settings_retention_days.d.ts +4 -0
  78. package/dist/db/migrations/136_master_key_wrapped_tables_key_version.d.ts +4 -0
  79. package/dist/db/migrations/137_execution_runs_pr_head_context.d.ts +4 -0
  80. package/dist/db/migrations/138_execution_runs_subject_trigger_event.d.ts +4 -0
  81. package/dist/db/migrations/139_admin_token_subject.d.ts +4 -0
  82. package/dist/db/migrations/140_dispatch_queue_canonical_labels.d.ts +54 -0
  83. package/dist/db/migrations/141_event_log_shed.d.ts +15 -0
  84. package/dist/db/migrator.d.ts +47 -0
  85. package/dist/db/notify-listener.d.ts +60 -0
  86. package/dist/db/synthetic-job-ids.d.ts +24 -0
  87. package/dist/db/types.d.ts +169 -4
  88. package/dist/deployment/deployment-identity.d.ts +31 -1
  89. package/dist/diagnostics/checks/index.d.ts +2 -1
  90. package/dist/diagnostics/checks/retention.d.ts +24 -0
  91. package/dist/diagnostics/types.d.ts +7 -0
  92. package/dist/events/event-router.d.ts +1 -1
  93. package/dist/fault-injection-types.d.ts +53 -0
  94. package/dist/firecracker/host-network.d.ts +63 -8
  95. package/dist/git/credential-broker.d.ts +35 -2
  96. package/dist/git/inherited-secret.d.ts +7 -2
  97. package/dist/git/job-context.d.ts +7 -4
  98. package/dist/helpers/secure-write.d.ts +35 -0
  99. package/dist/index.js +196 -67
  100. package/dist/metrics/prometheus.d.ts +116 -1
  101. package/dist/oidc/db-signer.d.ts +27 -5
  102. package/dist/oidc/id-token-claims.d.ts +33 -2
  103. package/dist/oidc/oidc-mint-registration.d.ts +6 -0
  104. package/dist/oidc/orchestrator-mint.d.ts +8 -0
  105. package/dist/oidc/reconcile-signing-key.d.ts +37 -0
  106. package/dist/orchestrator-core.d.ts +9 -3
  107. package/dist/pipeline/decorating-secret-resolver.d.ts +1 -1
  108. package/dist/pipeline/dispatch-matched-workflow.d.ts +65 -14
  109. package/dist/pipeline/manual-schedule.d.ts +1 -0
  110. package/dist/pipeline/process-webhook.d.ts +20 -2
  111. package/dist/pipeline/route-or-dispatch-jobs.d.ts +7 -0
  112. package/dist/pipeline/run-event-context.d.ts +39 -0
  113. package/dist/policy/dashboard-write-policy-listener.d.ts +1 -1
  114. package/dist/policy/dashboard-write-policy.d.ts +47 -1
  115. package/dist/provenance/dispatch-context.d.ts +46 -0
  116. package/dist/provenance/verify-deferred-capture.d.ts +39 -0
  117. package/dist/providers/github/auth.d.ts +9 -0
  118. package/dist/providers/github/deliveries.d.ts +118 -0
  119. package/dist/providers/universal-git/config.d.ts +1 -1
  120. package/dist/queue/cleanup.d.ts +13 -0
  121. package/dist/queue/job-queue.d.ts +162 -14
  122. package/dist/queue/retention.d.ts +105 -0
  123. package/dist/reporting/execution-tracker.d.ts +87 -36
  124. package/dist/routes/admin-db.d.ts +5 -0
  125. package/dist/routes/admin-events.d.ts +13 -0
  126. package/dist/routes/admin-held-runs.d.ts +11 -1
  127. package/dist/routes/admin-org-settings.d.ts +9 -0
  128. package/dist/routes/admin-runs.d.ts +7 -4
  129. package/dist/routes/admin-sources.d.ts +15 -0
  130. package/dist/routes/admin-trust-policy.d.ts +11 -0
  131. package/dist/routes/admin.d.ts +25 -0
  132. package/dist/scaler/backend-factory.d.ts +16 -0
  133. package/dist/scaler/bare-metal-backend.d.ts +22 -4
  134. package/dist/scaler/config.d.ts +6 -3
  135. package/dist/scaler/container-backend.d.ts +102 -3
  136. package/dist/scaler/container-hostconfig.d.ts +60 -0
  137. package/dist/scaler/file-tail.d.ts +25 -2
  138. package/dist/scaler/firecracker-api.d.ts +9 -1
  139. package/dist/scaler/firecracker-backend.d.ts +135 -2
  140. package/dist/scaler/host-access.d.ts +59 -0
  141. package/dist/scaler/index.d.ts +1 -0
  142. package/dist/scaler/manager.d.ts +33 -0
  143. package/dist/scaler/scaler-state-store.d.ts +6 -0
  144. package/dist/scaler/types.d.ts +5 -7
  145. package/dist/secrets/config.d.ts +48 -0
  146. package/dist/secrets/dashboard-encryption-key.d.ts +19 -0
  147. package/dist/secrets/ephemeral-keys.d.ts +9 -3
  148. package/dist/secrets/index.d.ts +3 -1
  149. package/dist/secrets/job-secret-gate.d.ts +76 -0
  150. package/dist/secrets/master-key-rotation.d.ts +65 -0
  151. package/dist/secrets/secret-output-crypto.d.ts +17 -0
  152. package/dist/secrets/secret-resolver.d.ts +23 -6
  153. package/dist/secrets/source-credentials.d.ts +1 -1
  154. package/dist/secrets/token-manager.d.ts +4 -1
  155. package/dist/security/reduced-privilege-note.d.ts +5 -0
  156. package/dist/security/trust-policy-gate.d.ts +70 -6
  157. package/dist/security/trust-tier.d.ts +31 -0
  158. package/dist/server.d.ts +1 -1
  159. package/dist/server.js +10661 -5282
  160. package/dist/sources/source-manager.d.ts +1 -1
  161. package/dist/stale-detector/stale-dispatch-candidates.d.ts +43 -0
  162. package/dist/standalone.js +9258 -4122
  163. package/dist/storage/loopback-guard.d.ts +53 -6
  164. package/dist/webhook/event-log.d.ts +14 -1
  165. package/dist/webhook/generic-sources-listener.d.ts +1 -1
  166. package/dist/webhook/ingest-admission.d.ts +24 -0
  167. package/dist/webhook/ingest-overflow-replayer.d.ts +78 -14
  168. package/dist/webhook/ingest-overflow-types.d.ts +13 -1
  169. package/dist/webhook/register-source-bundle.d.ts +23 -0
  170. package/dist/webhook/relay-reinject.d.ts +11 -12
  171. package/dist/webhook/shed-breadcrumb.d.ts +15 -0
  172. package/dist/ws/git-credential-relay.d.ts +41 -12
  173. package/dist/ws/platform-client.d.ts +28 -5
  174. package/installer-image-digests.json +3 -3
  175. package/package.json +4 -4
  176. package/sbom.spdx.json +53 -53
  177. package/dist/concurrency/group-tracker.d.ts +0 -51
  178. package/dist/scaler/nftables.d.ts +0 -111
@@ -3,7 +3,7 @@ import type { Database } from '../db/types.js';
3
3
  /** Numeric columns on cluster_settings readable via {@link ClusterSettingsReader}. */
4
4
  export type ClusterNumberColumn = 'max_github_payload_bytes' | 'event_log_max_payload_bytes' | 'lock_file_max_bytes' | 'webhook_dedup_ttl_ms'
5
5
  /** @deprecated Readable, but no call site reads it. Removed at v1.0.0. */
6
- | 'contributor_cache_ttl_ms' | 'event_router_event_ttl_seconds' | 'event_router_max_dispatch_attempts' | 'queue_max_depth' | 'reroute_flap_grace_ms' | 'max_fanout_hosts' | 'event_router_rate_limit_per_workflow_per_minute' | 'cache_max_tarball_bytes' | 'cache_ttl_days' | 'lockfile_cache_max' | 'lockfile_cache_max_bytes' | 'lockfile_cache_ttl_ms' | 'content_cache_max' | 'content_cache_max_bytes' | 'content_cache_ttl_ms' | 'global_eval_round_timeout_ms' | 'global_eval_candidate_timeout_ms' | 'global_eval_cache_max' | 'global_eval_wait_timeout_ms' | 'check_run_tracking_ttl_days' | 'concurrency_wait_timeout_ms' | 'agent_token_ttl_ms' | 'ownership_db_check_timeout_ms' | 'unroutable_grace_ms' | 'ingest_overflow_claim_timeout_ms' | 'scaler_reap_interval_ms' | 'scaler_reap_stranded_timeout_ms' | 'scaler_reap_reattempt_interval_ms' | 'scaler_claim_retention_ms' | 'scaler_provision_backoff_base_ms' | 'scaler_provision_backoff_max_ms' | 'scaler_provision_max_consecutive_failures';
6
+ | 'contributor_cache_ttl_ms' | 'event_router_event_ttl_seconds' | 'event_router_max_dispatch_attempts' | 'queue_max_depth' | 'reroute_flap_grace_ms' | 'max_fanout_hosts' | 'event_router_rate_limit_per_workflow_per_minute' | 'cache_max_tarball_bytes' | 'cache_ttl_days' | 'lockfile_cache_max' | 'lockfile_cache_max_bytes' | 'lockfile_cache_ttl_ms' | 'content_cache_max' | 'content_cache_max_bytes' | 'content_cache_ttl_ms' | 'global_eval_round_timeout_ms' | 'global_eval_candidate_timeout_ms' | 'global_eval_cache_max' | 'global_eval_wait_timeout_ms' | 'check_run_tracking_ttl_days' | 'run_retention_days' | 'audit_retention_days' | 'provenance_retention_days' | 'held_run_retention_days' | 'concurrency_wait_timeout_ms' | 'agent_token_ttl_ms' | 'ownership_db_check_timeout_ms' | 'unroutable_grace_ms' | 'ingest_overflow_claim_timeout_ms' | 'scaler_reap_interval_ms' | 'scaler_reap_stranded_timeout_ms' | 'scaler_reap_reattempt_interval_ms' | 'scaler_claim_retention_ms' | 'scaler_provision_backoff_base_ms' | 'scaler_provision_backoff_max_ms' | 'scaler_provision_max_consecutive_failures';
7
7
  /** Text columns on cluster_settings readable via {@link ClusterSettingsReader}. */
8
8
  export type ClusterStringColumn = 'dashboard_verified_issuer';
9
9
  /** Boolean columns on cluster_settings readable via {@link ClusterSettingsReader}. */
@@ -40,7 +40,7 @@ export declare const CACHE_MAX_ENTRIES_CEILING = 100000;
40
40
  *
41
41
  * A value that is not a usable positive count (NaN, non-finite, below 1) falls
42
42
  * back to the configured default rather than clamping to 1: a 1-entry cache
43
- * thrashes silently, which is harder to diagnose than simply ignoring garbage.
43
+ * thrashes silently, which is harder to diagnose than ignoring garbage.
44
44
  * The fallback is floored and bounded by the ceiling on the same path as the
45
45
  * stored value, so neither a fractional nor an above-ceiling configured default
46
46
  * can reach the LRU constructor as a non-integer or unclamped max — the
@@ -47,6 +47,7 @@ export interface JobToRoute {
47
47
  sha: string;
48
48
  sourceTarUrl?: string;
49
49
  sourceTarHash?: string;
50
+ sourceTarDigest?: string;
50
51
  depsUrl?: string;
51
52
  depsHash?: string;
52
53
  /** Labels that the dispatched agent must NOT have. */
@@ -325,6 +326,16 @@ export declare class RunCoordinator {
325
326
  private handleRerouteSpawnTimeout;
326
327
  /** Best-effort cancel a single job on a peer (double-execution guard). */
327
328
  private cancelPeerJob;
329
+ /**
330
+ * Forward `job.cancel` for ONE job to the coordinator that holds its agent's
331
+ * socket, reporting whether the message left this process.
332
+ *
333
+ * The run-cancel path needs this for a job this coordinator never routed:
334
+ * the Platform picks any pool member to receive `run.cancel.request`, and only
335
+ * the coordinator holding the agent's socket can put a cancel frame on it.
336
+ * {@link RunCoordinator.cancelRun} covers rerouted jobs and is unchanged.
337
+ */
338
+ cancelJobOnPeer(peerId: string, runId: string, jobId: string, reason: string): boolean;
328
339
  /**
329
340
  * Local fallback for a re-dispatch: dispatch the job on this coordinator under
330
341
  * the existing jobId. Returns true when the local dispatcher accepts it.
@@ -0,0 +1,96 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Database } from '../db/types.js';
3
+ /** Default interval between `cluster_instances` heartbeat writes. */
4
+ export declare const DEFAULT_INSTANCE_HEARTBEAT_MS = 10000;
5
+ /**
6
+ * How many missed ticks a coordinator gets before it reads as dead. Six ticks
7
+ * (60 s at the default interval) is wide enough that a GC pause, a slow DB or a
8
+ * brief network stall does not evict a healthy coordinator — the cost of a
9
+ * false "dead" verdict is another coordinator recovering jobs it is watching.
10
+ */
11
+ export declare const HEARTBEAT_MISSES_BEFORE_DEAD = 6;
12
+ /**
13
+ * The dispatcher's recovery grace period at the default config: twice
14
+ * `agentMaxReconnectDelayMs` (60 s). Used as the fallback floor when a caller
15
+ * has no configured grace period to hand in.
16
+ */
17
+ export declare const DEFAULT_RECOVERY_GRACE_MS = 120000;
18
+ /**
19
+ * The window inside which a `cluster_instances` row counts as live.
20
+ *
21
+ * Deliberately the larger of the recovery grace period and six heartbeat ticks:
22
+ * the ownership predicate must never call a coordinator dead sooner than the
23
+ * recovery machinery would have given it to reconnect.
24
+ */
25
+ export declare function instanceLivenessGraceMs(gracePeriodMs: number, heartbeatMs?: number): number;
26
+ export interface InstanceHeartbeatOptions {
27
+ db: Kysely<Database>;
28
+ instanceId: string;
29
+ /** Interval between writes. Defaults to {@link DEFAULT_INSTANCE_HEARTBEAT_MS}. */
30
+ intervalMs?: number;
31
+ /** Orchestrator version, recorded for operator visibility. */
32
+ version?: string;
33
+ /** Reads the current Raft role, when the node runs Raft. */
34
+ getRole?: () => string | null;
35
+ }
36
+ /**
37
+ * Writes this coordinator's liveness into `cluster_instances` on a fixed tick.
38
+ *
39
+ * Every ownership predicate in the dispatch plane asks "is the coordinator that
40
+ * owns this row still alive?", and the answer has to come from the database.
41
+ * The in-memory peer registry cannot answer it: it is empty at exactly the
42
+ * moment startup recovery runs, because a freshly booted process has not
43
+ * handshaken with any peer yet, so it reads every live sibling as dead — which
44
+ * is the boot-time bug the predicate exists to fix. Raft membership is worse:
45
+ * a partitioned coordinator self-elects and reads the other half as gone.
46
+ *
47
+ * A DB heartbeat needs no peer connectivity, so it behaves the same in a
48
+ * Raft cluster, a plain multi-coordinator deployment and a standalone one.
49
+ * `host_roster` already derives agent liveness this way; this is the same
50
+ * mechanism pointed at coordinators.
51
+ */
52
+ export declare class InstanceHeartbeat {
53
+ private readonly db;
54
+ private readonly instanceId;
55
+ private readonly intervalMs;
56
+ private readonly version;
57
+ private readonly getRole?;
58
+ private timer;
59
+ constructor(opts: InstanceHeartbeatOptions);
60
+ /**
61
+ * Write one heartbeat immediately, then every `intervalMs`.
62
+ *
63
+ * The immediate write matters: startup recovery runs moments after boot and
64
+ * reads this table, so a coordinator that had not yet written a row would be
65
+ * invisible to its own siblings' predicates.
66
+ */
67
+ start(): Promise<void>;
68
+ stop(): void;
69
+ /**
70
+ * Remove this instance's row on a clean shutdown, so a coordinator that
71
+ * stopped on purpose is dead to its siblings immediately rather than after
72
+ * the grace window. Best-effort: a crash leaves the row to expire.
73
+ */
74
+ retire(): Promise<void>;
75
+ /**
76
+ * One heartbeat write. Best-effort: a failed tick is retried on the next.
77
+ *
78
+ * `processStart` is set for the write `start()` makes, and is the only one
79
+ * that rewrites `started_at` — the column records when THIS process came up,
80
+ * so a restart under the same instance id must move it while an ordinary tick
81
+ * must not.
82
+ */
83
+ beat(opts?: {
84
+ processStart?: boolean;
85
+ }): Promise<void>;
86
+ }
87
+ /**
88
+ * Which of the given coordinator instance ids are alive right now.
89
+ *
90
+ * Reads `cluster_instances` directly rather than the peer registry, for the
91
+ * reason the class above documents: the peer registry is empty during boot and
92
+ * blind under a partition, and a wrong "dead" verdict here means acting on a
93
+ * row a working coordinator owns.
94
+ */
95
+ export declare function liveInstanceIds(db: Kysely<Database>, instanceIds: readonly string[], graceMs: number): Promise<Set<string>>;
96
+ //# sourceMappingURL=instance-heartbeat.d.ts.map
@@ -6,7 +6,7 @@
6
6
  * 2. Send join.request with the join token
7
7
  * 3. Receive join.response with encrypted config bundle
8
8
  * 4. Decrypt bundle using token-derived AES-256-GCM key
9
- * 5. Write decrypted config as local YAML file
9
+ * 5. Write the decrypted config as an env file the orchestrator boots from
10
10
  *
11
11
  * The token carries routing info (cleartext for Platform relay) and a secret
12
12
  * (used for HKDF key derivation). Only the joiner and the token creator
@@ -22,12 +22,23 @@ interface JoinClientOptions {
22
22
  peerUrl?: string;
23
23
  /** API key for Platform authentication (required for --platform mode) */
24
24
  apiKey?: string;
25
- /** Path to write the resulting local config YAML */
25
+ /**
26
+ * Path to write the resulting local config YAML.
27
+ *
28
+ * @deprecated The orchestrator boots from its environment, so nothing reads
29
+ * this file. Leave it unset and use {@link JoinClientOptions.envFilePath}.
30
+ */
26
31
  configPath?: string;
32
+ /** Path to write the env file `orchestrator install --env-file` consumes. */
33
+ envFilePath?: string;
27
34
  }
35
+ /** Default path the join writes its env file to. */
36
+ export declare const DEFAULT_JOIN_ENV_FILE = "./kici-orchestrator.env";
28
37
  /**
29
- * Local config structure written by the join client.
30
- * Compatible with loadLocalConfig() from config/loader.ts.
38
+ * Local config structure written by the deprecated `--config` path.
39
+ *
40
+ * @deprecated `loadLocalConfig()` strips `storage` and `secrets`, and the boot
41
+ * path never reads the file at all.
31
42
  */
32
43
  interface JoinLocalConfig {
33
44
  database: {
@@ -45,10 +56,48 @@ export declare function decryptAndParseBundle(encryptedB64: string, encryptionKe
45
56
  /**
46
57
  * Build a JoinLocalConfig from a decrypted ConfigBundle.
47
58
  * Maps bundle fields to the structure expected by loadLocalConfig().
59
+ *
60
+ * @deprecated Feeds the deprecated `--config` artifact. Use
61
+ * {@link buildEnvFile}.
48
62
  */
49
63
  export declare function buildLocalConfig(bundle: ConfigBundle): JoinLocalConfig;
64
+ /**
65
+ * Startup environment variable each `ConfigBundle.storage` field is delivered
66
+ * through.
67
+ *
68
+ * The `satisfies` clause makes a bundle storage field with no entry here a
69
+ * compile error, so the projection cannot silently drop one — which is the
70
+ * whole defect this artifact exists to avoid. The companion test pins the
71
+ * other half: every field `sharedConfigSchema.storage` accepts, and every
72
+ * variable named here, resolves in the startup env definition.
73
+ */
74
+ export declare const STORAGE_ENV_VARS: {
75
+ readonly type: 'KICI_STORAGE_TYPE';
76
+ readonly bucket: 'KICI_STORAGE_BUCKET';
77
+ readonly prefix: 'KICI_STORAGE_PREFIX';
78
+ readonly region: 'KICI_STORAGE_REGION';
79
+ readonly endpoint: 'KICI_STORAGE_ENDPOINT';
80
+ readonly externalEndpoint: 'KICI_STORAGE_EXTERNAL_ENDPOINT';
81
+ readonly forcePathStyle: 'KICI_STORAGE_FORCE_PATH_STYLE';
82
+ readonly logBucket: 'KICI_STORAGE_LOG_BUCKET';
83
+ };
84
+ /**
85
+ * Project a decrypted `ConfigBundle` onto the environment variables the
86
+ * orchestrator boot path reads.
87
+ *
88
+ * A value carrying a newline would silently truncate the file, so it is
89
+ * rejected by name rather than written.
90
+ */
91
+ export declare function buildEnvFile(bundle: ConfigBundle): string;
92
+ /**
93
+ * Write the env file the orchestrator boots from.
94
+ */
95
+ export declare function writeEnvFile(path: string, bundle: ConfigBundle): Promise<void>;
50
96
  /**
51
97
  * Write a local config object to a YAML file.
98
+ *
99
+ * @deprecated The orchestrator boot path is environment-only, so nothing reads
100
+ * this file. Use {@link writeEnvFile}.
52
101
  */
53
102
  export declare function writeConfigFile(path: string, config: Record<string, unknown>): Promise<void>;
54
103
  export declare class JoinClient {
@@ -59,7 +108,8 @@ export declare class JoinClient {
59
108
  * 1. Send join.request with token to Platform relay or direct peer
60
109
  * 2. Receive join.response with encrypted config bundle
61
110
  * 3. Decrypt bundle using token-derived key
62
- * 4. Write local config YAML
111
+ * 4. Write the env file the orchestrator boots from (and, when `--config`
112
+ * names one, the deprecated local YAML)
63
113
  */
64
114
  join(): Promise<void>;
65
115
  /**
@@ -33,6 +33,7 @@ export interface ConfigBundle {
33
33
  prefix?: string;
34
34
  region?: string;
35
35
  endpoint?: string;
36
+ externalEndpoint?: string;
36
37
  forcePathStyle?: boolean;
37
38
  logBucket?: string;
38
39
  };
@@ -60,6 +60,22 @@ export declare class OrphanRecovery {
60
60
  * Only runs when this node is the Raft leader.
61
61
  */
62
62
  scanForOrphans(): Promise<void>;
63
+ /**
64
+ * Release concurrency slots still marked `active` for a run that has already
65
+ * finished. Run once per leader transition (`start()`), which covers a boot
66
+ * and a failover.
67
+ *
68
+ * A slot outlives its run whenever the release path did not run — a
69
+ * coordinator crash between "the run finished" and "the slot was released".
70
+ * On a `max: 1` deploy gate a single leaked slot blocks every later deploy
71
+ * forever, and nothing else would ever notice: the DB says the slot is held
72
+ * and the run that held it is gone.
73
+ *
74
+ * `getActiveRuns` reconciles the same way inside its own transaction on every
75
+ * acquire, so this is the sweep for a scope nobody happens to ask about —
76
+ * which is why once per leader transition is enough.
77
+ */
78
+ releaseLeakedConcurrencySlots(): Promise<void>;
63
79
  /**
64
80
  * Attempt to recover a single potentially orphaned run.
65
81
  */
@@ -127,6 +127,16 @@ export declare class PeerRegistry {
127
127
  * Update peer state from a heartbeat message.
128
128
  * Updates agent inventory, draining status, capabilities, term, leaderId,
129
129
  * and lastHeartbeatAt timestamp.
130
+ *
131
+ * A heartbeat is an ingress into the label-matching domain, so every label
132
+ * it carries is folded here rather than at each comparison site. A peer
133
+ * running an older build sends the case its operator typed — `Docker` — and
134
+ * every consumer of `PeerInfo` compares against folded job selectors, so a
135
+ * consumer that forgets to fold silently stops counting that peer's agents.
136
+ * Folding on arrival means a consumer cannot forget.
137
+ *
138
+ * Identifiers are deliberately NOT folded: `agentId`, `scalerName`, and the
139
+ * scaler's `name` / `type` are opaque and case-sensitive.
130
140
  */
131
141
  updateHeartbeat(instanceId: string, heartbeat: PeerHeartbeat): void;
132
142
  /**
@@ -12,27 +12,15 @@
12
12
  * get permanently stuck on a phantom holder.
13
13
  */
14
14
  import type { AgentRegistry } from '../agent/registry.js';
15
- import type { ConcurrencyGroupTracker } from './group-tracker.js';
16
15
  import type { ConcurrencyQueueManager } from './queue-manager.js';
17
16
  import type { ConcurrencyWaiters } from './waiters.js';
18
17
  export interface DispatchNextQueuedDeps {
19
- /** In-memory tracker — kept in sync with the DB on dequeue. */
20
- tracker: ConcurrencyGroupTracker;
21
- /** DB-backed queue manager. */
18
+ /** The DB arbiter. */
22
19
  queueManager: ConcurrencyQueueManager;
23
20
  /** Agent registry for finding the waiter's WS. */
24
21
  registry: AgentRegistry;
25
22
  /** In-memory waiter map. */
26
23
  waiters: ConcurrencyWaiters;
27
- /**
28
- * Default max for `acquireSlot` when re-syncing the in-memory tracker after
29
- * a dequeue. The runtime concurrency config (per-workflow `max`) is not
30
- * available at slot-release time; the dequeued waiter has already been
31
- * accounted for under that policy when it was originally enqueued, so any
32
- * value `>= 1` is safe here. We pass 1 because the v1 contract documents
33
- * single-slot semantics (max>1 is out of scope).
34
- */
35
- trackerMaxFallback?: number;
36
24
  }
37
25
  /**
38
26
  * Try to dequeue the next queued entry for `(group, routingKey)` and notify
@@ -1,3 +1,2 @@
1
- export { ConcurrencyGroupTracker, type HydrateRecord } from './group-tracker.js';
2
- export { ConcurrencyQueueManager, type QueuedJob, type EnqueueOptions } from './queue-manager.js';
1
+ export { ConcurrencyQueueManager, ConcurrencySlotStatus, type QueuedJob, type EnqueueOptions, } from './queue-manager.js';
3
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,12 +1,23 @@
1
1
  /**
2
- * DB-backed concurrency queue manager.
2
+ * The arbiter of `concurrency: { group, max }`.
3
3
  *
4
- * Persists queued jobs to the concurrency_groups table and dequeues
5
- * the oldest queued job when a slot opens. Works with ConcurrencyGroupTracker
6
- * for slot-based coordination.
4
+ * Every decision is a transaction against `concurrency_groups`, which is what
5
+ * makes `max` mean the same thing across a restart and across every coordinator
6
+ * in a cluster. A process-local map cannot: a restart came back with an empty
7
+ * map and let a second run deploy alongside the one already deploying, and two
8
+ * coordinators each held their own map so `max: 1` was never cluster-wide.
9
+ * `concurrency: { group, max: 1 }` is the guard customers use to serialise
10
+ * deploys and migrations, so "usually right" is not a useful guarantee.
7
11
  */
8
- import type { Kysely } from 'kysely';
12
+ import { type Kysely } from 'kysely';
9
13
  import type { Database } from '../db/types.js';
14
+ /** `concurrency_groups.status` values. */
15
+ export declare enum ConcurrencySlotStatus {
16
+ Active = "active",
17
+ Queued = "queued",
18
+ Completed = "completed",
19
+ Cancelled = "cancelled"
20
+ }
10
21
  /** A queued job returned from dequeue operations. */
11
22
  export interface QueuedJob {
12
23
  id: string;
@@ -25,20 +36,63 @@ export interface EnqueueOptions {
25
36
  export declare class ConcurrencyQueueManager {
26
37
  private readonly db;
27
38
  constructor(db: Kysely<Database>);
39
+ /**
40
+ * Serialise every arbitration for one (routingKey, groupKey) scope.
41
+ *
42
+ * A transaction-scoped advisory lock, so it is released on commit or
43
+ * rollback with no cleanup path to get wrong. Counting active rows and
44
+ * inserting one are two statements, and without the lock two coordinators
45
+ * both read `count < max` and both insert — which is exactly the
46
+ * double-deploy the cap exists to prevent.
47
+ */
48
+ private static lockScope;
49
+ /**
50
+ * Try to take a slot in a concurrency group.
51
+ *
52
+ * Idempotent: a run that already holds an active slot re-acquires it without
53
+ * consuming a second one, which the partial unique index on
54
+ * `(routing_key, group_key, run_id) WHERE status = 'active'` also enforces at
55
+ * the DB level.
56
+ *
57
+ * @returns whether this run holds a slot afterwards.
58
+ */
59
+ acquireSlot(groupKey: string, routingKey: string, runId: string, jobId: string, opts: {
60
+ max: number;
61
+ }): Promise<boolean>;
62
+ /** Release this run's slot in a group. No-op when it holds none. */
63
+ releaseSlot(groupKey: string, routingKey: string, runId: string): Promise<void>;
64
+ /**
65
+ * The runs holding an active slot, oldest first.
66
+ *
67
+ * A run whose `execution_runs` row is already terminal is excluded and its
68
+ * slot is released in the same transaction. A slot outlives its run whenever
69
+ * the release path did not run — a coordinator crash between "the run
70
+ * finished" and "the slot was released" — and a leaked slot on a `max: 1`
71
+ * deploy gate blocks every later deploy forever.
72
+ */
73
+ getActiveRuns(groupKey: string, routingKey: string): Promise<string[]>;
74
+ /** The oldest active run, which `cancelInProgress` supersedes. */
75
+ getOldestRun(groupKey: string, routingKey: string): Promise<string | null>;
76
+ /**
77
+ * Active run ids for a scope, reconciled against `execution_runs`. Must be
78
+ * called with the scope lock already held.
79
+ */
80
+ private static activeRunIds;
28
81
  /**
29
82
  * Enqueue a job that is waiting for a concurrency slot.
30
83
  * Inserts a row with status='queued' into concurrency_groups.
31
84
  */
32
85
  enqueue(opts: EnqueueOptions): Promise<void>;
33
- /**
34
- * Record an active slot in the DB for persistence across restarts.
35
- */
36
- recordActive(opts: EnqueueOptions): Promise<void>;
37
86
  /**
38
87
  * Dequeue the oldest queued job for a concurrency group.
39
88
  * Returns null if no queued jobs exist.
40
89
  */
41
90
  dequeueNext(groupKey: string, routingKey: string): Promise<QueuedJob | null>;
91
+ /**
92
+ * Promote the oldest queued waiter to `active`. Must be called with the scope
93
+ * lock already held.
94
+ */
95
+ private static promoteOldestQueued;
42
96
  /**
43
97
  * Cancel all queued entries for a run.
44
98
  * Marks them as 'cancelled' with a completion timestamp.
@@ -55,14 +109,5 @@ export declare class ConcurrencyQueueManager {
55
109
  * queued job should be dispatched.
56
110
  */
57
111
  onJobComplete(groupKey: string, routingKey: string, runId: string): Promise<QueuedJob | null>;
58
- /**
59
- * Load all active entries from DB for hydrating the in-memory tracker.
60
- * Called on orchestrator startup.
61
- */
62
- loadActiveEntries(): Promise<Array<{
63
- groupKey: string;
64
- routingKey: string;
65
- runId: string;
66
- }>>;
67
112
  }
68
113
  //# sourceMappingURL=queue-manager.d.ts.map
@@ -12,9 +12,10 @@
12
12
  * Convert a KICI_ env var key to a config path array.
13
13
  * Returns null for non-KICI_ keys or unknown mappings.
14
14
  *
15
- * Handles two patterns:
15
+ * Handles three patterns:
16
16
  * 1. Direct mappings (KICI_DATABASE_URL -> ['database', 'url'])
17
- * 2. Multi-app GitHub provider (KICI_PROVIDERS_GITHUB_<NAME>_<FIELD> ->
17
+ * 2. Startup names whose overlay spelling differs (KICI_PORT -> ['server', 'port'])
18
+ * 3. Multi-app GitHub provider (KICI_PROVIDERS_GITHUB_<NAME>_<FIELD> ->
18
19
  * ['providers', 'github', '<name-lowered>', '<camelField>'])
19
20
  */
20
21
  export declare function envKeyToConfigPath(key: string): string[] | null;
package/dist/config.d.ts CHANGED
@@ -34,6 +34,7 @@ declare const configSchema: z.ZodObject<{
34
34
  platform: "platform";
35
35
  }>>;
36
36
  port: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
37
+ host: z.ZodDefault<z.ZodString>;
37
38
  basePath: z.ZodDefault<z.ZodString>;
38
39
  tlsCertPath: z.ZodOptional<z.ZodString>;
39
40
  platformUrl: z.ZodOptional<z.ZodString>;
@@ -42,6 +43,7 @@ declare const configSchema: z.ZodObject<{
42
43
  provenanceIssuer: z.ZodOptional<z.ZodString>;
43
44
  provenanceSigningIssuer: z.ZodOptional<z.ZodString>;
44
45
  provenanceSignerKind: z.ZodOptional<z.ZodString>;
46
+ oidcLegacyPrSub: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>, z.ZodTransform<boolean, string | boolean>>;
45
47
  provenanceKmsKeyArn: z.ZodOptional<z.ZodString>;
46
48
  provenanceKmsRegion: z.ZodOptional<z.ZodString>;
47
49
  provenanceKmsAccessKeyId: z.ZodOptional<z.ZodString>;
@@ -89,6 +91,7 @@ declare const configSchema: z.ZodObject<{
89
91
  ingestOverflowReplayIntervalMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
90
92
  ingestOverflowReplayBatch: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
91
93
  ingestOverflowMaxAttempts: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
94
+ ingestOverflowMaxAgeMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
92
95
  ingestOverflowClaimTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
93
96
  rerouteSpawnWindowMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
94
97
  rerouteAckTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -129,6 +132,10 @@ declare const configSchema: z.ZodObject<{
129
132
  dispatchQueueTtlDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
130
133
  stepLogTtlDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
131
134
  checkRunTrackingTtlDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
135
+ runRetentionDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
136
+ auditRetentionDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
137
+ provenanceRetentionDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
138
+ heldRunRetentionDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
132
139
  reconnectReplayWindowHours: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
133
140
  cacheBuildTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
134
141
  cacheMaxTarballBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -203,6 +210,7 @@ declare const configSchema: z.ZodObject<{
203
210
  }>>;
204
211
  autoMigrate: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<boolean, string>>;
205
212
  agentMaxReconnectDelayMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
213
+ clusterInstanceHeartbeatMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
206
214
  otelExporterOtlpEndpoint: z.ZodOptional<z.ZodString>;
207
215
  clusterName: z.ZodOptional<z.ZodString>;
208
216
  cluster: z.ZodPrefault<z.ZodObject<{
@@ -237,6 +245,7 @@ export declare const packagingConfigSchema: z.ZodObject<{
237
245
  platform: "platform";
238
246
  }>>;
239
247
  port: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
248
+ host: z.ZodDefault<z.ZodString>;
240
249
  basePath: z.ZodDefault<z.ZodString>;
241
250
  tlsCertPath: z.ZodOptional<z.ZodString>;
242
251
  platformUrl: z.ZodOptional<z.ZodString>;
@@ -245,6 +254,7 @@ export declare const packagingConfigSchema: z.ZodObject<{
245
254
  provenanceIssuer: z.ZodOptional<z.ZodString>;
246
255
  provenanceSigningIssuer: z.ZodOptional<z.ZodString>;
247
256
  provenanceSignerKind: z.ZodOptional<z.ZodString>;
257
+ oidcLegacyPrSub: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>, z.ZodTransform<boolean, string | boolean>>;
248
258
  provenanceKmsKeyArn: z.ZodOptional<z.ZodString>;
249
259
  provenanceKmsRegion: z.ZodOptional<z.ZodString>;
250
260
  provenanceKmsAccessKeyId: z.ZodOptional<z.ZodString>;
@@ -292,6 +302,7 @@ export declare const packagingConfigSchema: z.ZodObject<{
292
302
  ingestOverflowReplayIntervalMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
293
303
  ingestOverflowReplayBatch: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
294
304
  ingestOverflowMaxAttempts: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
305
+ ingestOverflowMaxAgeMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
295
306
  ingestOverflowClaimTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
296
307
  rerouteSpawnWindowMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
297
308
  rerouteAckTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -332,6 +343,10 @@ export declare const packagingConfigSchema: z.ZodObject<{
332
343
  dispatchQueueTtlDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
333
344
  stepLogTtlDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
334
345
  checkRunTrackingTtlDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
346
+ runRetentionDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
347
+ auditRetentionDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
348
+ provenanceRetentionDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
349
+ heldRunRetentionDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
335
350
  reconnectReplayWindowHours: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
336
351
  cacheBuildTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
337
352
  cacheMaxTarballBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -406,6 +421,7 @@ export declare const packagingConfigSchema: z.ZodObject<{
406
421
  }>>;
407
422
  autoMigrate: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<boolean, string>>;
408
423
  agentMaxReconnectDelayMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
424
+ clusterInstanceHeartbeatMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
409
425
  otelExporterOtlpEndpoint: z.ZodOptional<z.ZodString>;
410
426
  clusterName: z.ZodOptional<z.ZodString>;
411
427
  cluster: z.ZodPrefault<z.ZodObject<{
@@ -473,6 +489,7 @@ export type AppConfig = z.infer<typeof configSchema> & {
473
489
  export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
474
490
  mode: "hybrid" | "independent" | "observed" | "platform";
475
491
  port: number;
492
+ host: string;
476
493
  basePath: string;
477
494
  tlsCertPath?: string | undefined;
478
495
  platformUrl?: string | undefined;
@@ -481,6 +498,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
481
498
  provenanceIssuer?: string | undefined;
482
499
  provenanceSigningIssuer?: string | undefined;
483
500
  provenanceSignerKind?: string | undefined;
501
+ oidcLegacyPrSub: boolean;
484
502
  provenanceKmsKeyArn?: string | undefined;
485
503
  provenanceKmsRegion?: string | undefined;
486
504
  provenanceKmsAccessKeyId?: string | undefined;
@@ -525,6 +543,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
525
543
  ingestOverflowReplayIntervalMs: number;
526
544
  ingestOverflowReplayBatch: number;
527
545
  ingestOverflowMaxAttempts: number;
546
+ ingestOverflowMaxAgeMs: number;
528
547
  ingestOverflowClaimTimeoutMs: number;
529
548
  rerouteSpawnWindowMs: number;
530
549
  rerouteAckTimeoutMs: number;
@@ -559,6 +578,10 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
559
578
  dispatchQueueTtlDays: number;
560
579
  stepLogTtlDays: number;
561
580
  checkRunTrackingTtlDays: number;
581
+ runRetentionDays: number;
582
+ auditRetentionDays: number;
583
+ provenanceRetentionDays: number;
584
+ heldRunRetentionDays: number;
562
585
  reconnectReplayWindowHours: number;
563
586
  cacheBuildTimeoutMs: number;
564
587
  cacheMaxTarballBytes: number;
@@ -612,6 +635,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
612
635
  nodeEnv: "development" | "production" | "test";
613
636
  autoMigrate: boolean;
614
637
  agentMaxReconnectDelayMs: number;
638
+ clusterInstanceHeartbeatMs: number;
615
639
  otelExporterOtlpEndpoint?: string | undefined;
616
640
  clusterName?: string | undefined;
617
641
  cluster: {
@@ -12,6 +12,13 @@ export interface ReleaseQueuedHoldsArgs {
12
12
  /** Resume a released hold by re-dispatching its job. */
13
13
  onJobRelease: (signal: ReleaseSignal) => Promise<void>;
14
14
  }
15
+ /** Jobs of one run the caller already accounts for itself. */
16
+ export interface OccupyingJobExclusion {
17
+ /** The run the excluded names belong to. */
18
+ runId: string;
19
+ /** `execution_jobs.job_name` values to leave out of the count. */
20
+ jobNames: readonly string[];
21
+ }
15
22
  /**
16
23
  * Count the jobs OCCUPYING a slot in a concurrency group, scoped to one org.
17
24
  *
@@ -23,10 +30,25 @@ export interface ReleaseQueuedHoldsArgs {
23
30
  * one: the release path re-checks the limit precisely so the bound holds
24
31
  * whenever the sweep happens to run, and an undercount defeats that re-check.
25
32
  *
33
+ * A PLACEHOLDER row is excluded, and that exclusion is what keeps the count
34
+ * from deadlocking the queue it gates. A job the dispatch pass held back — on a
35
+ * `needs` edge, a rolling-wave slot, an invoke-gate summon, or a protection-rule
36
+ * hold — is registered under a `needs-pending-` id at status `pending` so the
37
+ * run stays open for it. It has reached no agent, so it occupies nothing; and
38
+ * counting it meant a queued hold counted against the very limit that gates its
39
+ * own release. One held job in a limit-1 group then read as `free = 1 - 1 = 0`
40
+ * and was never released at all, and the hold sat until the expiry sweep failed
41
+ * it. Its real row, with the dispatcher's own job id, replaces the placeholder
42
+ * the moment it dispatches — so the slot is counted from exactly then.
43
+ *
26
44
  * The `customer_id` predicate is not optional: a context name shared across
27
45
  * tenants would otherwise leak concurrency between them.
46
+ *
47
+ * `exclude` lets a caller that carries its own in-memory reservation for some
48
+ * of these jobs keep the two terms disjoint. The dispatch gate is the one such
49
+ * caller; the release sweep passes nothing and counts everything.
28
50
  */
29
- export declare function countOccupyingJobs(db: Kysely<Database>, orgId: string, concurrencyGroup: string): Promise<number>;
51
+ export declare function countOccupyingJobs(db: Kysely<Database>, orgId: string, concurrencyGroup: string, exclude?: OccupyingJobExclusion): Promise<number>;
30
52
  /**
31
53
  * Release up to `limit - running` queued holds for a group, oldest first.
32
54
  * Returns how many actually resumed.
@@ -21,6 +21,7 @@ export declare function baseAttestationsQuery(db: Kysely<Database>): import("kys
21
21
  batch_accumulation_items: import("../db/types.js").BatchAccumulationItemsTable;
22
22
  batch_accumulation_windows: import("../db/types.js").BatchAccumulationWindowsTable;
23
23
  check_run_tracking: import("../db/types.js").CheckRunTrackingTable;
24
+ cluster_instances: import("../db/types.js").ClusterInstancesTable;
24
25
  cluster_meta: import("../db/types.js").ClusterMetaTable;
25
26
  cluster_settings: import("../db/types.js").ClusterSettingsTable;
26
27
  cold_store_chunk_counts: import("../db/types.js").ColdStoreChunkCountsTable;
@@ -96,6 +97,7 @@ export declare function basePendingAttestationsQuery(db: Kysely<Database>): impo
96
97
  batch_accumulation_items: import("../db/types.js").BatchAccumulationItemsTable;
97
98
  batch_accumulation_windows: import("../db/types.js").BatchAccumulationWindowsTable;
98
99
  check_run_tracking: import("../db/types.js").CheckRunTrackingTable;
100
+ cluster_instances: import("../db/types.js").ClusterInstancesTable;
99
101
  cluster_meta: import("../db/types.js").ClusterMetaTable;
100
102
  cluster_settings: import("../db/types.js").ClusterSettingsTable;
101
103
  cold_store_chunk_counts: import("../db/types.js").ColdStoreChunkCountsTable;