@kici-dev/orchestrator 0.6.1 → 0.8.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 (187) 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 +12544 -9299
  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/local/lock-file-fetcher.d.ts +8 -0
  120. package/dist/providers/universal-git/config.d.ts +1 -1
  121. package/dist/queue/cleanup.d.ts +13 -0
  122. package/dist/queue/job-queue.d.ts +162 -14
  123. package/dist/queue/retention.d.ts +105 -0
  124. package/dist/reporting/execution-tracker.d.ts +87 -36
  125. package/dist/routes/admin-db.d.ts +5 -0
  126. package/dist/routes/admin-events.d.ts +13 -0
  127. package/dist/routes/admin-held-runs.d.ts +11 -1
  128. package/dist/routes/admin-org-settings.d.ts +9 -0
  129. package/dist/routes/admin-runs.d.ts +7 -4
  130. package/dist/routes/admin-sources.d.ts +15 -0
  131. package/dist/routes/admin-trust-policy.d.ts +11 -0
  132. package/dist/routes/admin.d.ts +25 -0
  133. package/dist/scaler/backend-factory.d.ts +16 -0
  134. package/dist/scaler/bare-metal-backend.d.ts +22 -4
  135. package/dist/scaler/claim-store.d.ts +0 -2
  136. package/dist/scaler/config.d.ts +6 -3
  137. package/dist/scaler/container-backend.d.ts +102 -3
  138. package/dist/scaler/container-hostconfig.d.ts +60 -0
  139. package/dist/scaler/event-backend.d.ts +7 -6
  140. package/dist/scaler/file-tail.d.ts +25 -2
  141. package/dist/scaler/firecracker-api.d.ts +9 -1
  142. package/dist/scaler/firecracker-backend.d.ts +135 -2
  143. package/dist/scaler/host-access.d.ts +59 -0
  144. package/dist/scaler/index.d.ts +1 -0
  145. package/dist/scaler/manager.d.ts +51 -2
  146. package/dist/scaler/scaler-state-store.d.ts +13 -0
  147. package/dist/scaler/types.d.ts +15 -7
  148. package/dist/scaler/warm-pool.d.ts +6 -4
  149. package/dist/secrets/config.d.ts +48 -0
  150. package/dist/secrets/dashboard-encryption-key.d.ts +19 -0
  151. package/dist/secrets/ephemeral-keys.d.ts +9 -3
  152. package/dist/secrets/index.d.ts +3 -1
  153. package/dist/secrets/job-secret-gate.d.ts +76 -0
  154. package/dist/secrets/master-key-rotation.d.ts +65 -0
  155. package/dist/secrets/secret-output-crypto.d.ts +17 -0
  156. package/dist/secrets/secret-resolver.d.ts +23 -6
  157. package/dist/secrets/source-credentials.d.ts +1 -1
  158. package/dist/secrets/token-manager.d.ts +4 -1
  159. package/dist/security/identity-link.d.ts +1 -1
  160. package/dist/security/reduced-privilege-note.d.ts +5 -0
  161. package/dist/security/trust-directory-store.d.ts +1 -1
  162. package/dist/security/trust-policy-gate.d.ts +70 -6
  163. package/dist/security/trust-tier.d.ts +31 -0
  164. package/dist/server.d.ts +1 -1
  165. package/dist/server.js +10793 -5323
  166. package/dist/sources/source-manager.d.ts +1 -1
  167. package/dist/stale-detector/stale-dispatch-candidates.d.ts +43 -0
  168. package/dist/standalone.js +9385 -4160
  169. package/dist/storage/loopback-guard.d.ts +53 -6
  170. package/dist/webhook/event-log.d.ts +14 -1
  171. package/dist/webhook/generic-sources-listener.d.ts +1 -1
  172. package/dist/webhook/ingest-admission.d.ts +24 -0
  173. package/dist/webhook/ingest-overflow-replayer.d.ts +78 -14
  174. package/dist/webhook/ingest-overflow-types.d.ts +13 -1
  175. package/dist/webhook/register-source-bundle.d.ts +23 -0
  176. package/dist/webhook/relay-reinject.d.ts +11 -12
  177. package/dist/webhook/shed-breadcrumb.d.ts +15 -0
  178. package/dist/worker/in-memory-job-queue.d.ts +9 -0
  179. package/dist/ws/fleet-agent-collector.d.ts +7 -0
  180. package/dist/ws/git-credential-relay.d.ts +41 -12
  181. package/dist/ws/platform-client.d.ts +28 -5
  182. package/dist/ws/test-relay-handlers.d.ts +2 -0
  183. package/installer-image-digests.json +3 -3
  184. package/package.json +4 -4
  185. package/sbom.spdx.json +53 -53
  186. package/dist/concurrency/group-tracker.d.ts +0 -51
  187. package/dist/scaler/nftables.d.ts +0 -111
@@ -97,16 +97,24 @@ export interface SpawnContext {
97
97
  * single source, and computing them twice is the defect this field closes.
98
98
  */
99
99
  platformTaints?: readonly string[];
100
+ /**
101
+ * The full taint gate the spawned agent is registered under: the pool's
102
+ * configured `mandatoryLabels` plus every derived platform taint, resolved
103
+ * by `ScalerManager` for the label set this spawn belongs to — the same set
104
+ * the durable spawn row carries. A backend publishes it (an event scaler puts
105
+ * it on the scale-up payload) so a provisioning workflow sees the gate the
106
+ * agent will actually be held to. A backend must never derive it itself: the
107
+ * manager is the single source, exactly as for `platformTaints`.
108
+ */
109
+ mandatoryLabels?: readonly string[];
100
110
  }
101
111
  /**
102
- * Network policy controlling RFC1918 and internet access for agents in this label set.
112
+ * Network policy controlling RFC1918 and internet access for agents in this
113
+ * label set. Defined in `@kici-dev/shared/net` alongside the nftables rule builder
114
+ * that consumes it, and re-exported here so scaler call sites are unchanged.
103
115
  */
104
- export interface NetworkPolicy {
105
- /** CIDR ranges allowed as exceptions to the default RFC1918 block */
106
- allowlist?: string[];
107
- /** Block all outbound traffic except allowlisted ranges */
108
- denyAll?: boolean;
109
- }
116
+ import type { NetworkPolicy } from '@kici-dev/shared/net';
117
+ export type { NetworkPolicy };
110
118
  /**
111
119
  * Image pull policy for container-backend agent images.
112
120
  *
@@ -125,10 +125,12 @@ export declare class WarmPoolManager {
125
125
  * Start the periodic tick: reap surplus agents past their idle timeout, then
126
126
  * top the pools back up.
127
127
  *
128
- * The first pass runs immediately rather than a tick later. Both hosts call
129
- * this only after `ensureHostsReady()`, so spawning here is safe — and
130
- * waiting would leave the pool empty for 30 seconds after every restart,
131
- * which is precisely the cold start it exists to remove.
128
+ * The first pass runs immediately rather than a tick later. The one caller
129
+ * is `ScalerManager.startWarmPools()`, which each host reaches only after
130
+ * `ensureHostsReady()` and on the coordinator after the event emitter
131
+ * is assigned, so spawning here is safe. Waiting a tick instead would leave
132
+ * the pool empty for 30 seconds after every restart, which is precisely the
133
+ * cold start it exists to remove.
132
134
  */
133
135
  start(): void;
134
136
  /**
@@ -24,6 +24,18 @@ export interface SecretStoreConfig {
24
24
  * @throws If neither env var nor key file provides a valid key.
25
25
  */
26
26
  export declare function loadMasterKey(envKey?: string, keyFilePath?: string): Buffer;
27
+ /**
28
+ * Load the master key MATERIAL — the raw configured string, before
29
+ * {@link deriveKey}. Same env-then-file precedence as {@link loadMasterKey}.
30
+ *
31
+ * Several master-key-wrapped stores take the key as a string and derive it
32
+ * themselves (`unwrapPrivateJwk`, `ephemeral-keys.decryptPrivateKey`). Reading
33
+ * `config.secretKey` at those call sites honours `KICI_SECRET_KEY` but silently
34
+ * ignores `KICI_SECRET_KEY_FILE`, so an operator who supplies the key by file
35
+ * gets no signing key and no dashboard-encryption key. Resolving the material
36
+ * here keeps one precedence rule for both the Buffer and string forms.
37
+ */
38
+ export declare function loadMasterKeyMaterial(envKey?: string, keyFilePath?: string): string;
27
39
  /**
28
40
  * Load the old (previous) master encryption key for key rotation.
29
41
  *
@@ -35,6 +47,42 @@ export declare function loadMasterKey(envKey?: string, keyFilePath?: string): Bu
35
47
  * @returns 32-byte Buffer or undefined if no old key is configured.
36
48
  */
37
49
  export declare function loadOldMasterKey(envKey?: string, keyFilePath?: string): Buffer | undefined;
50
+ /**
51
+ * Load the old master key MATERIAL — the raw configured string, before
52
+ * {@link deriveKey}. The string-form counterpart of {@link loadOldMasterKey},
53
+ * for the wrapped stores that take the key as a string.
54
+ */
55
+ export declare function loadOldMasterKeyMaterial(envKey?: string, keyFilePath?: string): string | undefined;
56
+ /**
57
+ * The master key in both the forms the wrapped stores need: the raw configured
58
+ * material (for stores that take a string and derive it themselves) and the
59
+ * derived AES key (for stores that hold a Buffer), each paired with the
60
+ * previous generation during a rotation grace window.
61
+ *
62
+ * Resolved once at boot so every master-key-wrapped store agrees on the same
63
+ * key, and so `KICI_SECRET_KEY_FILE` reaches all of them rather than only the
64
+ * two that happened to call {@link loadMasterKey}.
65
+ */
66
+ export interface ResolvedMasterKeys {
67
+ /** Raw configured key material, before {@link deriveKey}. */
68
+ material: string;
69
+ /** The previous generation's raw material, when a rotation window is open. */
70
+ materialOld: string | undefined;
71
+ /** Derived 32-byte AES key. */
72
+ current: Buffer;
73
+ /** The previous generation's derived key, when a rotation window is open. */
74
+ old: Buffer | undefined;
75
+ }
76
+ /**
77
+ * Resolve the master key (and, when configured, its predecessor) from the
78
+ * env-then-file precedence both loaders use. Returns null when no master key is
79
+ * configured at all — the secrets subsystem is off in that case.
80
+ */
81
+ export declare function resolveMasterKeys(opts: {
82
+ secretKey?: string | undefined;
83
+ secretKeyFile?: string | undefined;
84
+ secretKeyFileOld?: string | undefined;
85
+ }): ResolvedMasterKeys | null;
38
86
  /**
39
87
  * Load the full secret store configuration.
40
88
  *
@@ -19,10 +19,29 @@ export interface ReconcileDashboardEncryptionKeyDeps {
19
19
  isLeader: () => boolean;
20
20
  /** The orchestrator master key (`KICI_SECRET_KEY`), for db-custody wrapping. */
21
21
  secretKey: string | undefined;
22
+ /**
23
+ * The previous master key (`KICI_SECRET_KEY_OLD`), during a rotation grace
24
+ * window. Lets a row still sealed under the old key unwrap, and drives the
25
+ * boot self-heal that re-seals it under the current key.
26
+ */
27
+ oldSecretKey?: string | undefined;
22
28
  /** Called once when a NEW kid is first activated (audit log, system actor). */
23
29
  audit: (info: {
24
30
  kid: string;
25
31
  }) => Promise<void> | void;
32
+ /**
33
+ * Re-seal the active row when it opened only under the OLD master key, back
34
+ * under the current one. Optional: when unset the key still resolves but
35
+ * stays stranded. Best-effort — a failure is warned and never blocks the
36
+ * boot. The recovery path for a deployment stranded by a rotation that ran
37
+ * before `dashboard_encryption_keys` was part of the sweep.
38
+ */
39
+ selfHeal?: (row: {
40
+ kid: string;
41
+ key_version: number;
42
+ }, privateKeyDer: Buffer) => Promise<void>;
43
+ /** Structured warn sink for the self-heal notice. */
44
+ logWarn?: (message: string, meta: Record<string, unknown>) => void;
26
45
  }
27
46
  /** The resolved active dashboard-encryption key + a per-request decrypt closure. */
28
47
  export interface ResolvedDashboardEncryptionKey {
@@ -1,3 +1,6 @@
1
+ /** AAD string used when encrypting/decrypting private keys with secret key. */
2
+ /** AAD binding a wrapped run private key to its purpose (AES-GCM authenticated data). */
3
+ export declare const PRIVATE_KEY_AAD = "ephemeral-private-key";
1
4
  /**
2
5
  * Generate an X25519 key pair for a run.
3
6
  *
@@ -16,14 +19,17 @@ export declare function generateRunKeyPair(): {
16
19
  */
17
20
  export declare function encryptPrivateKey(privateKey: Buffer, secretKey: string): string;
18
21
  /**
19
- * Decrypt a run's private key using the orchestrator secret key (KICI_SECRET_KEY).
22
+ * Decrypt a run's private key using the orchestrator secret key
23
+ * (KICI_SECRET_KEY), falling back to the old key during a rotation grace
24
+ * window. Same dual-key pattern as `PgSecretStore.decryptWithFallback`.
20
25
  *
21
26
  * @param encryptedData - Base64-encoded encrypted private key
22
27
  * @param secretKey - Orchestrator secret key (hex or base64 string)
28
+ * @param oldSecretKey - Previous master key, during a rotation grace window
23
29
  * @returns DER-encoded private key Buffer
24
- * @throws If decryption fails
30
+ * @throws If decryption fails under both keys
25
31
  */
26
- export declare function decryptPrivateKey(encryptedData: string, secretKey: string): Buffer;
32
+ export declare function decryptPrivateKey(encryptedData: string, secretKey: string, oldSecretKey?: string): Buffer;
27
33
  /**
28
34
  * Decrypt a secret output envelope produced by an agent.
29
35
  *
@@ -6,7 +6,9 @@
6
6
  */
7
7
  export { PgSecretStore } from './pg-secret-store.js';
8
8
  export { AuditLogger } from './audit-logger.js';
9
- export { loadMasterKey, loadOldMasterKey, loadSecretStoreConfig, type SecretStoreConfig, } from './config.js';
9
+ export { loadMasterKey, loadMasterKeyMaterial, loadOldMasterKey, loadOldMasterKeyMaterial, loadSecretStoreConfig, resolveMasterKeys, type ResolvedMasterKeys, type SecretStoreConfig, } from './config.js';
10
+ export { secretOutputAad, sealSecretOutput, unsealSecretOutput } from './secret-output-crypto.js';
11
+ export { rotateMasterKeyWrappedTables, selfHealStrandedDashboardKey, selfHealStrandedSigningKey, strandedKeyError, type MasterKeyRotationResult, type SweepResult, } from './master-key-rotation.js';
10
12
  export { encrypt, decrypt, deriveKey, generateMasterKey, type EncryptedValue, } from '@kici-dev/shared';
11
13
  export { VaultSecretStore, type VaultConfig } from './vault-secret-store.js';
12
14
  export { SecretResolver, type SecretResolverDeps, type ResolvedSecretMeta, type ContextStoreLike, type BindingStoreLike, type SecretStoreLike, } from './secret-resolver.js';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The one resolution path for a job-originated qualified secret reference.
3
+ *
4
+ * A workflow may name a secret as `<context>:<key>` in a git credential or a
5
+ * container-registry auth reference. Those references are resolved
6
+ * orchestrator-side, outside the job's bound `contexts:` list — which is what
7
+ * the published examples in `docs/user/patterns/git-credentials.md` and
8
+ * `docs/user/container-jobs.md` do, so a bound-only rule would refuse every
9
+ * documented use.
10
+ *
11
+ * Resolving outside the bindings is not the same as resolving with no
12
+ * authorization. Three independent checks stand between a job and the value,
13
+ * and this module is where all three run:
14
+ *
15
+ * 1. the reserved namespace is refused — a job may never name the
16
+ * orchestrator's own `__system__` org or a `__…__` scope;
17
+ * 2. an untrusted contributor gets nothing, mirroring the install-secrets
18
+ * strip;
19
+ * 3. the NAMED context's own protection rules run against this dispatch —
20
+ * the branch restriction, `minimumTrust`, concurrency, reviewers and wait
21
+ * timers the operator configured on `prod` apply to a `prod:` reference.
22
+ *
23
+ * The lock declaration is the fourth check and lives at the callers: the relay
24
+ * pins the wire ref to `execution_jobs.git_credentials`, and container-registry
25
+ * refs come from the lock by construction.
26
+ *
27
+ * Callers use this instead of `SecretResolverApi.resolveNamedInternal`, which
28
+ * is the system-scoped direct lookup and applies none of the above.
29
+ */
30
+ import type { TrustTier } from '@kici-dev/engine';
31
+ import type { ContextStore } from '../contexts/context-store.js';
32
+ import { type JobDispatchContext } from '../contexts/protection/pipeline.js';
33
+ import type { SecretResolverApi } from './secret-resolver.js';
34
+ /**
35
+ * The org id the orchestrator stores its OWN credentials under — GitHub App
36
+ * private keys, webhook signing secrets, universal-git PATs. A job's `orgId` is
37
+ * its `customer_id`, so a job can never legitimately present this value.
38
+ */
39
+ export declare const RESERVED_ORG_ID = "__system__";
40
+ /**
41
+ * Refuse a reference into the orchestrator's own reserved namespace.
42
+ *
43
+ * Today no job can reach one anyway — a job's `orgId` is its `customer_id`, so
44
+ * `(orgId, '__source__/x')` misses. That is a consequence of two unrelated
45
+ * constants agreeing, not a check: an org literally named `__system__`, a
46
+ * source-scoped secret copied into a customer org, or a helper resolving under
47
+ * a caller-supplied org would each remove it silently. Stating it makes it
48
+ * survive those changes.
49
+ *
50
+ * The message names the reserved namespace, never the secret: a job that
51
+ * guessed a scope learns only that the scope is reserved.
52
+ */
53
+ export declare function assertResolvableJobScope(orgId: string, context: string): void;
54
+ export interface ResolveJobQualifiedSecretArgs {
55
+ resolver: SecretResolverApi;
56
+ contextStore: ContextStore;
57
+ orgId: string;
58
+ runId?: string;
59
+ jobId?: string;
60
+ /** The context half of the workflow's `<context>:<key>` reference. */
61
+ context: string;
62
+ /** The secret-name half. Never logged, never quoted back in an error. */
63
+ key: string;
64
+ /** The dispatch facts the protection rules evaluate against, from server truth. */
65
+ dispatchCtx: JobDispatchContext;
66
+ trustTier: TrustTier | undefined;
67
+ }
68
+ /**
69
+ * Resolve one `<context>:<key>` reference on behalf of a running job.
70
+ *
71
+ * Throws — with a reason naming the check that refused — rather than returning
72
+ * null, because every caller treats a miss as a hard failure and a nullable
73
+ * return is one `??` away from becoming a silent fallback.
74
+ */
75
+ export declare function resolveJobQualifiedSecret(args: ResolveJobQualifiedSecretArgs): Promise<string>;
76
+ //# sourceMappingURL=job-secret-gate.d.ts.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Master-key rotation sweeps for the four wrapped stores `rotate-key` did not
3
+ * cover: the provenance signing key, the dashboard-encryption key, run
4
+ * ephemeral keys, and stored secret outputs.
5
+ *
6
+ * Each sweep runs in its own transaction with the same skip-and-count
7
+ * discipline as `BackendRegistry.rotateKey` — a row neither key opens is
8
+ * counted and left alone rather than failing the whole rotation, so the
9
+ * operator sees a non-zero `skipped` and can act on it. Each sweep is
10
+ * idempotent: re-running over rows already at the current key re-seals them at
11
+ * the next version, which is exactly what the no-old-key "periodic
12
+ * re-encryption" mode is for.
13
+ */
14
+ import type { Kysely } from 'kysely';
15
+ import type { Database } from '../db/types.js';
16
+ import { wrapPrivateJwk } from '../oidc/db-signer.js';
17
+ import type { ResolvedMasterKeys } from './config.js';
18
+ /** One store's rotation outcome. Mirrors the three existing sweeps' shape. */
19
+ export interface SweepResult {
20
+ reEncrypted: number;
21
+ skipped: number;
22
+ }
23
+ /** Every wrapped store this module sweeps, keyed as the API reports them. */
24
+ export interface MasterKeyRotationResult {
25
+ signingKeys: SweepResult;
26
+ dashboardKeys: SweepResult;
27
+ ephemeralKeys: SweepResult;
28
+ secretOutputs: SweepResult;
29
+ }
30
+ type Warn = (message: string, meta: Record<string, unknown>) => void;
31
+ /**
32
+ * Run all four sweeps, each in its own transaction so a bug in one cannot roll
33
+ * back a good rotation of another. Order is least-to-most volume, so the
34
+ * singleton key tables — the two whose loss brings the orchestrator down — move
35
+ * first.
36
+ */
37
+ export declare function rotateMasterKeyWrappedTables(db: Kysely<Database>, keys: ResolvedMasterKeys, warn: Warn): Promise<MasterKeyRotationResult>;
38
+ /**
39
+ * Re-seal a single row that decrypted only under the OLD master key, back under
40
+ * the current one. This is the boot self-heal: a deployment already stranded by
41
+ * a rotation that ran before these tables were swept recovers by restoring the
42
+ * old key once and restarting.
43
+ *
44
+ * The `key_version` match is the concurrent-rotation guard — mirrors
45
+ * `BackendRegistry.selfHealStrandedRow`. A rotation running at the same time
46
+ * has already moved the version, so this UPDATE matches no row and is a no-op
47
+ * rather than a clobber.
48
+ */
49
+ export declare function selfHealStrandedSigningKey(db: Kysely<Database>, row: {
50
+ kid: string;
51
+ key_version: number;
52
+ }, privateJwk: Parameters<typeof wrapPrivateJwk>[0], keys: ResolvedMasterKeys): Promise<void>;
53
+ /** The dashboard-encryption twin of {@link selfHealStrandedSigningKey}. */
54
+ export declare function selfHealStrandedDashboardKey(db: Kysely<Database>, row: {
55
+ kid: string;
56
+ key_version: number;
57
+ }, privateKeyDer: Buffer, keys: ResolvedMasterKeys): Promise<void>;
58
+ /**
59
+ * Loud, recovery-pointing error for a key stranded by a master-key rotation.
60
+ * Same shape and intent as `BackendRegistry.strandedError`: the operator needs
61
+ * to be told the recovery path, not just that AES-GCM failed.
62
+ */
63
+ export declare function strandedKeyError(store: string): Error;
64
+ export {};
65
+ //# sourceMappingURL=master-key-rotation.d.ts.map
@@ -0,0 +1,17 @@
1
+ /** AAD binding a stored secret output to its run (AES-GCM authenticated data). */
2
+ export declare function secretOutputAad(runId: string): string;
3
+ /** Seal a secret-output plaintext under the master key at `keyVersion`. */
4
+ export declare function sealSecretOutput(plaintext: string, masterKey: Buffer, runId: string, keyVersion?: number): {
5
+ data: string;
6
+ keyVersion: number;
7
+ };
8
+ /**
9
+ * Unseal a stored secret output with the current master key, falling back to
10
+ * the old key during a rotation grace window. Same dual-key pattern as
11
+ * `PgSecretStore.decryptWithFallback`. Throws when neither key opens it.
12
+ */
13
+ export declare function unsealSecretOutput(encryptedValue: string, runId: string, keys: {
14
+ current: Buffer;
15
+ old?: Buffer;
16
+ }, keyVersion?: number): string;
17
+ //# sourceMappingURL=secret-output-crypto.d.ts.map
@@ -38,7 +38,7 @@ export interface BindingStoreLike {
38
38
  * Minimal secret store interface (subset needed by resolver).
39
39
  * getAllSecrets returns raw encrypted secrets; decrypt decrypts a single secret.
40
40
  * getSecrets returns a decrypted key-value map for a single scope (used by
41
- * resolveNamed for source-scoped credential lookup).
41
+ * resolveNamedInternal for source-scoped credential lookup).
42
42
  */
43
43
  export interface SecretStoreLike {
44
44
  getAllSecrets(orgId: string): Promise<ScopedSecret[]>;
@@ -70,7 +70,12 @@ export interface ResolvedSecretMeta {
70
70
  */
71
71
  export interface SecretResolverApi {
72
72
  resolveForJob(orgId: string, contextName: string, hostCtx?: HostFacts): Promise<Record<string, string>>;
73
- resolveNamed(orgId: string, scope: string, key: string, opts?: {
73
+ /**
74
+ * System-scoped direct lookup — no context binding, no protection rule, no
75
+ * trust tier. A job-originated reference goes through
76
+ * `resolveJobQualifiedSecret` instead. See the implementation's doc comment.
77
+ */
78
+ resolveNamedInternal(orgId: string, scope: string, key: string, opts?: {
74
79
  store?: string;
75
80
  runId?: string;
76
81
  jobId?: string;
@@ -107,9 +112,21 @@ export declare class SecretResolver implements SecretResolverApi {
107
112
  resolveForJob(orgId: string, contextName: string, hostCtx?: HostFacts): Promise<Record<string, string>>;
108
113
  /**
109
114
  * Resolve a single named secret by (orgId, scope, key), optionally scoped to
110
- * a specific backend. Bypasses context bindings — this is a direct
111
- * lookup used for source-scoped credentials (e.g. universal-git PAT/SSH
112
- * keys stored under `__source__/<sourceId>`).
115
+ * a specific backend.
116
+ *
117
+ * SYSTEM-SCOPED CALLERS ONLY. It applies no authorization of any kind: no
118
+ * context binding, no protection rule, no trust tier. Its legitimate callers
119
+ * resolve the orchestrator's OWN credentials, under scopes a workflow can
120
+ * never name — `resolveSourceCredential` (`__source__/<sourceId>`) and the
121
+ * bringup API's host secret refs.
122
+ *
123
+ * A JOB-ORIGINATED reference goes through `resolveJobQualifiedSecret`
124
+ * (`secrets/job-secret-gate.ts`) instead, which runs the named context's
125
+ * protection rules and the trust-tier strip before reaching this method. The
126
+ * `Internal` suffix is the enforcement: this method's doc comment already
127
+ * warned that it bypassed context bindings, and three call sites took it
128
+ * anyway — a warning is not a boundary, but a name shows up in a grep of
129
+ * callers.
113
130
  *
114
131
  * When `store` is omitted, backends are tried in Map iteration order (the
115
132
  * order they were registered) and the first hit wins. An explicit `store`
@@ -120,7 +137,7 @@ export declare class SecretResolver implements SecretResolverApi {
120
137
  * fail-fast policy — the caller asked for a specific backend and
121
138
  * it's gone.
122
139
  */
123
- resolveNamed(orgId: string, scope: string, key: string, opts?: {
140
+ resolveNamedInternal(orgId: string, scope: string, key: string, opts?: {
124
141
  store?: string;
125
142
  runId?: string;
126
143
  jobId?: string;
@@ -9,7 +9,7 @@
9
9
  * the key name.
10
10
  *
11
11
  * This module is a thin, opinionated wrapper around
12
- * `SecretResolver.resolveNamed()` that:
12
+ * `SecretResolver.resolveNamedInternal()` that:
13
13
  * 1. Builds the canonical scope string for a given source ID.
14
14
  * 2. Delegates to the resolver's direct-lookup method.
15
15
  * 3. Returns a structured failure (null + reason) so callers can emit
@@ -18,9 +18,12 @@ export declare class TokenManager {
18
18
  * @param routingKey - Optional routing key scope (null = all).
19
19
  * @param expiresAt - Optional absolute expiry. When omitted the token
20
20
  * never expires (null); `validate()` already enforces the column.
21
+ * @param subject - Optional intended holder (an OIDC `sub` or an email).
22
+ * Advisory only — never read when authorizing. Omitted leaves the token
23
+ * `unlinked` in the RBAC drift report.
21
24
  * @returns The plaintext token (shown once) and the row ID.
22
25
  */
23
- generateToken(label: string, role: Role, routingKey?: string | null, expiresAt?: Date | null): Promise<{
26
+ generateToken(label: string, role: Role, routingKey?: string | null, expiresAt?: Date | null, subject?: string | null): Promise<{
24
27
  token: string;
25
28
  id: string;
26
29
  }>;
@@ -40,7 +40,7 @@ export interface IdentityLink {
40
40
  * the answer is unambiguous; when they disagree there is no way to tell here
41
41
  * which provider serves the commit, so this answers undefined rather than
42
42
  * naming the wrong account. Undefined is a safe answer at every call site: the
43
- * attribution is simply omitted, never replaced by the raw subject id.
43
+ * attribution is omitted, never replaced by the raw subject id.
44
44
  */
45
45
  export declare function resolveLinkedUsername(identityLinks: IdentityLink[], userId: string): string | undefined;
46
46
  /**
@@ -43,6 +43,11 @@ export declare const REDUCED_PRIVILEGE_MARKER = "**Reduced privileges (untrusted
43
43
  * - `resolveInstallSecrets` returns `npmRegistries: undefined` and
44
44
  * `installEnvSecrets: undefined` for a resolved non-trusted tier, so the run
45
45
  * carries neither.
46
+ * - the per-job container-registry resolution in `dispatch-matched-workflow.ts`
47
+ * is skipped for the same tiers, so `jobConfig` carries no
48
+ * `containerRegistryAuth` and a private base image fails to pull. Keyed on
49
+ * the tier through the same `isUntrustedTier`, so the clause and the strip
50
+ * cannot disagree.
46
51
  * - `deriveCacheRefScope` returns the isolated scope for the same tiers, and
47
52
  * `UserCache` confines WRITES to it; a restore still falls back to the shared
48
53
  * scope, so the clause is about what the run saves, not what it can read.
@@ -64,7 +64,7 @@ import type { Database } from '../db/types.js';
64
64
  * What it does not pin: the field *set*. A fourth directory field added to
65
65
  * `trustPolicyUpdateSchema` is not picked up here, and `server.ts` hand-builds
66
66
  * the value it passes to `upsertFromPlatform`, so nothing would fail — the new
67
- * field would simply go unpersisted. Adding one means extending this object and
67
+ * field would go unpersisted. Adding one means extending this object and
68
68
  * that literal together.
69
69
  */
70
70
  export declare const trustDirectorySchema: z.ZodObject<{
@@ -104,11 +104,12 @@ export declare const DEFAULT_FORK_POLICY: ForkPolicy;
104
104
  * events is the fail-closed posture: nothing foreign dispatches, and the event
105
105
  * is dropped rather than parked in a queue nobody is watching.
106
106
  *
107
- * This is NOT necessarily a brief transient. The Platform sends
108
- * `trust_policy.update` only when the org has a `trust_policies` row, and that
109
- * row is created lazily on a dashboard read so an org that has never opened
110
- * Settings > CI trust receives no push at all and stays on these values
111
- * indefinitely.
107
+ * This is NOT necessarily a brief transient. The Platform pushes
108
+ * `trust_policy.update` on every authenticated handshake, and for an org with
109
+ * no `trust_policies` row it pushes its own defaults, whose fork switch is
110
+ * `ignore` as well. So an org that has never opened Settings > CI trust holds
111
+ * these values for as long as nobody chooses otherwise, and every fork pull
112
+ * request it receives is dropped.
112
113
  */
113
114
  export declare const FAIL_CLOSED_POLICY: TrustPolicy;
114
115
  /**
@@ -127,17 +128,80 @@ export declare const FAIL_CLOSED_POLICY: TrustPolicy;
127
128
  * can approve it — which an ignored event is not.
128
129
  */
129
130
  export declare const READ_FAILURE_POLICY: TrustPolicy;
131
+ /**
132
+ * Where the policy in force came from.
133
+ *
134
+ * The three arms are three different things to tell an operator, and the
135
+ * difference is what makes a verdict explicable: `stored` is a choice somebody
136
+ * made, `unconfigured` is what nobody choosing looks like, and `read-failure`
137
+ * is neither — it is the orchestrator declining to guess.
138
+ *
139
+ * Named for the resolution rather than the policy, because
140
+ * `TrustPolicySource` is already taken by a sibling in this directory —
141
+ * `trust-policy-store.ts` answers "who WROTE the cached row" (`platform` /
142
+ * `local`) and exports that name from the package barrel. Two exported types
143
+ * of that name would read as one to anyone grepping for it.
144
+ */
145
+ export declare const EffectivePolicySource: z.ZodEnum<{
146
+ "read-failure": "read-failure";
147
+ stored: "stored";
148
+ unconfigured: "unconfigured";
149
+ }>;
150
+ export type EffectivePolicySource = z.infer<typeof EffectivePolicySource>;
151
+ /** A policy plus where it came from. */
152
+ export interface EffectiveTrustPolicy {
153
+ policy: TrustPolicy;
154
+ source: EffectivePolicySource;
155
+ }
130
156
  /**
131
157
  * Pick the policy to evaluate. A stored row always wins; without one every
132
158
  * orchestrator gets the fail-closed policy above.
133
159
  *
160
+ * Returns the source alongside the policy because the values alone cannot
161
+ * answer "why was my pull request dropped?" — `ignore` reads identically
162
+ * whether an operator chose it or nobody chose anything.
163
+ *
164
+ * `read-failure` is not produced here: this function is handed the outcome of a
165
+ * read, not the read itself, so a caller whose read THREW builds that arm from
166
+ * `READ_FAILURE_POLICY` at its own call site.
167
+ *
134
168
  * `mode` no longer selects between two postures — an independent orchestrator
135
169
  * has no upstream authority, which is a reason to be stricter rather than more
136
170
  * permissive — but it stays on the signature so callers that legitimately hold
137
171
  * a mode do not have to change, and so a future per-mode difference has a place
138
172
  * to land.
139
173
  */
140
- export declare function resolveEffectivePolicy(stored: StoredTrustPolicy | null, _mode: OrchestratorMode): TrustPolicy;
174
+ export declare function resolveEffectivePolicy(stored: StoredTrustPolicy | null, _mode: OrchestratorMode): EffectiveTrustPolicy;
175
+ /**
176
+ * The org fork switch in force for one delivery, and where it came from.
177
+ *
178
+ * Carried out of the evaluation so a call site that has to EXPLAIN the verdict
179
+ * — the drop site, which is the one exit that leaves nothing else behind — can
180
+ * name both without re-reading the store.
181
+ */
182
+ export interface ForkSwitchContext {
183
+ policy: ForkPolicy;
184
+ source: EffectivePolicySource;
185
+ /**
186
+ * True where a Platform owns this org's policy. It decides which remedy the
187
+ * explanation names: an independent orchestrator has no dashboard to send its
188
+ * operator to, and a Platform-attached one answers a local write with a 409.
189
+ */
190
+ platformManaged: boolean;
191
+ }
192
+ /**
193
+ * The sentence written on the event-log row of a dropped fork pull request.
194
+ *
195
+ * This row is the ONLY trace an `ignore` verdict leaves: there is no run, no
196
+ * check status, and nothing on the pull request itself. So it names the switch
197
+ * value, where that value came from, and what did not happen — an operator
198
+ * reading it must not have to already know the fork switch exists.
199
+ *
200
+ * When nobody chose the switch it also names the remedy, which follows the
201
+ * deployment: the dashboard owns the policy wherever a Platform is attached,
202
+ * and `kici-admin trust-policy set` owns it where none is.
203
+ */
204
+ export declare function forkDropExplanation(forkSwitch: ForkSwitchContext): string;
141
205
  /**
142
206
  * Evaluate the fork switch.
143
207
  *
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The one predicate for "this run's contributor is not trusted".
3
+ *
4
+ * It lives beside `trust-resolver.ts` and `reduced-privilege-note.ts` — the
5
+ * modules that decide and render the posture it reads — rather than inside any
6
+ * one consumer, because three unrelated subsystems now withhold credentials on
7
+ * it: the install-secrets resolver, the job secret gate, and container-registry
8
+ * auth. A copy per consumer is how two of them would end up disagreeing about
9
+ * what an absent tier means.
10
+ */
11
+ import type { TrustTier } from '@kici-dev/engine';
12
+ /**
13
+ * True when the resolved trust tier is anything other than 'trusted'.
14
+ *
15
+ * `undefined` is the LENIENT direction here: an unresolved tier leaves
16
+ * credentials in place, while `deriveCacheRefScope` maps the same `undefined`
17
+ * to the isolated cache scope, which is what `resolveWorkflowDockerfileBuilds`
18
+ * then denies unless the org opted in. `evaluateTrustGate` reads it leniently
19
+ * too; `deriveCacheRefScope`, `selectLockFileSource`, and the trust-policy
20
+ * gate's `isNonTrusted` all read the same `undefined` strictly.
21
+ *
22
+ * Several dispatch paths reach it with no tier, and the set is open — a
23
+ * provider bundle carrying a fork model does not keep a run out of it. Among
24
+ * them: an internally-triggered run whose inheritance lookup degrades, a
25
+ * pull-request event from a provider with no fork model, a cross-source
26
+ * dispatch, and a `kici run` remote test run. Treat "no tier" as its own case
27
+ * rather than as a proxy for any one of them. Documented for operators in
28
+ * `docs/user/events.md` under the trust-tier rules.
29
+ */
30
+ export declare function isUntrustedTier(tier: TrustTier | undefined): boolean;
31
+ //# sourceMappingURL=trust-tier.d.ts.map
package/dist/server.d.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * Graceful shutdown in reverse order:
14
14
  * Platform client -> agent WS -> heartbeat -> HTTP -> DB
15
15
  */
16
- import type { OrchestratorFaultInjection } from './testing/fault-injection.js';
16
+ import type { OrchestratorFaultInjection } from './fault-injection-types.js';
17
17
  /**
18
18
  * Boot the Platform-connected orchestrator. Factored out of the module entry so
19
19
  * both this file's production entry (which passes no fault injection) and the