@kici-dev/orchestrator 0.1.19 → 0.1.21

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 (41) hide show
  1. package/dist/agent/host-roster.d.ts +36 -1
  2. package/dist/agent/registry.d.ts +22 -0
  3. package/dist/app.d.ts +5 -0
  4. package/dist/cli/commands/source-manifest.d.ts +34 -0
  5. package/dist/cli/loopback-callback.d.ts +24 -0
  6. package/dist/cli/open-browser.d.ts +12 -0
  7. package/dist/cli.js +847 -205
  8. package/dist/cluster/coordinator.d.ts +5 -3
  9. package/dist/cluster/index.d.ts +2 -0
  10. package/dist/cluster/join-token.d.ts +27 -5
  11. package/dist/cluster/peer-auth-coordinator.d.ts +36 -0
  12. package/dist/cluster/peer-client.d.ts +34 -14
  13. package/dist/cluster/peer-credentials.d.ts +14 -2
  14. package/dist/cluster/peer-handler.d.ts +2 -2
  15. package/dist/cluster/rerouted-job-guard.d.ts +39 -0
  16. package/dist/db/migrations/043_rerouted_to_peer.d.ts +4 -0
  17. package/dist/db/migrations/044_check_mode.d.ts +4 -0
  18. package/dist/db/migrations/045_host_properties.d.ts +16 -0
  19. package/dist/db/migrations/046_join_token_consumed_by_instance.d.ts +17 -0
  20. package/dist/db/types.d.ts +33 -1
  21. package/dist/index.d.ts +5 -1
  22. package/dist/index.js +1168 -33
  23. package/dist/lockfile-validate.d.ts +24 -0
  24. package/dist/pipeline/dispatch-matched-workflow.d.ts +53 -1
  25. package/dist/pipeline/test-pipeline.d.ts +7 -0
  26. package/dist/providers/github/manifest-form.d.ts +15 -0
  27. package/dist/providers/github/manifest.d.ts +78 -0
  28. package/dist/reporting/execution-tracker.d.ts +10 -1
  29. package/dist/routes/admin-sources.d.ts +11 -0
  30. package/dist/routes/admin.d.ts +8 -0
  31. package/dist/secrets/pg-secret-store.d.ts +9 -0
  32. package/dist/server.js +3052 -2004
  33. package/dist/stale-detector/stale-run-detector.d.ts +8 -0
  34. package/dist/standalone.js +2837 -1817
  35. package/dist/worker/in-memory-job-queue.d.ts +17 -0
  36. package/dist/worker/peer-outbox.d.ts +36 -0
  37. package/dist/worker/worker-outbox-relay.d.ts +13 -0
  38. package/dist/ws/inventory-api.d.ts +17 -0
  39. package/installer-image-digests.json +3 -3
  40. package/package.json +4 -4
  41. package/sbom.spdx.json +77 -128
@@ -1,6 +1,8 @@
1
1
  import { type Kysely } from 'kysely';
2
- import { type LabelMatcher } from '@kici-dev/engine';
2
+ import { type HostInventoryEntry, type InventorySelector, type LabelMatcher } from '@kici-dev/engine';
3
3
  import type { Database, HostRosterRow } from '../db/types.js';
4
+ /** Typed host-vars bag carried by roster rows (`string | number | boolean`). */
5
+ export type HostProperties = Record<string, string | number | boolean>;
4
6
  /**
5
7
  * Derived (read-time) status of a roster host. Never a stored mutable column:
6
8
  * status is computed from the shared `last_seen` + `connected_instance_id` so
@@ -33,6 +35,8 @@ export interface MatchedHost {
33
35
  status: HostStatus;
34
36
  platform: string | null;
35
37
  arch: string | null;
38
+ /** Typed host-vars bag (parsed from `host_properties`). */
39
+ properties: HostProperties;
36
40
  }
37
41
  export interface UpsertHostInput {
38
42
  agentId: string;
@@ -43,6 +47,13 @@ export interface UpsertHostInput {
43
47
  platform: string;
44
48
  arch: string;
45
49
  instanceId: string;
50
+ /**
51
+ * Agent-reported typed host-vars. Shallow-merged into any existing bag on
52
+ * conflict (agent-reported keys win; operator-declared keys the agent does
53
+ * not report are preserved). Omitted ⇒ no change to the stored bag on update,
54
+ * `{}` on insert.
55
+ */
56
+ properties?: HostProperties;
46
57
  }
47
58
  /** Minimal row shape `deriveHostStatus` reads (the store row or the CLI row). */
48
59
  export interface HostStatusRow {
@@ -60,6 +71,12 @@ export interface HostStatusRow {
60
71
  * `stale` (scaled down, awaiting reap).
61
72
  */
62
73
  export declare function deriveHostStatus(row: HostStatusRow, nowMs: number, graceMs: number): HostStatus;
74
+ /**
75
+ * Normalize a stored `host_properties` value into a typed host-vars bag. The pg
76
+ * driver returns parsed JSON for a `jsonb` column, but accept a JSON string too
77
+ * (defensive for non-pg paths / tests). Non-object / null reads back as `{}`.
78
+ */
79
+ export declare function parseHostProperties(value: unknown): HostProperties;
63
80
  /**
64
81
  * Durable, cluster-shared roster of every agent the cluster has ever enrolled.
65
82
  *
@@ -84,6 +101,7 @@ export declare class HostRosterStore {
84
101
  agentId: string;
85
102
  labels: string[];
86
103
  hostname?: string;
104
+ properties?: HostProperties;
87
105
  }): Promise<void>;
88
106
  get(agentId: string): Promise<HostRosterRow | null>;
89
107
  listAll(): Promise<HostRosterRow[]>;
@@ -95,6 +113,23 @@ export declare class HostRosterStore {
95
113
  * registry alone cannot name an expected-but-absent host.
96
114
  */
97
115
  findMatching(include: readonly (readonly LabelMatcher[])[], exclude: readonly LabelMatcher[], graceMs: number): Promise<MatchedHost[]>;
116
+ /**
117
+ * Map a roster row to the canonical {@link HostInventoryEntry} — the queryable
118
+ * shape returned by the `inventory.query`/`inventory.get` RPC and typed on the
119
+ * SDK's `ctx.kici.inventory`. Status is derived the same way `findMatching`
120
+ * derives it (live + fresh ⇒ ready; declared-but-absent static ⇒ unreachable;
121
+ * ephemeral past ttl ⇒ stale).
122
+ */
123
+ toInventoryEntry(row: HostRosterRow, graceMs: number): HostInventoryEntry;
124
+ /**
125
+ * Query the roster as canonical {@link HostInventoryEntry} records. With a
126
+ * selector, reuses `findMatching`'s label filtering (server-side, glob/regex);
127
+ * property filtering is done client-side in the workflow. Omit the selector ⇒
128
+ * every host.
129
+ */
130
+ queryInventory(selector: InventorySelector | undefined, graceMs: number): Promise<HostInventoryEntry[]>;
131
+ /** Single-host inventory lookup; null when the agent is not in the roster. */
132
+ getInventory(agentId: string, graceMs: number): Promise<HostInventoryEntry | null>;
98
133
  /**
99
134
  * Count `static` (declared) hosts whose derived status is `unreachable` —
100
135
  * the "declared-but-absent" alarm population. Reuses the single-source
@@ -120,6 +120,12 @@ interface AgentMetadata {
120
120
  * `lifecycle_class`. `null` / undefined when auth mode is `none`.
121
121
  */
122
122
  tokenAgentType?: 'static' | 'ephemeral' | null;
123
+ /**
124
+ * Agent-reported typed host-vars (the `KICI_PROPERTIES` bag), threaded from
125
+ * the `agent.register` message into the roster upsert (shallow-merged with
126
+ * any operator-declared properties). Undefined ⇒ the agent reported none.
127
+ */
128
+ properties?: Record<string, string | number | boolean>;
123
129
  }
124
130
  /**
125
131
  * Subset of `HostRosterStore` the registry reconciles into. Optional — when no
@@ -136,6 +142,7 @@ export interface RosterReconciler {
136
142
  platform: string;
137
143
  arch: string;
138
144
  instanceId: string;
145
+ properties?: Record<string, string | number | boolean>;
139
146
  }): Promise<void>;
140
147
  markDisconnected(agentId: string, instanceId: string): Promise<void>;
141
148
  stampLastSeen(agentId: string, instanceId: string): Promise<void>;
@@ -307,6 +314,21 @@ export declare class AgentRegistry {
307
314
  * Update the last heartbeat timestamp for an agent.
308
315
  */
309
316
  updateHeartbeat(agentId: string): boolean;
317
+ /**
318
+ * Compact, allocation-cheap snapshot of every registered agent's routing
319
+ * facts. Used by the dispatcher to log WHY a job found no backend (required
320
+ * labels vs each agent's labels / capacity), which is otherwise invisible —
321
+ * a `queued-no-backend` outcome alone doesn't say whether the label set
322
+ * mismatched, the agent was at capacity, or no agent was connected at all.
323
+ */
324
+ agentSummaries(): Array<{
325
+ agentId: string;
326
+ labels: string[];
327
+ platform: string;
328
+ arch: string;
329
+ activeJobs: number;
330
+ maxConcurrency: number;
331
+ }>;
310
332
  /**
311
333
  * Get a single agent entry by ID.
312
334
  */
package/dist/app.d.ts CHANGED
@@ -151,6 +151,11 @@ export interface AppDependencies {
151
151
  * Platform client + config are in scope; passed into the admin source routes.
152
152
  */
153
153
  resolveSourceWebhookUrl?: AdminRouteDeps['resolveSourceWebhookUrl'];
154
+ /**
155
+ * Resolves the org-scoped GitHub webhook URL for the manifest setup
156
+ * pre-flight (before any App exists). Passed into the admin source routes.
157
+ */
158
+ resolveGithubWebhookUrl?: AdminRouteDeps['resolveGithubWebhookUrl'];
154
159
  /** Config admin API route dependencies. Optional -- mounted when config management is available. */
155
160
  configRouteDeps?: ConfigRouteDeps;
156
161
  /** Event router for internal event delivery. Optional -- if not set, event routing is inactive. */
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `kici-admin source add github --manifest` — the one-click GitHub App setup
3
+ * flow. Drives GitHub's App Manifest flow end-to-end: builds a pre-configured
4
+ * manifest (KiCI's exact permissions/events/webhook URL), hands it to GitHub via
5
+ * an auto-submitting form, catches the returned short-lived setup code over a
6
+ * localhost loopback (or copy-paste in --no-browser mode), exchanges it for the
7
+ * App's id + private key + webhook secret, then reuses the existing source
8
+ * storage + Platform-registration path.
9
+ *
10
+ * The private-key-bearing conversion happens entirely here on the orchestrator
11
+ * host — it never transits the Platform (sovereignty invariant).
12
+ */
13
+ import type { AdminApiClient } from '../api-client.js';
14
+ import { waitForInstallation, verifyRepoAccess, type GithubAppCredentials } from '../../providers/github/manifest.js';
15
+ import { startManifestLoopback } from '../loopback-callback.js';
16
+ export interface ManifestSetupOptions {
17
+ name: string;
18
+ noBrowser?: boolean;
19
+ githubOrg?: string;
20
+ }
21
+ /** Injectable boundary so unit tests can stub GitHub + the browser + stdin. */
22
+ export interface ManifestSetupDeps {
23
+ startLoopback: typeof startManifestLoopback;
24
+ openBrowser: (url: string) => void | Promise<void>;
25
+ readLine: (prompt: string) => Promise<string>;
26
+ convert: (code: string) => Promise<GithubAppCredentials>;
27
+ waitForInstallation: typeof waitForInstallation;
28
+ verifyRepoAccess: typeof verifyRepoAccess;
29
+ /** Persist a created App's PEM to a 0600 file for orphan-app recovery. */
30
+ writeRecoveryFile: (appId: string, pem: string) => string;
31
+ }
32
+ export declare const realManifestSetupDeps: ManifestSetupDeps;
33
+ export declare function runGithubManifestSetup(opts: ManifestSetupOptions, client: AdminApiClient, deps?: ManifestSetupDeps): Promise<void>;
34
+ //# sourceMappingURL=source-manifest.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Ephemeral localhost server that backs the GitHub App manifest setup flow.
3
+ * Serves the auto-submitting manifest form at `/` and catches GitHub's redirect
4
+ * (`/cb?code=…&state=…`). The setup code is exchanged for credentials in the
5
+ * CLI, never on the Platform — this loopback keeps the whole flow on the
6
+ * orchestrator host (mirrors the `kici login` browser-OAuth loopback).
7
+ */
8
+ export interface ManifestLoopback {
9
+ /** http://127.0.0.1:<port>/cb — GitHub redirect target. */
10
+ redirectUrl: string;
11
+ /** http://127.0.0.1:<port>/ — serves the manifest form. */
12
+ formUrl: string;
13
+ waitForCode(timeoutMs: number): Promise<{
14
+ code: string;
15
+ state: string;
16
+ }>;
17
+ close(): void;
18
+ }
19
+ export declare function startManifestLoopback(opts: {
20
+ state: string;
21
+ manifestJson: string;
22
+ createUrl: string;
23
+ }): Promise<ManifestLoopback>;
24
+ //# sourceMappingURL=loopback-callback.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Open a URL in the operator's default browser, best-effort. Used by the GitHub
3
+ * App manifest setup flow to launch the create / install pages. Failure is
4
+ * non-fatal — the caller always prints the URL too, so a headless host (or a
5
+ * blocked launcher) just falls back to copy-paste.
6
+ *
7
+ * `KICI_BROWSER_CMD` overrides the launcher: `none` suppresses it entirely
8
+ * (E2E / headless capture), any other value is run with `{url}` substituted —
9
+ * mirroring the `kici login` convention.
10
+ */
11
+ export declare function openBrowserBestEffort(url: string): void;
12
+ //# sourceMappingURL=open-browser.d.ts.map