@kici-dev/orchestrator 0.6.0 → 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 +10674 -5283
  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
@@ -69,6 +69,21 @@ export interface TrustPolicyView {
69
69
  * route returned, so the values remain reachable for anyone who needs them.
70
70
  */
71
71
  export declare function formatPolicy(policy: TrustPolicyView, format: string): string;
72
+ /**
73
+ * Warn that the fork switch in force silently drops pull requests.
74
+ *
75
+ * `ignore` is the one verdict that leaves nothing behind: no run row, no check
76
+ * status, and nothing on the pull request itself. A maintainer who does not
77
+ * already know the switch exists has no way to connect a fork PR that CI never
78
+ * touched to a policy they never set, so the reader of `trust-policy show`
79
+ * is told outright — with where to look for each individual drop.
80
+ *
81
+ * `reject` is deprecated and resolves through the same arm, so it warns too.
82
+ *
83
+ * Returns the lines rather than printing them so the check is unit-testable,
84
+ * matching `policyDeprecationWarnings` above.
85
+ */
86
+ export declare function forkDropWarnings(policy: TrustPolicyView): string[];
72
87
  /**
73
88
  * Render the enforced hold window.
74
89
  *
@@ -135,9 +150,47 @@ export interface TrustDirectoryView {
135
150
  interface DirectoryResponse {
136
151
  directory: TrustDirectoryView | null;
137
152
  platformManaged: boolean;
153
+ /**
154
+ * Whether the orchestrator's Platform connection is up right now.
155
+ *
156
+ * Absent whenever there is no connection state to report, which never means
157
+ * "down": an independent orchestrator, the `PATCH` and `DELETE` responses,
158
+ * and an orchestrator that predates the field. {@link directoryStalenessNote}
159
+ * escalates only on an explicit `false`.
160
+ */
161
+ platformConnected?: boolean;
138
162
  /** Present on `directory-remove`: false when the member held nothing. */
139
163
  removed?: boolean;
140
164
  }
165
+ /**
166
+ * Say how long ago the directory was written, beside the timestamp itself.
167
+ *
168
+ * The absolute time is what the orchestrator stored and stays first, because it
169
+ * is what correlates with a Platform-side change. The age is what an operator
170
+ * would otherwise compute in their head at the exact moment they are least able
171
+ * to — mid-incident, reading a refused approval.
172
+ *
173
+ * A timestamp this cannot parse renders alone rather than as `NaN`, and one in
174
+ * the future renders as `0s` — the two clocks are different machines'.
175
+ */
176
+ export declare function formatDirectoryAge(updatedAt: string, now: number): string;
177
+ /**
178
+ * Explain what an old directory costs, and escalate when it cannot refresh.
179
+ *
180
+ * Only a Platform-attached orchestrator has an upstream to lag behind. An
181
+ * independent one's directory is written by the operator reading this, so there
182
+ * is nothing to be stale against and nothing to say.
183
+ *
184
+ * The connected wording is a `Note:` and the disconnected one a `Warning:`,
185
+ * because the two ask for different things: one states a property of the design
186
+ * the reader should know, the other names a condition they should act on. The
187
+ * `Warning:` producers above — `forkDropWarnings`, `policyExpiryWarnings` and
188
+ * `policyDeprecationWarnings` — are all of the second kind.
189
+ *
190
+ * Returns the lines rather than printing them so the check is unit-testable,
191
+ * matching `forkDropWarnings` above.
192
+ */
193
+ export declare function directoryStalenessNote(res: DirectoryResponse): string[];
141
194
  /**
142
195
  * Render the stored approval directory, or say why there is nothing to render.
143
196
  *
@@ -146,8 +199,11 @@ interface DirectoryResponse {
146
199
  * decides whether the approval counts, and a `{team}` clause is matched against
147
200
  * the team memberships. So the listing is per-entry rather than a set of counts
148
201
  * — "who can approve right now" is the question this command exists to answer.
202
+ *
203
+ * `now` is a parameter so the rendered age is deterministic under test; every
204
+ * caller takes the default.
149
205
  */
150
- export declare function formatDirectory(res: DirectoryResponse, format: string): string;
206
+ export declare function formatDirectory(res: DirectoryResponse, format: string, now?: number): string;
151
207
  export declare function registerTrustPolicyCommands(program: Command, getClient: () => AdminApiClient): void;
152
208
  export {};
153
209
  //# sourceMappingURL=trust-policy.d.ts.map
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Bootstraps a new orchestrator by connecting to an existing cluster via
5
5
  * Platform relay or direct peer, receiving an encrypted config bundle, and
6
- * writing the local YAML config.
6
+ * writing an env file the orchestrator boots from.
7
7
  *
8
8
  * Usage:
9
9
  * kici-admin join --token kici_join_v1.xxx.yyy --platform wss://api.kici.dev/ws --api-key KEY
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Scheduled orchestrator database backups.
3
+ *
4
+ * Renders and registers a `kici-<instance>-db-backup` service + timer pair that
5
+ * runs `kici-admin db backup --output-dir <dir> --keep <n>` on a calendar
6
+ * schedule. systemd gets a `.service` plus a `.timer`; launchd gets one plist
7
+ * with a `StartCalendarInterval`.
8
+ *
9
+ * Windows and Compose are refused: `sc.exe` has no timer concept, and a
10
+ * Compose orchestrator runs in a container whose host scheduler KiCI does not
11
+ * own. Both refusals name the platform-native equivalent.
12
+ */
13
+ import type { ServicePlatform } from './types.js';
14
+ /** Default retention: keep the last week of daily dumps. */
15
+ export declare const BACKUP_TIMER_DEFAULT_KEEP = 7;
16
+ /** Default calendar spec — midnight every day, on both platforms. */
17
+ export declare const BACKUP_TIMER_DEFAULT_SCHEDULE = "daily";
18
+ export interface BackupTimerConfig {
19
+ /** Orchestrator instance name the timer backs up (e.g. `kici-orchestrator`). */
20
+ serviceName: string;
21
+ /** Calendar spec: a systemd `OnCalendar=` value, or `daily` / `HH:MM`. */
22
+ schedule: string;
23
+ /** Directory the timestamped dumps are written to. */
24
+ outputDir: string;
25
+ /** How many dumps to retain after each run. */
26
+ keep: number;
27
+ /** Node binary the scheduled unit runs. */
28
+ nodeBinPath: string;
29
+ /** `kici-admin` entry script the scheduled unit runs. */
30
+ cliScriptPath: string;
31
+ /** Env file carrying `KICI_DATABASE_URL` for the scheduled run. */
32
+ envFilePath: string;
33
+ /** User-level (`systemctl --user`, LaunchAgent) vs system-level. */
34
+ isUserLevel: boolean;
35
+ /** Account a system-level unit runs as. */
36
+ user?: string;
37
+ }
38
+ /**
39
+ * `kici-orchestrator` -> `kici-orchestrator-db-backup`. A name that already
40
+ * carries the `kici-` prefix keeps exactly one, so the unit is always
41
+ * `kici-<instance>-db-backup`.
42
+ */
43
+ export declare function backupTimerName(serviceName: string): string;
44
+ /** The argv the scheduled unit executes, after the node binary. */
45
+ export declare function backupCommandArgs(config: BackupTimerConfig): string[];
46
+ /**
47
+ * Explain why a platform cannot carry a KiCI-managed backup timer, and what to
48
+ * use instead. Returns null when the platform is supported.
49
+ */
50
+ export declare function unsupportedPlatformMessage(platform: ServicePlatform, outputDir: string, keep: number): string | null;
51
+ /** Throw the {@link unsupportedPlatformMessage} refusal, if there is one. */
52
+ export declare function assertTimerPlatformSupported(platform: ServicePlatform, outputDir: string, keep: number): void;
53
+ /** Render the systemd `.service` unit the timer triggers. */
54
+ export declare function renderSystemdBackupService(config: BackupTimerConfig): string;
55
+ /** Render the systemd `.timer` unit that schedules the backup service. */
56
+ export declare function renderSystemdBackupTimer(config: BackupTimerConfig): string;
57
+ /**
58
+ * Translate a calendar spec into the launchd `StartCalendarInterval` fields.
59
+ * launchd has no calendar-expression parser, so only the two forms that map
60
+ * cleanly are accepted; anything else is refused by name.
61
+ */
62
+ export declare function parseLaunchdSchedule(schedule: string): {
63
+ hour: number;
64
+ minute: number;
65
+ };
66
+ /**
67
+ * Read one key out of a `KEY=value` env file. Returns undefined when the file
68
+ * or the key is absent — the caller decides whether that is fatal.
69
+ *
70
+ * The value is trimmed and stripped of surrounding quotes, because these files
71
+ * are hand-edited as often as they are generated: a path written as
72
+ * `"/etc/kici/scalers.yaml"` or left with a trailing space names the same file
73
+ * and must read back the same way. Only the outer quote characters go — an
74
+ * embedded `=` stays, so `B=two=three` still reads as `two=three`.
75
+ *
76
+ * A duplicated key takes its **last** occurrence, because that is the value the
77
+ * running process holds: systemd's `EnvironmentFile=` applies each assignment in
78
+ * order, so the last one wins. Reporting an earlier one would name a path or a
79
+ * database the service never used, which is the opposite of what these readers
80
+ * are for. Every other env-file reader in the repo takes the last occurrence too.
81
+ */
82
+ export declare function readEnvValue(content: string, key: string): string | undefined;
83
+ /** Render the launchd plist that schedules the backup run. */
84
+ export declare function renderLaunchdBackupPlist(config: BackupTimerConfig, env: Record<string, string>): string;
85
+ /** Filesystem + process seams, so install/uninstall are unit-testable. */
86
+ export interface TimerIo {
87
+ mkdirp: (dir: string) => void;
88
+ writeFile: (file: string, content: string) => void;
89
+ readFile: (file: string) => string;
90
+ removeFile: (file: string) => void;
91
+ run: (bin: string, args: string[]) => void;
92
+ }
93
+ export declare const defaultTimerIo: TimerIo;
94
+ /** Directory holding the generated systemd units for this privilege level. */
95
+ export declare function systemdUnitDir(isUserLevel: boolean): string;
96
+ /** Path of the generated launchd plist for this privilege level. */
97
+ export declare function launchdPlistPath(name: string, isUserLevel: boolean): string;
98
+ /** Install the backup timer for the detected platform. Returns the files written. */
99
+ export declare function installBackupTimer(platform: ServicePlatform, config: BackupTimerConfig, io?: TimerIo): {
100
+ unitName: string;
101
+ files: string[];
102
+ };
103
+ /** Remove the backup timer for the detected platform. Returns the files removed. */
104
+ export declare function uninstallBackupTimer(platform: ServicePlatform, config: Pick<BackupTimerConfig, 'serviceName' | 'isUserLevel' | 'outputDir' | 'keep'>, io?: TimerIo): {
105
+ unitName: string;
106
+ files: string[];
107
+ };
108
+ //# sourceMappingURL=backup-timer.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Where the generated compose file for a service lives: beside its env file,
3
+ * named for the service.
4
+ *
5
+ * This sits in its own module because two callers need it and they cannot
6
+ * share `compose.ts` — that module also detects a container runtime, which the
7
+ * env-file writer has no business pulling in. One definition means the naming
8
+ * convention is written down once rather than re-derived at each site.
9
+ */
10
+ export declare function composeFilePath(envFilePath: string, serviceName: string): string;
11
+ //# sourceMappingURL=compose-path.d.ts.map
@@ -20,7 +20,29 @@ type Runtime = 'podman' | 'docker';
20
20
  */
21
21
  export declare function detectRuntime(): Runtime;
22
22
  export declare class ComposeServiceManager implements ServiceManager {
23
+ readonly platform: 'compose';
23
24
  private runtime;
25
+ /**
26
+ * True when the container runtime's own registry answers. A host with
27
+ * neither podman nor docker cannot hold a running compose install, and a
28
+ * host whose runtime is momentarily down still holds its installs — so
29
+ * discovery reads a `false` here as "do not touch this platform's index
30
+ * rows" rather than as an empty scan.
31
+ *
32
+ * Both halves are needed, and the second is the one that costs something.
33
+ * `<runtime> compose version` is answered by the compose client alone: with
34
+ * dockerd stopped it still exits 0 (measured), so a client-only probe reports
35
+ * available, `list()` then gets an error it cannot distinguish from an empty
36
+ * registry, and the reconcile prunes live rows. The `RUNTIME_PROBE_TIMEOUT_MS`
37
+ * bound does not cover this — the client answers instantly whether or not the
38
+ * daemon is there, so there is nothing for a timeout to catch.
39
+ *
40
+ * `ps -q` is the cheapest question only the registry can answer (0.02s
41
+ * docker / 0.07s podman on a healthy host), and it fails the same two ways
42
+ * the scan itself would: non-zero for a daemon that is down, and a timeout
43
+ * kill for one that is hung.
44
+ */
45
+ available(): Promise<boolean>;
24
46
  /** Get the runtime binary (`podman` or `docker`), detecting it if not already done. */
25
47
  private getRuntime;
26
48
  /** Run a compose command for the given service config. */
@@ -1,12 +1,3 @@
1
- /**
2
- * Deployment-identity env injection for the orchestrator installer.
3
- *
4
- * The orchestrator reports its own deployment shape (systemd / launchd /
5
- * windows / compose) in `source.register` so the dashboard can build the
6
- * correct `kici-admin` invocation. The shape is only knowable at install time
7
- * (a running container can't learn its own container name), so the installer
8
- * injects it into the orchestrator's env file via these `KICI_DEPLOY_*` vars.
9
- */
10
1
  import type { ServicePlatform } from './types.js';
11
2
  /** Inputs the installer already has when it writes the env file. */
12
3
  export interface DeployEnvInput {
@@ -15,11 +6,19 @@ export interface DeployEnvInput {
15
6
  serviceName: string;
16
7
  /** Container runtime, only meaningful for the `compose` platform. */
17
8
  containerRuntime?: 'podman' | 'docker';
9
+ /**
10
+ * Absolute path of the env file the installer is writing. Stamped into the
11
+ * file itself because the running process cannot derive it: systemd's
12
+ * `EnvironmentFile=` delivers the variables and not the filename, and a
13
+ * compose container never sees the host path at all.
14
+ */
15
+ envFilePath: string;
18
16
  }
19
17
  /**
20
18
  * Build the `KICI_DEPLOY_*` env lines for a given deployment shape. The mode
21
- * line is always emitted; container name + runtime are emitted only for the
22
- * `compose` platform (the only shape with a container to `exec` into).
19
+ * and config-file lines are always emitted; the container name, runtime, and
20
+ * compose file are emitted only for the `compose` platform (the only shape
21
+ * with a container to `exec` into and a generated compose file to read).
23
22
  */
24
23
  export declare function buildDeployEnvLines(input: DeployEnvInput): string[];
25
24
  /**
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Single-variable upsert for service env-file content.
3
+ *
4
+ * The sibling {@link ./deploy-env.js} upserts a whole `KICI_DEPLOY_*` block by
5
+ * stripping the prefix and re-appending it. A variable an operator may also
6
+ * have set by hand needs the narrower shape: replace the assignment where it
7
+ * already sits, so the comment above it still describes the value below it.
8
+ */
9
+ /**
10
+ * Whether `content` carries an uncommented assignment of `name`.
11
+ *
12
+ * A `#`-prefixed mention does not count: it is documentation, and the readers
13
+ * that matter (systemd's `EnvironmentFile=`, `selectServerEntry`) skip it.
14
+ */
15
+ export declare function hasEnvAssignment(content: string, name: string): boolean;
16
+ /**
17
+ * Return `content` with `name` assigned `value`.
18
+ *
19
+ * Every uncommented assignment of `name` is rewritten in place; a file that
20
+ * declares it nowhere gains the assignment on its last line. Commented lines
21
+ * are left alone, so a stub's explanatory `# KICI_MODE=...` survives.
22
+ */
23
+ export declare function upsertEnvAssignment(content: string, name: string, value: string): string;
24
+ //# sourceMappingURL=env-assignment.d.ts.map
@@ -14,8 +14,8 @@ export type { Component, InstanceManifest, IndexEntry, ResolveOptions, ResolvedI
14
14
  export { COMPONENTS, isComponent } from './instance/types.js';
15
15
  export { manifestFilename, manifestPath, readManifest, writeManifest, readKiciVersion, resolveVersionFromLaunchSpec, resolveNpmInstallTarget, type NpmInstallTarget, } from './instance/manifest.js';
16
16
  export { indexPath, readIndex, writeIndex, appendIndexEntry, removeIndexEntry, } from './instance/index-file.js';
17
- export { listInstances, resolveInstance, formatRefusal, InstanceNotFoundError, } from './instance/resolve.js';
18
- export type { ListedInstance, ResolveArgs, ListInstancesArgs } from './instance/resolve.js';
17
+ export { candidatePlatforms, listInstances, resolveInstance, resolveInstanceTarget, formatRefusal, InstanceNotFoundError, } from './instance/resolve.js';
18
+ export type { ListedInstance, ResolveArgs, ListInstancesArgs, ResolveTargetArgs, ResolvedTarget, } from './instance/resolve.js';
19
19
  import type { ServiceManager, ServicePlatform } from './types.js';
20
20
  /**
21
21
  * Create a ServiceManager for the given platform.
@@ -8,30 +8,49 @@
8
8
  * 3. CWD manifest → read ./.kici-<component>.json
9
9
  * 4. otherwise → refuse with a candidate list (throws)
10
10
  *
11
- * listInstances reconciles the on-disk index (cache) with the driver's
12
- * native scan (source of truth). The reconciled result is rewritten back to
11
+ * listInstances reconciles the on-disk index (cache) with the drivers'
12
+ * native scans (source of truth). The reconciled result is rewritten back to
13
13
  * the index to self-heal stale entries.
14
+ *
15
+ * Discovery is platform-plural and operation is platform-singular. A host can
16
+ * hold both a systemd install and a compose install, so finding an instance
17
+ * scans every candidate driver; acting on the one that was found uses the
18
+ * single driver its manifest names.
14
19
  */
15
- import type { DiscoveredInstance, ServiceManager } from '../types.js';
20
+ import type { DiscoveredInstance, ServiceManager, ServicePlatform } from '../types.js';
16
21
  import type { Component, ResolveOptions, ResolvedInstance } from './types.js';
17
22
  /** A reconciled view of a discovered instance: driver scan + (maybe) index entry. */
18
23
  export interface ListedInstance extends DiscoveredInstance {
19
24
  instanceDir?: string;
20
25
  source: 'index' | 'scan' | 'index+scan';
21
26
  }
27
+ /**
28
+ * The drivers that can plausibly hold an install on this host.
29
+ *
30
+ * `--platform` forces one driver, so an operator keeps an escape hatch to
31
+ * today's exact single-driver behaviour on every path. Otherwise the host's own
32
+ * init system is joined by compose, which runs on any host with a container
33
+ * runtime — a compose install on a systemd box is the case the single-driver
34
+ * scan could not see.
35
+ */
36
+ export declare function candidatePlatforms(override?: ServicePlatform): ServicePlatform[];
22
37
  export interface ListInstancesArgs {
23
38
  component: Component;
24
39
  isUserLevel: boolean;
25
40
  kiciRoot: string;
26
- manager: ServiceManager;
41
+ /**
42
+ * The drivers to scan. Omit to scan every candidate driver for this host.
43
+ * Each driver is authoritative only over its own platform's index rows.
44
+ */
45
+ managers?: ServiceManager[];
27
46
  }
28
47
  /**
29
- * Reconcile <kiciRoot>/instances.json with the driver's native scan, then
48
+ * Reconcile <kiciRoot>/instances.json with the drivers' native scans, then
30
49
  * rewrite the index so it mirrors the scanned instances that have a known
31
50
  * instanceDir. Two self-heal directions happen here:
32
51
  *
33
52
  * - backward: drop index entries whose unit no longer exists.
34
- * - forward: adopt an instanceDir the driver recovered from the unit marker
53
+ * - forward: adopt an instanceDir a driver recovered from the unit marker
35
54
  * when the index entry is missing or carries no dir — so a lost or emptied
36
55
  * index rebuilds itself from the init system on the next read.
37
56
  *
@@ -39,6 +58,11 @@ export interface ListInstancesArgs {
39
58
  * (via the X-KiCI-InstanceDir / KiCIInstanceDir / dev.kici.instance-dir / [KiCI-DIR]
40
59
  * markers), and the index is a pure cache. Returns the merged list filtered to
41
60
  * the requested component + isUserLevel.
61
+ *
62
+ * The rewrite is scoped to the platforms that were actually scanned. A driver
63
+ * speaks for its own platform's rows and for no others, so a systemd scan
64
+ * cannot delete a compose install's row, and a driver that could not reach its
65
+ * registry deletes nothing at all.
42
66
  */
43
67
  export declare function listInstances(args: ListInstancesArgs): Promise<ListedInstance[]>;
44
68
  /**
@@ -57,7 +81,11 @@ export interface ResolveArgs {
57
81
  opts: ResolveOptions;
58
82
  cwd: string;
59
83
  kiciRoot: string;
60
- manager: ServiceManager;
84
+ /**
85
+ * Every driver discovery should scan. Omit to scan every candidate driver
86
+ * for this host.
87
+ */
88
+ managers?: ServiceManager[];
61
89
  /**
62
90
  * Privilege scope to resolve against. Must match the caller's resolved
63
91
  * --system / --user-level decision so candidate lists and name lookups see
@@ -77,4 +105,40 @@ export declare function resolveInstance(args: ResolveArgs): Promise<ResolvedInst
77
105
  * When candidates exist, lists them with their instanceDir (or "(no manifest)").
78
106
  */
79
107
  export declare function formatRefusal(component: Component, candidates: ListedInstance[]): string;
108
+ /** What {@link resolveInstanceTarget} is asked. */
109
+ export interface ResolveTargetArgs {
110
+ component: Component;
111
+ opts: ResolveOptions;
112
+ cwd: string;
113
+ kiciRoot: string;
114
+ /**
115
+ * Privilege scope to resolve against. Must match the caller's resolved
116
+ * --system / --user-level decision.
117
+ */
118
+ isUserLevel: boolean;
119
+ /**
120
+ * The `--platform` flag. When set it forces the driver for both discovery and
121
+ * operation, which is the operator's escape hatch to a single-driver run.
122
+ */
123
+ platformOverride?: ServicePlatform;
124
+ /** Driver factory. Defaults to the service barrel's createServiceManager. */
125
+ createManager?: (platform: ServicePlatform) => Promise<ServiceManager>;
126
+ }
127
+ /** The instance a lifecycle command operates on, and the driver that can do it. */
128
+ export interface ResolvedTarget {
129
+ resolved: ResolvedInstance;
130
+ manager: ServiceManager;
131
+ /** The install's own platform — `platformOverride ?? manifest.platform`. */
132
+ platform: ServicePlatform;
133
+ }
134
+ /**
135
+ * Resolve the target instance and the driver that manages it.
136
+ *
137
+ * The question a lifecycle command asks is about the install, not the host: a
138
+ * compose install stays a compose install on a systemd box. So discovery scans
139
+ * every candidate driver, and the manifest that discovery produced then names
140
+ * the single driver the command operates through. The two need not be the same
141
+ * object, and only the second has to be right.
142
+ */
143
+ export declare function resolveInstanceTarget(args: ResolveTargetArgs): Promise<ResolvedTarget>;
80
144
  //# sourceMappingURL=resolve.d.ts.map
@@ -32,6 +32,21 @@ export interface InstanceManifest {
32
32
  installBase: string;
33
33
  createdAt: string;
34
34
  kiciVersion: string;
35
+ /**
36
+ * Installed version → the last applied migration at the moment that version
37
+ * was running.
38
+ *
39
+ * A rollback flips a symlink, and the previous release's static migration
40
+ * provider does not carry the newer migrations' names — so Kysely refuses to
41
+ * start against a ledger that records them, and the documented recovery
42
+ * produces a second outage. This map is what lets the switch check whether
43
+ * the database is ahead of the version it is about to activate.
44
+ *
45
+ * Merged, never replaced, at every version change. Absent on every instance
46
+ * installed before this field existed, which the guard treats as "unknown"
47
+ * and warns about rather than refusing.
48
+ */
49
+ migrationHeads?: Record<string, string>;
35
50
  }
36
51
  /** A row in `<kiciRoot>/instances.json`. */
37
52
  export interface IndexEntry {
@@ -6,7 +6,14 @@
6
6
  * user-level (~/Library/LaunchAgents/) agents.
7
7
  */
8
8
  import type { ServiceConfig, ServiceManager, ServiceStatus, LogOptions, DiscoveredInstance, LaunchSpec } from './types.js';
9
+ /**
10
+ * `dev.kici.my-orch` -> `my-orch`. Returns the input unchanged when it carries
11
+ * no known prefix, so a hand-written plist is still discoverable under
12
+ * whatever label it uses.
13
+ */
14
+ export declare function stripLabelPrefix(label: string): string;
9
15
  export declare class LaunchdServiceManager implements ServiceManager {
16
+ readonly platform: 'launchd';
10
17
  /** Build the launchd label for a service. */
11
18
  private label;
12
19
  /** Resolve the log directory based on service level. */
@@ -18,6 +18,7 @@ import type { DiscoveredInstance, ServiceConfig, ServiceManager, ServiceStatus,
18
18
  */
19
19
  export declare function isLingerEnabled(user: string): boolean;
20
20
  export declare class SystemdServiceManager implements ServiceManager {
21
+ readonly platform: 'systemd';
21
22
  /**
22
23
  * Generate a systemd unit file from a service config.
23
24
  * Visible for testing.
@@ -136,6 +136,36 @@ export interface DiscoveredInstance {
136
136
  * Each method operates on a service identified by the ServiceConfig.
137
137
  */
138
138
  export interface ServiceManager {
139
+ /**
140
+ * The platform this driver manages. Discovery scans several drivers at once
141
+ * (a host can hold both a systemd install and a compose install), and each
142
+ * driver's scan is authoritative only over its own platform's index rows —
143
+ * including when that scan comes back empty. An empty scan carries no row to
144
+ * read a platform off, so the driver states it here.
145
+ */
146
+ readonly platform: ServicePlatform;
147
+ /**
148
+ * Whether this driver can inspect its registry right now. A driver that
149
+ * answers `false` is excluded from discovery, and the index rows for its
150
+ * platform are left untouched — an empty scan from a driver whose registry is
151
+ * unreachable is not evidence that the instances are gone.
152
+ *
153
+ * Absent means always available: an init system a driver targets is either
154
+ * present or the driver is not a candidate for the host at all — which is
155
+ * why the two filesystem-backed drivers (systemd, launchd) omit it and let
156
+ * an unreadable unit directory throw out of `list()` instead. Compose and
157
+ * Windows implement it because their registry is a service that can be down
158
+ * or hung while the host and the client binary are fine.
159
+ *
160
+ * A driver that implements it MUST probe the registry itself, not the client
161
+ * that talks to it: `<runtime> compose version` and `where powershell` both
162
+ * answer on a host whose registry is down, which is the shape that reported
163
+ * available, scanned empty, and pruned live rows.
164
+ *
165
+ * Discovery calls this INSIDE its own error handling, so a probe that
166
+ * rejects drops only this driver.
167
+ */
168
+ available?(): Promise<boolean>;
139
169
  /** Install and register the service with the init system. */
140
170
  install(config: ServiceConfig): Promise<void>;
141
171
  /** Remove the service registration (preserves config/data). */
@@ -160,6 +190,12 @@ export interface ServiceManager {
160
190
  *
161
191
  * `isUserLevel` selects user vs system scope on platforms where that matters
162
192
  * (systemd, launchd); ignored on Windows/compose.
193
+ *
194
+ * An empty array means the registry answered and holds no KiCI services —
195
+ * the reconcile prunes this platform's index rows on that basis. A driver
196
+ * that could not read its registry must therefore throw rather than return
197
+ * `[]`; discovery drops a throwing driver exactly as it drops one whose
198
+ * `available()` said false.
163
199
  */
164
200
  list(isUserLevel: boolean): Promise<DiscoveredInstance[]>;
165
201
  /**
@@ -8,6 +8,22 @@
8
8
  */
9
9
  import type { ServiceManager, ServiceConfig, ServiceStatus, LogOptions, DiscoveredInstance, LaunchSpec } from './types.js';
10
10
  export declare class WindowsServiceManager implements ServiceManager {
11
+ readonly platform: 'windows';
12
+ /**
13
+ * True when the service registry this driver reads actually answers.
14
+ *
15
+ * Probes the same subsystem `list()` reads — CIM's `Win32_Service` — with a
16
+ * filter that cannot match, so a healthy host answers with an empty result
17
+ * set and exits 0. It deliberately does NOT probe for the presence of
18
+ * `powershell` or `sc.exe`: a client-only probe is exactly the defect this
19
+ * driver's compose sibling had, where the client answered on a host whose
20
+ * registry was down and the empty scan then pruned live rows.
21
+ *
22
+ * A name filter rather than a well-known service name: no service is assumed
23
+ * to exist, and no display string is parsed, so the probe carries no locale
24
+ * or Windows-edition assumption.
25
+ */
26
+ available(): Promise<boolean>;
11
27
  /**
12
28
  * Check whether a Windows service is currently registered.
13
29
  * `sc.exe query` exits non-zero (execSync throws) when the service does
@@ -14,6 +14,15 @@ export interface OrchestratorSourceHint {
14
14
  privateKeyPath: string;
15
15
  webhookSecret?: string;
16
16
  }
17
+ /** Inputs the install command threads into the wizard. */
18
+ export interface OrchestratorWizardOptions {
19
+ /**
20
+ * Preselect the operating-mode prompt (from `orchestrator install --mode`).
21
+ * Defaults to `hybrid`, the recommended shape: the Platform relay plus this
22
+ * orchestrator's own direct webhook ingress.
23
+ */
24
+ defaultMode?: OrchestratorMode;
25
+ }
17
26
  /** Config produced by the orchestrator wizard. */
18
27
  interface OrchestratorInstallConfig {
19
28
  mode: OrchestratorMode;
@@ -54,15 +63,15 @@ export declare const DEFAULT_PLATFORM_RELAY_URL = "wss://api.kici.dev/ws";
54
63
  * Run the interactive orchestrator setup wizard.
55
64
  *
56
65
  * Asks only essential questions per the user decision:
57
- * 1. Mode (platform/hybrid/observed/independent)
66
+ * 1. Mode (hybrid/platform/observed/independent)
58
67
  * 2. Database URL
59
68
  * 3. Port
60
69
  * 4. Secrets encryption key
61
70
  * 5. Bootstrap admin token (for kici-admin authentication)
62
71
  * 6. Platform URL + token (if platform/hybrid/observed mode), plus the public
63
- * webhook base URL when observed
72
+ * webhook base URL -- required in observed mode, optional in hybrid
64
73
  * 7. Webhook secret (if hybrid/independent mode)
65
74
  */
66
- export declare function runOrchestratorWizard(): Promise<OrchestratorInstallConfig>;
75
+ export declare function runOrchestratorWizard(opts?: OrchestratorWizardOptions): Promise<OrchestratorInstallConfig>;
67
76
  export {};
68
77
  //# sourceMappingURL=orchestrator-wizard.d.ts.map
@@ -12,6 +12,13 @@ export declare function promptPort(defaultPort: number): Promise<number>;
12
12
  export declare function promptConfirm(message: string, defaultValue?: boolean): Promise<boolean>;
13
13
  /** Prompt for a URL with http(s):// validation. */
14
14
  export declare function promptUrl(message: string, defaultValue?: string): Promise<string>;
15
+ /**
16
+ * Prompt for an optional URL: an empty answer returns undefined, any other
17
+ * answer must carry an http(s)/ws(s) scheme. Used where a URL genuinely may be
18
+ * left unset (the hybrid public ingress base), which `promptUrl` cannot express
19
+ * because its validator rejects a blank answer.
20
+ */
21
+ export declare function promptOptionalUrl(message: string): Promise<string | undefined>;
15
22
  /** Prompt for a secret/password (masked input). */
16
23
  export declare function promptSecret(message: string): Promise<string>;
17
24
  /** Prompt for a selection from a list of options. */