@kici-dev/orchestrator 0.1.21 → 0.1.23

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 (53) hide show
  1. package/dist/agent/dispatcher.d.ts +44 -0
  2. package/dist/agent/host-roster-reaper.d.ts +2 -1
  3. package/dist/agent/host-roster.d.ts +57 -3
  4. package/dist/agent/token-store.d.ts +29 -0
  5. package/dist/approvals/step-approval-bridge.d.ts +5 -0
  6. package/dist/cli/commands/source-manifest.d.ts +8 -0
  7. package/dist/cli/service/compose.d.ts +13 -0
  8. package/dist/cli/service/deploy-env.d.ts +31 -0
  9. package/dist/cli.js +687 -219
  10. package/dist/config.d.ts +6 -0
  11. package/dist/dashboard/needs-edges.d.ts +4 -3
  12. package/dist/db/migrations/047_needs_run_on.d.ts +4 -0
  13. package/dist/db/migrations/048_host_reboot_pending.d.ts +19 -0
  14. package/dist/db/migrations/049_held_runs_payload.d.ts +14 -0
  15. package/dist/db/migrations/050_sources_slug.d.ts +17 -0
  16. package/dist/db/migrations/051_binding_host_pattern.d.ts +18 -0
  17. package/dist/db/migrations/052_host_reach_metadata.d.ts +19 -0
  18. package/dist/db/migrations/053_agent_token_single_use.d.ts +17 -0
  19. package/dist/db/types.d.ts +50 -4
  20. package/dist/deployment/deployment-identity.d.ts +9 -0
  21. package/dist/entry-helpers.d.ts +7 -0
  22. package/dist/environments/binding-store.d.ts +12 -3
  23. package/dist/environments/held-runs.d.ts +31 -1
  24. package/dist/github-app-name-refresher/github-app-name-refresher.d.ts +77 -0
  25. package/dist/index.js +21 -2
  26. package/dist/metrics/prometheus.d.ts +2 -0
  27. package/dist/orchestrator-core.d.ts +13 -1
  28. package/dist/pipeline/decorating-secret-resolver.d.ts +32 -0
  29. package/dist/pipeline/dispatch-matched-workflow.d.ts +132 -5
  30. package/dist/pipeline/install-secrets-resolver.d.ts +2 -2
  31. package/dist/pipeline/needs-scheduler.d.ts +22 -13
  32. package/dist/pipeline/processor.d.ts +2 -2
  33. package/dist/pipeline/test-pipeline.d.ts +37 -59
  34. package/dist/providers/github/manifest.d.ts +25 -0
  35. package/dist/reporting/log-writer.d.ts +19 -0
  36. package/dist/routes/admin-sources.d.ts +7 -0
  37. package/dist/scaler/manager.d.ts +17 -0
  38. package/dist/secrets/secret-resolver.d.ts +33 -5
  39. package/dist/server.js +21664 -19907
  40. package/dist/sources/source-store.d.ts +4 -0
  41. package/dist/sources/source-validator.d.ts +2 -0
  42. package/dist/stale-detector/reboot-deadline-sweep.d.ts +29 -0
  43. package/dist/standalone.js +25263 -24075
  44. package/dist/ws/agent-handler.d.ts +11 -6
  45. package/dist/ws/bringup-api.d.ts +76 -0
  46. package/dist/ws/dashboard-fleet-handler.d.ts +43 -0
  47. package/dist/ws/dashboard-fleet-write-handler.d.ts +60 -0
  48. package/dist/ws/fleet-runs-on-all.d.ts +19 -0
  49. package/dist/ws/platform-client.d.ts +16 -1
  50. package/dist/ws/test-relay-handlers.d.ts +13 -2
  51. package/installer-image-digests.json +3 -3
  52. package/package.json +4 -4
  53. package/sbom.spdx.json +50 -50
@@ -13,6 +13,18 @@ export interface DispatchMetrics {
13
13
  /** Set the queue depth gauge. */
14
14
  setQueueDepth(depth: number): void;
15
15
  }
16
+ /**
17
+ * Narrow slice of `HostRosterStore` the dispatcher needs for the host-restart
18
+ * flow. Kept as an interface so the dispatcher doesn't import the full store
19
+ * (which pulls in engine label matchers) and to make the reboot logic testable
20
+ * with a tiny in-memory stub.
21
+ */
22
+ export interface HostRosterRebootStore {
23
+ /** True when the host's reboot-pending deadline is still in the future. */
24
+ isRebootPending(agentId: string, nowMs: number): Promise<boolean>;
25
+ /** Clear the reboot-pending flag (down-then-up release on reconnect). */
26
+ clearRebootPending(agentId: string): Promise<void>;
27
+ }
16
28
  /**
17
29
  * Result of a dispatch attempt.
18
30
  */
@@ -120,6 +132,8 @@ export declare class Dispatcher {
120
132
  private readonly recoveringJobs;
121
133
  /** Grace period = 2x max reconnection delay. */
122
134
  private get gracePeriodMs();
135
+ /** Host roster store for the reboot-pending gate; undefined ⇒ flow inert. */
136
+ private readonly rosterStore?;
123
137
  constructor(deps: {
124
138
  registry: AgentRegistry;
125
139
  queue: JobQueue;
@@ -139,6 +153,14 @@ export declare class Dispatcher {
139
153
  getAckTimeoutMs?: (job: QueuedJob) => Promise<number>;
140
154
  /** Cancel + disconnect an agent whose dispatch ack deadline expired. */
141
155
  onAckTimeout?: (agentId: string, jobId: string, runId: string) => void;
156
+ /**
157
+ * Host roster store, used for the workflow-level host-restart flow: gate the
158
+ * pinned-drain off for a reboot-pending agent, and treat a reboot-pending
159
+ * agent's disconnect as the expected reboot (complete its in-flight job
160
+ * success rather than starting the recovery-fail timer). Omitted ⇒ the
161
+ * reboot-pending behavior is inert (every host reads not-pending).
162
+ */
163
+ rosterStore?: HostRosterRebootStore;
142
164
  });
143
165
  /**
144
166
  * Dispatch a job to a matching agent, or queue it if none available.
@@ -181,6 +203,20 @@ export declare class Dispatcher {
181
203
  * Dequeues matching jobs from the queue while the agent has capacity,
182
204
  * calling onDispatch for each.
183
205
  */
206
+ /**
207
+ * Drop agents whose reboot-pending flag is set from a candidate list. Used by
208
+ * the label-routed dispatch path so a post-restart job is never sent to a host
209
+ * that is about to reboot. No-op when no roster store is wired.
210
+ */
211
+ private filterRebootPending;
212
+ /**
213
+ * Release a reboot-pending host on its real reconnect (down-then-up). Clears
214
+ * the persisted flag so the very next `onAgentAvailable` drain dispatches the
215
+ * held post-restart job. Call this ONLY from the (re-)register path — a fresh
216
+ * connection after the box rebooted — never from a still-connected drain
217
+ * trigger (job completion / agent.status), which must keep the gate closed.
218
+ */
219
+ releaseRebootPending(agentId: string): Promise<void>;
184
220
  onAgentAvailable(agentId: string): Promise<void>;
185
221
  /** Record that a job began executing on its agent. */
186
222
  markJobStarted(jobId: string): void;
@@ -239,6 +275,14 @@ export declare class Dispatcher {
239
275
  * asynchronously when recovery timers expire).
240
276
  */
241
277
  onAgentDisconnect(agentId: string): Promise<string[]>;
278
+ /**
279
+ * Reboot-pending agent disconnected: complete its in-flight started job(s) as
280
+ * success (the restart job, which already reported success, may already be
281
+ * terminal — `markCompleted` is a no-op / guarded in that case). Never starts
282
+ * a recovery timer. Untracks the jobs so the reconnect does not reconcile a
283
+ * phantom in-flight set.
284
+ */
285
+ private completeInFlightForReboot;
242
286
  /** Drop grace-window entries owned by an agent. */
243
287
  private cleanupGraceEntriesForAgent;
244
288
  /**
@@ -1,6 +1,6 @@
1
1
  import type { HostRosterStore } from './host-roster.js';
2
2
  export interface HostRosterReaperOptions {
3
- store: Pick<HostRosterStore, 'reapEphemeralPastTtl' | 'countStaticUnreachable'>;
3
+ store: Pick<HostRosterStore, 'reapEphemeralPastTtl' | 'countStaticUnreachable' | 'listExpiredRebootPending' | 'clearRebootPending'>;
4
4
  ttlMs: number;
5
5
  /** Grace window for the connected-but-stale case in `countStaticUnreachable`. */
6
6
  graceMs: number;
@@ -30,6 +30,7 @@ export declare class HostRosterReaper {
30
30
  private readonly graceMs;
31
31
  private readonly scanIntervalMs;
32
32
  private readonly setUnreachableGauge;
33
+ private readonly rebootSweep;
33
34
  private timer;
34
35
  private isLeader;
35
36
  constructor(opts: HostRosterReaperOptions);
@@ -3,6 +3,19 @@ import { type HostInventoryEntry, type InventorySelector, type LabelMatcher } fr
3
3
  import type { Database, HostRosterRow } from '../db/types.js';
4
4
  /** Typed host-vars bag carried by roster rows (`string | number | boolean`). */
5
5
  export type HostProperties = Record<string, string | number | boolean>;
6
+ /**
7
+ * Pre-agent reach metadata for a declared host — how to SSH to it for bootstrap
8
+ * bring-up before it has a KiCI agent. All fields nullable: a host with no reach
9
+ * metadata cannot be bootstrapped. `sshKeySecret` is a scoped-secret ref
10
+ * (`scope/key`) the orchestrator resolves server-side; the key never lives here.
11
+ */
12
+ export interface HostReach {
13
+ agentId: string;
14
+ address: string | null;
15
+ sshUser: string | null;
16
+ sshPort: number | null;
17
+ sshKeySecret: string | null;
18
+ }
6
19
  /**
7
20
  * Derived (read-time) status of a roster host. Never a stored mutable column:
8
21
  * status is computed from the shared `last_seen` + `connected_instance_id` so
@@ -96,13 +109,36 @@ export declare class HostRosterStore {
96
109
  markDisconnected(agentId: string, instanceId: string): Promise<void>;
97
110
  /** Coarse heartbeat stamp — same owner-guard as markDisconnected. */
98
111
  stampLastSeen(agentId: string, instanceId: string): Promise<void>;
99
- /** Operator pre-declare of a static host before its agent dials in. */
112
+ /**
113
+ * Operator pre-declare of a static host. A re-declare of an existing agent_id
114
+ * converges the operator-owned columns (labels, hostname, reach,
115
+ * host_properties) to the newly-declared values while preserving the
116
+ * agent-reported liveness/identity columns (connected_instance_id, last_seen,
117
+ * platform, arch, lifecycle_class, token_id, reboot_pending_until). The
118
+ * optional reach fields (`address`/`sshUser`/`sshPort`/`sshKeySecret`) carry
119
+ * how to SSH to the host for bootstrap bring-up. Each operator field is
120
+ * preserve-on-omit: an omitted field keeps the existing column value.
121
+ *
122
+ * Returns `{ created: true }` when the declare inserted a new row,
123
+ * `{ created: false }` when it converged an existing one.
124
+ */
100
125
  declareStatic(input: {
101
126
  agentId: string;
102
- labels: string[];
127
+ labels?: string[];
103
128
  hostname?: string;
104
129
  properties?: HostProperties;
105
- }): Promise<void>;
130
+ address?: string;
131
+ sshUser?: string;
132
+ sshPort?: number;
133
+ sshKeySecret?: string;
134
+ }): Promise<{
135
+ created: boolean;
136
+ }>;
137
+ /**
138
+ * Read a host's pre-agent reach metadata. Returns null when the host is not
139
+ * in the roster; a host declared without reach fields reads back with nulls.
140
+ */
141
+ getReach(agentId: string): Promise<HostReach | null>;
106
142
  get(agentId: string): Promise<HostRosterRow | null>;
107
143
  listAll(): Promise<HostRosterRow[]>;
108
144
  /**
@@ -139,7 +175,25 @@ export declare class HostRosterStore {
139
175
  * depends on `graceMs`).
140
176
  */
141
177
  countStaticUnreachable(graceMs: number): Promise<number>;
178
+ /** Remove a host from the roster by agent id. Returns rows deleted. */
179
+ removeStatic(agentId: string): Promise<number>;
142
180
  /** Delete ephemeral rows whose last_seen is older than ttl. Returns count. */
143
181
  reapEphemeralPastTtl(ttlMs: number): Promise<number>;
182
+ /**
183
+ * Mark a host as about-to-reboot until `until`. Set by the `host.requestReboot`
184
+ * API handler when an agent's `restartHost()` step runs. While the value is in
185
+ * the future, the host's disconnect is the expected reboot and its pinned
186
+ * post-restart job is held.
187
+ */
188
+ setRebootPending(agentId: string, until: Date): Promise<void>;
189
+ /** Clear the reboot-pending flag (on reconnect, deadline expiry, or cancel). */
190
+ clearRebootPending(agentId: string): Promise<void>;
191
+ /** True when the host has a reboot-pending deadline still in the future at `nowMs`. */
192
+ isRebootPending(agentId: string, nowMs: number): Promise<boolean>;
193
+ /**
194
+ * Agent ids whose reboot-pending deadline has passed at `nowMs`. The deadline
195
+ * sweep clears these; the held post-restart job then hits the queue timeout.
196
+ */
197
+ listExpiredRebootPending(nowMs: number): Promise<string[]>;
144
198
  }
145
199
  //# sourceMappingURL=host-roster.d.ts.map
@@ -41,6 +41,35 @@ export declare class AgentTokenStore {
41
41
  * @returns The plaintext token (shown once).
42
42
  */
43
43
  createEphemeral(agentId: string, labels: string[], ttlMs: number): Promise<string>;
44
+ /**
45
+ * Mint a single-use, short-TTL bootstrap token for an init-runner bring-up.
46
+ *
47
+ * The token is stored as `agent_type: 'ephemeral'` (so the existing expiry
48
+ * cleanup reaps it) and is bound to exactly the init-runner label set. It is
49
+ * single-use: `consumeBootstrapToken` sets `consumed_at` on first
50
+ * `agent.register`, and a second register is rejected. A leaked token is
51
+ * therefore inert after enrollment AND after its short TTL elapses.
52
+ *
53
+ * @param targetAgentId - The agent id the init-runner will register as.
54
+ * @param ttlMs - Time-to-live in milliseconds (short, ~10 min).
55
+ * @param labels - The init-runner labels the token authorizes.
56
+ * @returns The plaintext token (shown once) and the DB row ID.
57
+ */
58
+ mintBootstrapToken(opts: {
59
+ targetAgentId: string;
60
+ ttlMs: number;
61
+ labels: string[];
62
+ }): Promise<{
63
+ token: string;
64
+ id: string;
65
+ }>;
66
+ /**
67
+ * Consume a single-use bootstrap token by id. Sets `consumed_at` only when it
68
+ * is currently null, so the first register wins and any subsequent attempt
69
+ * with the same token returns false (already consumed). Returns true when
70
+ * this call performed the consumption.
71
+ */
72
+ consumeBootstrapToken(id: string): Promise<boolean>;
44
73
  /**
45
74
  * Validate a plaintext token against stored hashes.
46
75
  *
@@ -17,6 +17,11 @@ export interface StepApprovalRequest {
17
17
  reason: string;
18
18
  /** Per-gate timeout (seconds); falls back to the org default. */
19
19
  timeoutSeconds?: number;
20
+ /** Computed drift payload for a `when: 'drift'` gate; persisted on the hold. */
21
+ payload?: {
22
+ summaryMarkdown: string;
23
+ drift: unknown;
24
+ };
20
25
  }
21
26
  /** Dependencies injected into the bridge. */
22
27
  export interface StepApprovalBridgeDeps {
@@ -17,6 +17,14 @@ export interface ManifestSetupOptions {
17
17
  name: string;
18
18
  noBrowser?: boolean;
19
19
  githubOrg?: string;
20
+ /**
21
+ * Self-hosted webhook URL override. When set, it is baked verbatim into the
22
+ * App manifest's `hook_attributes.url` and the platform-mode webhook-URL
23
+ * resolution is skipped — so the flow works even where the auto-resolved KiCI
24
+ * platform URL is unavailable. KiCI adds no ingress at this URL; the operator
25
+ * owns delivery. Advanced/self-hosted only.
26
+ */
27
+ webhookUrl?: string;
20
28
  }
21
29
  /** Injectable boundary so unit tests can stub GitHub + the browser + stdin. */
22
30
  export interface ManifestSetupDeps {
@@ -7,6 +7,18 @@
7
7
  * platform with Docker or Podman installed.
8
8
  */
9
9
  import type { ServiceManager, ServiceConfig, ServiceStatus, LogOptions, DiscoveredInstance, LaunchSpec } from './types.js';
10
+ /** Container runtime binary (`podman` or `docker`); compose form is `${runtime} compose`. */
11
+ type Runtime = 'podman' | 'docker';
12
+ /**
13
+ * Detect which container runtime is available.
14
+ * Tries podman compose first, then docker compose.
15
+ *
16
+ * Exported so the orchestrator installer can resolve the runtime once when
17
+ * injecting the `KICI_DEPLOY_CONTAINER_RUNTIME` env var for a compose deploy.
18
+ *
19
+ * @throws When neither runtime is found.
20
+ */
21
+ export declare function detectRuntime(): Runtime;
10
22
  export declare class ComposeServiceManager implements ServiceManager {
11
23
  private runtime;
12
24
  /** Get the runtime binary (`podman` or `docker`), detecting it if not already done. */
@@ -24,4 +36,5 @@ export declare class ComposeServiceManager implements ServiceManager {
24
36
  list(isUserLevel: boolean): Promise<DiscoveredInstance[]>;
25
37
  readLaunchSpec(_config: ServiceConfig): Promise<LaunchSpec | null>;
26
38
  }
39
+ export {};
27
40
  //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1,31 @@
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
+ import type { ServicePlatform } from './types.js';
11
+ /** Inputs the installer already has when it writes the env file. */
12
+ export interface DeployEnvInput {
13
+ platform: ServicePlatform;
14
+ /** The service / container name the installer assigns (= ServiceConfig.name). */
15
+ serviceName: string;
16
+ /** Container runtime, only meaningful for the `compose` platform. */
17
+ containerRuntime?: 'podman' | 'docker';
18
+ }
19
+ /**
20
+ * 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).
23
+ */
24
+ export declare function buildDeployEnvLines(input: DeployEnvInput): string[];
25
+ /**
26
+ * Append the freshly-computed deploy lines to existing env-file content,
27
+ * idempotently: any pre-existing `KICI_DEPLOY_*` line is stripped first so a
28
+ * re-install / upgrade heals the shape rather than duplicating it.
29
+ */
30
+ export declare function upsertDeployEnvLines(existingContent: string, lines: string[]): string;
31
+ //# sourceMappingURL=deploy-env.d.ts.map