@kici-dev/orchestrator 0.1.20 → 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
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Lock-load shape + compatibility validation.
3
+ *
4
+ * A fetched lock must be a valid JSON document whose schemaVersion matches this
5
+ * orchestrator's engine SCHEMA_VERSION exactly, and whose routing matchers are
6
+ * well-formed LabelMatcher objects. A lock compiled by an older engine stored
7
+ * `runsOn` as a plain string array; without this gate those strings would parse
8
+ * past the dispatch path as an empty label set and mis-route jobs to an
9
+ * arbitrary scaler. These checks run at the cache choke point and surface as a
10
+ * `LockFileParseError` (the established corrupt-lock signal).
11
+ */
12
+ import { type LockFile } from '@kici-dev/engine';
13
+ /** Parse the raw lock JSON and assert the minimal document shape. */
14
+ export declare function parseLockDocument(raw: string, repoIdentifier: string, ref: string): LockFile;
15
+ /** Reject a lock whose schemaVersion does not exactly match this orchestrator. */
16
+ export declare function assertLockFileSchemaCompatible(lockFile: LockFile): void;
17
+ /**
18
+ * Walk every static job's runsOn / excludeLabels / runsOnAll matchers and throw
19
+ * if any element is not a valid LabelMatcher. Dynamic job generators carry no
20
+ * static routing matchers, so only static jobs are checked (mirrors
21
+ * assertLockFileRegexesSafe).
22
+ */
23
+ export declare function assertLockFileMatchersValid(lockFile: LockFile): void;
24
+ //# sourceMappingURL=lockfile-validate.d.ts.map
@@ -14,7 +14,8 @@
14
14
  * results through the pipeline.
15
15
  */
16
16
  import { CacheRefScope } from '@kici-dev/engine';
17
- import type { LabelMatcher, LockWorkflow, SimulatedEvent, WorkflowDecision, MaterializedJob } from '@kici-dev/engine';
17
+ import type { LabelMatcher, LockWorkflow, LockJob, SimulatedEvent, WorkflowDecision, MaterializedJob } from '@kici-dev/engine';
18
+ import { type HostRosterStore } from '../agent/host-roster.js';
18
19
  import type { WebhookInfo } from '../webhook/handler.js';
19
20
  import type { ProviderBundle } from '../provider-registry.js';
20
21
  import type { TrustResolution } from '../security/trust-resolver.js';
@@ -126,6 +127,25 @@ export declare function runsOnSelectorsForLockJob(lockJob: {
126
127
  runsOn?: readonly LabelMatcher[];
127
128
  excludeLabels?: readonly LabelMatcher[];
128
129
  }): JobRoutingSelectors;
130
+ /**
131
+ * Resolve a generated job's single bare-`agentId` `runsOn` into a host pin.
132
+ *
133
+ * The documented inventory fan-out pattern is `runsOn: [h.agentId]`. A bare
134
+ * agentId is not a label any agent advertises, so the normal label path leaves
135
+ * the job `queued-no-backend`. When the single exact label names a known roster
136
+ * host, resolve it to a `pinnedAgentId` dispatch (+ the host's coordinator for
137
+ * cross-cluster reroute) — exact parity with how `runsOnAll` children pin. Any
138
+ * other shape (multi-label, a regex pattern, a non-roster label, or no roster
139
+ * store) returns null and the caller keeps normal label routing.
140
+ */
141
+ export declare function resolveRosterAgentPin(args: {
142
+ runsOnLabels: string[];
143
+ runsOnPatterns: LabelMatcher[];
144
+ hostRosterStore: HostRosterStore | undefined;
145
+ }): Promise<{
146
+ pinnedAgentId: string;
147
+ connectedInstanceId: string | null;
148
+ } | null>;
129
149
  /** Per-child rolling-wave plan: which children are held + the base's wave policy. */
130
150
  export interface WavePlan {
131
151
  /** `expandedName`s held behind the wave gate (beyond the maxParallel window). */
@@ -149,6 +169,38 @@ export interface WavePlan {
149
169
  * job (single child) or one without `maxParallel` contributes nothing.
150
170
  */
151
171
  export declare function computeWavePlan(materializedJobs: readonly MaterializedJob[]): WavePlan;
172
+ export interface GeneratedJobConfig {
173
+ /**
174
+ * The generated lock job with its `name` and `needs` rewritten to expanded
175
+ * matrix-child names (identical to the base job for non-matrix generated
176
+ * jobs). Downstream dispatch / needs-edge / tracking code keys on this name.
177
+ */
178
+ genJob: LockJob;
179
+ genJobConfig: Record<string, unknown>;
180
+ runsOnLabels: string[];
181
+ /** Regex matchers the agent's labels must satisfy (JS post-filter). */
182
+ runsOnPatterns: LabelMatcher[];
183
+ /** Exact labels the dispatched agent must NOT have. */
184
+ excludeLabels: string[];
185
+ /** Regex matchers that disqualify an agent (JS post-filter). */
186
+ excludePatterns: LabelMatcher[];
187
+ /** Host-fanout pin: when runsOn resolved to a roster host, route only to it. */
188
+ pinnedAgentId?: string;
189
+ /** The host's current coordinator (cross-cluster reroute hint); null = not connected. */
190
+ connectedInstanceId?: string | null;
191
+ /** The matrix combination for this child; absent for non-matrix generated jobs. */
192
+ matrixValues?: Record<string, unknown>;
193
+ }
194
+ /**
195
+ * Split generated configs into pinned (host-pin dispatch) and unpinned (normal
196
+ * label routing). A pinned config always rides the dispatcher pin path because
197
+ * the coordinator's `JobToRoute` shape carries no pin field — routing it via the
198
+ * coordinator would silently drop the pin.
199
+ */
200
+ export declare function partitionGeneratedConfigsByPin(configs: readonly GeneratedJobConfig[]): {
201
+ pinnedConfigs: GeneratedJobConfig[];
202
+ unpinnedConfigs: GeneratedJobConfig[];
203
+ };
152
204
  /**
153
205
  * Dispatch a single matched workflow.
154
206
  *
@@ -30,6 +30,7 @@ import type { VariableStore } from '../environments/variable-store.js';
30
30
  import type { LogStorage } from '../reporting/log-storage.js';
31
31
  import type { Kysely } from 'kysely';
32
32
  import type { Database } from '../db/types.js';
33
+ import type { CheckMode } from '@kici-dev/engine';
33
34
  /**
34
35
  * Input for a test trigger request.
35
36
  */
@@ -69,6 +70,12 @@ export interface TestTriggerInput {
69
70
  inlineLockFile?: string;
70
71
  /** When true, repo has no remote -- skip provider lookup, skip clone. */
71
72
  fullRepo?: boolean;
73
+ /**
74
+ * Run mode for idempotent steps (`apply` | `check` | `check-fail-on-drift`).
75
+ * Threaded onto each dispatched job's config and persisted on the run.
76
+ * Omitted means `apply`.
77
+ */
78
+ checkMode?: CheckMode;
72
79
  }
73
80
  /**
74
81
  * Result of processing a test trigger.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * GitHub's create-from-manifest flow is an HTML form POST (the `manifest`
3
+ * JSON cannot ride in a query string). These helpers render the auto-submitting
4
+ * form and the headless code-display page. Pure string builders — no IO — so
5
+ * both the CLI loopback server and the static marketing-site page can reuse the
6
+ * same shapes.
7
+ */
8
+ export declare function manifestCreateUrl(githubOrg?: string): string;
9
+ export declare function renderManifestFormHtml(opts: {
10
+ createUrl: string;
11
+ state: string;
12
+ manifestJson: string;
13
+ }): string;
14
+ export declare function renderCodeDisplayHtml(code: string): string;
15
+ //# sourceMappingURL=manifest-form.d.ts.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * GitHub App Manifest flow helpers. The manifest encodes KiCI's exact
3
+ * permissions + events + webhook config so the operator never picks them by
4
+ * hand — GitHub creates a correctly-configured App from this object in one
5
+ * click. See docs.github.com "Registering a GitHub App from a manifest".
6
+ *
7
+ * The webhook secret is NOT part of the manifest: GitHub generates it during
8
+ * registration and returns it on the conversion response, so both GitHub and
9
+ * the Platform end up sharing the same secret with zero operator effort.
10
+ */
11
+ import { Octokit } from '@octokit/rest';
12
+ export interface GithubManifestInput {
13
+ /** App name shown on GitHub. */
14
+ name: string;
15
+ /** Full https webhook URL (org-scoped) GitHub will POST events to. */
16
+ webhookUrl: string;
17
+ /** Loopback or static-page callback GitHub redirects to with the setup code. */
18
+ redirectUrl: string;
19
+ /** Optional post-install redirect. */
20
+ setupUrl?: string;
21
+ }
22
+ /** The JSON object GitHub's create-from-manifest endpoint expects. */
23
+ export interface GithubAppManifest {
24
+ name: string;
25
+ url: string;
26
+ hook_attributes: {
27
+ url: string;
28
+ active: boolean;
29
+ };
30
+ redirect_url: string;
31
+ setup_url?: string;
32
+ public: boolean;
33
+ default_permissions: Record<string, string>;
34
+ default_events: string[];
35
+ }
36
+ export declare function buildGithubAppManifest(input: GithubManifestInput): GithubAppManifest;
37
+ /** Credentials returned by GitHub's manifest-conversion endpoint. */
38
+ export interface GithubAppCredentials {
39
+ appId: string;
40
+ slug: string;
41
+ privateKey: string;
42
+ webhookSecret: string;
43
+ clientId?: string;
44
+ clientSecret?: string;
45
+ htmlUrl?: string;
46
+ }
47
+ /**
48
+ * Exchange the short-lived manifest `code` for the App's id, private key, and
49
+ * webhook secret. Runs server-to-server directly against GitHub — the private
50
+ * key never transits the Platform.
51
+ */
52
+ export declare function convertManifestCode(code: string, deps?: {
53
+ octokit?: Pick<Octokit, 'request'>;
54
+ }): Promise<GithubAppCredentials>;
55
+ /**
56
+ * Poll GitHub (as the App, via a JWT) until at least one installation exists,
57
+ * returning the first installation's id + account login. Throws on timeout.
58
+ */
59
+ export declare function waitForInstallation(creds: Pick<GithubAppCredentials, 'appId' | 'privateKey'>, opts: {
60
+ timeoutMs: number;
61
+ pollMs: number;
62
+ now?: () => number;
63
+ appOctokit?: Pick<Octokit, 'request'>;
64
+ }): Promise<{
65
+ installationId: number;
66
+ accountLogin: string;
67
+ }>;
68
+ /**
69
+ * Mint an installation token from the captured private key and confirm the App
70
+ * can reach repos — proves the key works end-to-end (the same path the agent's
71
+ * clone uses at runtime).
72
+ */
73
+ export declare function verifyRepoAccess(creds: Pick<GithubAppCredentials, 'appId' | 'privateKey'>, installationId: number, deps?: {
74
+ octokit?: Pick<Octokit, 'request'>;
75
+ }): Promise<{
76
+ repoCount: number;
77
+ }>;
78
+ //# sourceMappingURL=manifest.d.ts.map
@@ -227,7 +227,9 @@ export declare class ExecutionTracker {
227
227
  max?: number;
228
228
  },
229
229
  /** Workflow-level wall-clock timeout in ms from the lock file. Sets the run deadline. */
230
- workflowTimeoutMs?: number): Promise<void>;
230
+ workflowTimeoutMs?: number,
231
+ /** Run mode for idempotent steps; non-apply labels the run a check-mode preview. */
232
+ checkMode?: string): Promise<void>;
231
233
  /**
232
234
  * Add additional jobs to an already-started execution run.
233
235
  * Used when build jobs are tracked early and regular jobs are dispatched later.
@@ -250,6 +252,13 @@ export declare class ExecutionTracker {
250
252
  * uses, so dispatchEvalJob can swap it for the real eval job id.
251
253
  */
252
254
  findDynamicEvalSyntheticId(runId: string, evalJobName: string): Promise<string | undefined>;
255
+ /**
256
+ * Durably mark the projected `execution_jobs` row so run-recovery sweepers
257
+ * know this job lives on a remote worker peer and must not be force-failed
258
+ * while that worker is connected. Called by the owning coordinator right
259
+ * after a peer ACKs a reroute.
260
+ */
261
+ markJobReroutedToPeer(runId: string, jobId: string, peerId: string): Promise<void>;
253
262
  /**
254
263
  * Run `fn` while holding a per-run lock, serializing the run-mutating methods
255
264
  * (`onJobStatus`, `addJobsToRun`) so a status reply cannot interleave with the
@@ -28,6 +28,17 @@ interface SourceRouteDeps {
28
28
  webhookUrl: string | null;
29
29
  webhookNote?: string;
30
30
  }>;
31
+ /**
32
+ * Resolve the org-scoped GitHub webhook URL for the manifest setup flow
33
+ * BEFORE any App exists. The GitHub webhook URL is org-scoped
34
+ * (`<base>/webhook/<orgId>/github`), not app-scoped, so it can be computed
35
+ * up front and baked into the App manifest. Returns null + a note when the
36
+ * orchestrator cannot yet resolve a public base or its org id.
37
+ */
38
+ resolveGithubWebhookUrl?: () => Promise<{
39
+ webhookUrl: string | null;
40
+ webhookNote?: string;
41
+ }>;
31
42
  }
32
43
  type AdminSourcesEnv = {
33
44
  Variables: {
@@ -77,6 +77,14 @@ export interface AdminRouteDeps {
77
77
  webhookUrl: string | null;
78
78
  webhookNote?: string;
79
79
  }>;
80
+ /**
81
+ * Optional -- resolves the org-scoped GitHub webhook URL for the manifest
82
+ * setup pre-flight (before any App exists). Wired in platform/hybrid mode.
83
+ */
84
+ resolveGithubWebhookUrl?: () => Promise<{
85
+ webhookUrl: string | null;
86
+ webhookNote?: string;
87
+ }>;
80
88
  /** Optional -- for DB migration endpoints. */
81
89
  db?: Kysely<any>;
82
90
  /** Optional -- for DB migration endpoints. */
@@ -10,6 +10,15 @@ import { type Kysely } from 'kysely';
10
10
  import type { Database } from '../db/types.js';
11
11
  import type { SecretStore } from '@kici-dev/engine';
12
12
  import type { AuditLogger } from './audit-logger.js';
13
+ /**
14
+ * Thrown by `renameScope` when the named scope has no secret rows and no
15
+ * environment binding — i.e. there is nothing to rename. Consumers map this to
16
+ * a 404 (admin HTTP route) or a structured not-found response (dashboard path).
17
+ */
18
+ export declare class SecretScopeNotFoundError extends Error {
19
+ readonly scope: string;
20
+ constructor(scope: string);
21
+ }
13
22
  /**
14
23
  * PostgreSQL secret store with AES-256-GCM encryption.
15
24
  * Uses scoped_secrets table keyed by (org_id, scope, key).