@kici-dev/orchestrator 0.1.16 → 0.1.18

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 (83) hide show
  1. package/dist/agent/dispatcher.d.ts +27 -1
  2. package/dist/agent/host-roster-reaper.d.ts +42 -0
  3. package/dist/agent/host-roster.d.ts +110 -0
  4. package/dist/agent/registry.d.ts +65 -2
  5. package/dist/app.d.ts +3 -0
  6. package/dist/approvals/apply-decision.d.ts +11 -1
  7. package/dist/cache/pending-inits.d.ts +5 -0
  8. package/dist/cli/api-client.d.ts +11 -0
  9. package/dist/cli/commands/host.d.ts +13 -0
  10. package/dist/cli/commands/local-hook.d.ts +15 -0
  11. package/dist/cli/commands/local-trigger.d.ts +24 -0
  12. package/dist/cli/commands/remote-source.d.ts +17 -0
  13. package/dist/cli/commands/shared/versioned-upgrade.d.ts +37 -0
  14. package/dist/cli/kici-admin.d.ts +13 -0
  15. package/dist/cli/service/compose.d.ts +2 -1
  16. package/dist/cli/service/index.d.ts +2 -2
  17. package/dist/cli/service/instance/manifest.d.ts +12 -0
  18. package/dist/cli/service/instance/resolve.d.ts +12 -2
  19. package/dist/cli/service/launchd.d.ts +4 -1
  20. package/dist/cli/service/platform-detect.d.ts +5 -0
  21. package/dist/cli/service/systemd.d.ts +2 -1
  22. package/dist/cli/service/types.d.ts +37 -0
  23. package/dist/cli/service/windows.d.ts +2 -1
  24. package/dist/cli.js +1340 -198
  25. package/dist/cluster/coordinator.d.ts +5 -1
  26. package/dist/config/schema.d.ts +4 -0
  27. package/dist/config/types.d.ts +9 -0
  28. package/dist/config.d.ts +6 -4
  29. package/dist/dashboard/handler.d.ts +26 -2
  30. package/dist/dashboard/needs-edges.d.ts +13 -0
  31. package/dist/db/migrations/035_pending_workflow_contexts.d.ts +11 -0
  32. package/dist/db/migrations/036_attestations.d.ts +15 -0
  33. package/dist/db/migrations/037_generic_sources_provider_type_local.d.ts +19 -0
  34. package/dist/db/migrations/038_remote_sources.d.ts +14 -0
  35. package/dist/db/migrations/039_host_roster.d.ts +19 -0
  36. package/dist/db/migrations/040_runsonall_pin.d.ts +4 -0
  37. package/dist/db/migrations/041_wave_gated.d.ts +4 -0
  38. package/dist/db/migrations/042_dispatch_queue_patterns.d.ts +4 -0
  39. package/dist/db/types.d.ts +144 -10
  40. package/dist/diagnostics/fleet-collector.d.ts +1 -1
  41. package/dist/entry-helpers.d.ts +7 -29
  42. package/dist/environments/held-runs.d.ts +33 -0
  43. package/dist/index.js +1 -0
  44. package/dist/lockfile-redos-guard.d.ts +19 -0
  45. package/dist/metrics/prometheus.d.ts +17 -2
  46. package/dist/metrics/scheduled-jobs.d.ts +2 -2
  47. package/dist/orchestrator-core.d.ts +94 -1
  48. package/dist/pipeline/dispatch-matched-workflow.d.ts +60 -2
  49. package/dist/pipeline/install-secrets-resolver.d.ts +32 -5
  50. package/dist/pipeline/needs-scheduler.d.ts +12 -10
  51. package/dist/pipeline/pending-workflow-context.d.ts +44 -0
  52. package/dist/pipeline/processor.d.ts +31 -4
  53. package/dist/pipeline/remote-source-store.d.ts +21 -0
  54. package/dist/pipeline/resume-workflow.d.ts +26 -0
  55. package/dist/pipeline/wave-scheduler.d.ts +60 -0
  56. package/dist/providers/local/index.d.ts +33 -0
  57. package/dist/providers/local/local-source-config.d.ts +17 -0
  58. package/dist/providers/{internal → local}/lock-file-fetcher.d.ts +6 -6
  59. package/dist/providers/{internal → local}/normalizer.d.ts +28 -28
  60. package/dist/providers/{internal → local}/repo-url-builder.d.ts +6 -6
  61. package/dist/queue/job-queue.d.ts +61 -2
  62. package/dist/reporting/execution-tracker.d.ts +61 -0
  63. package/dist/routes/admin-events.d.ts +3 -4
  64. package/dist/routes/uploads.d.ts +36 -26
  65. package/dist/server.js +53755 -50888
  66. package/dist/sources/build-platform-sources.d.ts +4 -2
  67. package/dist/stale-detector/stale-run-detector.d.ts +14 -1
  68. package/dist/standalone.js +17234 -15725
  69. package/dist/webhook/generic-sources-listener.d.ts +4 -0
  70. package/dist/webhook/generic-sources.d.ts +37 -11
  71. package/dist/webhook/register-source-bundle.d.ts +14 -4
  72. package/dist/ws/agent-handler.d.ts +17 -0
  73. package/dist/ws/dashboard-dispatch-guard.d.ts +21 -0
  74. package/dist/ws/dashboard-env-handler.d.ts +13 -1
  75. package/dist/ws/oidc-token-relay.d.ts +59 -0
  76. package/dist/ws/platform-client.d.ts +30 -1
  77. package/dist/ws/test-relay-handlers.d.ts +112 -0
  78. package/installer-image-digests.json +3 -3
  79. package/package.json +22 -22
  80. package/sbom.spdx.json +3228 -6633
  81. package/dist/providers/internal/index.d.ts +0 -32
  82. package/dist/routes/test-trigger.d.ts +0 -41
  83. package/dist/ws/observer-handler.d.ts +0 -42
@@ -10,7 +10,7 @@
10
10
  * - "Peers report step-by-step progress back to coordinator"
11
11
  * - "Cancel mode: graceful -- finish current step, cancel remaining"
12
12
  */
13
- import type { JobReroute, JobProgress, PeerScalerEvent, PeerToPeerMessage, ResourceRequest } from '@kici-dev/engine';
13
+ import type { JobReroute, JobProgress, PeerScalerEvent, PeerToPeerMessage, ResourceRequest, LabelMatcher } from '@kici-dev/engine';
14
14
  import type { PeerRegistry } from './peer-registry.js';
15
15
  import type { PeerClient } from './peer-client.js';
16
16
  import type { Dispatcher } from '../agent/dispatcher.js';
@@ -37,6 +37,10 @@ export interface RunContext {
37
37
  export interface JobToRoute {
38
38
  jobName: string;
39
39
  runsOnLabels: string[][];
40
+ /** Regex matchers the agent's labels must satisfy (JS post-filter). */
41
+ runsOnPatterns?: LabelMatcher[];
42
+ /** Regex matchers that disqualify an agent (JS post-filter). */
43
+ excludePatterns?: LabelMatcher[];
40
44
  jobConfig: Record<string, unknown>;
41
45
  repoUrl: string;
42
46
  ref: string;
@@ -69,6 +69,8 @@ export declare const sharedConfigSchema: z.ZodObject<{
69
69
  none: "none";
70
70
  }>>;
71
71
  agentTokenTtlMs: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
72
+ rosterGraceMs: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
73
+ rosterTtlMs: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
72
74
  queue: z.ZodOptional<z.ZodObject<{
73
75
  maxDepth: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
74
76
  timeoutMs: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
@@ -156,6 +158,8 @@ export declare const appConfigSchema: z.ZodObject<{
156
158
  none: "none";
157
159
  }>>;
158
160
  agentTokenTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
161
+ rosterGraceMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
162
+ rosterTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
159
163
  queueMaxDepth: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
160
164
  queueTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
161
165
  queueBackpressureThreshold: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -50,6 +50,9 @@ export interface SharedConfig {
50
50
  };
51
51
  agentAuth?: 'token' | 'none';
52
52
  agentTokenTtlMs?: number;
53
+ rosterGraceMs?: number;
54
+ rosterTtlMs?: number;
55
+ maxFanoutHosts?: number;
53
56
  queue?: {
54
57
  maxDepth?: number;
55
58
  timeoutMs?: number;
@@ -135,6 +138,12 @@ export interface AppConfig {
135
138
  agentAuth: 'token' | 'none';
136
139
  /** Agent token TTL in milliseconds */
137
140
  agentTokenTtlMs: number;
141
+ /** Host roster: static grace before a disconnected static host reads unreachable (ms) */
142
+ rosterGraceMs: number;
143
+ /** Host roster: ephemeral GC ttl — past this a disconnected ephemeral host is reaped (ms) */
144
+ rosterTtlMs: number;
145
+ /** Cap on per-host children produced by a runsOnAll fan-out */
146
+ maxFanoutHosts: number;
138
147
  /** Queue settings */
139
148
  queueMaxDepth: number;
140
149
  queueTimeoutMs: number;
package/dist/config.d.ts CHANGED
@@ -82,6 +82,9 @@ declare const configSchema: z.ZodObject<{
82
82
  none: "none";
83
83
  }>>;
84
84
  agentTokenTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
85
+ rosterGraceMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
86
+ rosterTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
87
+ maxFanoutHosts: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
85
88
  eventRouterMaxChainDepth: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
86
89
  eventRouterRateLimitPerWorkflowPerMinute: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
87
90
  eventRouterEventTtlSeconds: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -105,8 +108,6 @@ declare const configSchema: z.ZodObject<{
105
108
  production: "production";
106
109
  test: "test";
107
110
  }>>;
108
- internalProviderRepoPath: z.ZodOptional<z.ZodString>;
109
- internalProviderCloneUrl: z.ZodOptional<z.ZodString>;
110
111
  autoMigrate: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<boolean, string>>;
111
112
  agentMaxReconnectDelayMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
112
113
  skipS3SentinelValidation: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<boolean, string>>;
@@ -199,6 +200,9 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
199
200
  pgCustomerSecrets: boolean;
200
201
  agentAuth: "token" | "none";
201
202
  agentTokenTtlMs: number;
203
+ rosterGraceMs: number;
204
+ rosterTtlMs: number;
205
+ maxFanoutHosts: number;
202
206
  eventRouterMaxChainDepth: number;
203
207
  eventRouterRateLimitPerWorkflowPerMinute: number;
204
208
  eventRouterEventTtlSeconds: number;
@@ -262,8 +266,6 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
262
266
  secretKeyFileOld?: string | undefined;
263
267
  bootstrapAdminToken?: string | undefined;
264
268
  testEventFailFirstN?: string | undefined;
265
- internalProviderRepoPath?: string | undefined;
266
- internalProviderCloneUrl?: string | undefined;
267
269
  otelExporterOtlpEndpoint?: string | undefined;
268
270
  clusterName?: string | undefined;
269
271
  }>;
@@ -13,14 +13,24 @@
13
13
  */
14
14
  import { type Kysely } from 'kysely';
15
15
  import { type ColdStore } from '@kici-dev/shared';
16
- import type { DashboardRunDetailRequest, DashboardRunsListRequest, DashboardRunsListResponse, DashboardRunsFiltersRequest, DashboardRunsFiltersResponse, DashboardSourcesListRequest, DashboardSourcesListResponse, DashboardStepLogsRequest, DashboardPayloadRequest, DashboardOrchLogsRequest, DashboardEventLogListRequest, DashboardEventLogDetailRequest, DashboardEventLogPayloadStreamRequest, DashboardAccessLogListRequest, DashboardEventDlqListRequest, DashboardEventDlqCountRequest, DashboardEventDlqRetryRequest, DashboardEventDlqDiscardRequest, RunRerunRequest, RunCancelRequest, ManualScheduleRequest } from '@kici-dev/engine';
16
+ import type { DashboardRunDetailRequest, DashboardRunsListRequest, DashboardRunsListResponse, DashboardRunsFiltersRequest, DashboardRunsFiltersResponse, DashboardSourcesListRequest, DashboardSourcesListResponse, DashboardStepLogsRequest, DashboardAttestationsListRequest, DashboardPayloadRequest, DashboardOrchLogsRequest, DashboardEventLogListRequest, DashboardEventLogDetailRequest, DashboardEventLogPayloadStreamRequest, DashboardAccessLogListRequest, DashboardEventDlqListRequest, DashboardEventDlqCountRequest, DashboardEventDlqRetryRequest, DashboardEventDlqDiscardRequest, RunRerunRequest, RunCancelRequest, ManualScheduleRequest } from '@kici-dev/engine';
17
17
  import type { Database } from '../db/types.js';
18
18
  import type { LogStorage } from '../reporting/log-storage.js';
19
+ import type { CacheStorage } from '../storage/types.js';
19
20
  import type { AccessLogWriter } from '../audit/access-log.js';
20
21
  import type { EventStore } from '../events/event-store.js';
21
22
  interface DashboardHandlerDeps {
22
23
  db: Kysely<Database>;
23
24
  logStorage: LogStorage;
25
+ /**
26
+ * Object-storage backend for provenance bundles. The attestations handler
27
+ * reads each stored bundle by `storage_key` and inlines it into the response
28
+ * so the dashboard verifies it client-side without a second fetch. Optional —
29
+ * when absent (e.g. orchestrators without provenance configured) the handler
30
+ * replies with an `error` and an empty list. Reuses the cache storage backend
31
+ * (the same one P1.5 writes bundles to).
32
+ */
33
+ provenanceStorage?: CacheStorage | null;
24
34
  /** Send a response message back to Platform over the WS connection. */
25
35
  send: (msg: unknown) => void;
26
36
  /** This orchestrator's instance ID, included in job detail responses. */
@@ -78,6 +88,7 @@ interface DashboardHandlerDeps {
78
88
  export declare class DashboardHandler {
79
89
  private readonly db;
80
90
  private readonly logStorage;
91
+ private readonly provenanceStorage;
81
92
  private readonly send;
82
93
  private readonly orchestratorId;
83
94
  private readonly accessLog;
@@ -222,7 +233,7 @@ export declare class DashboardHandler {
222
233
  *
223
234
  * `triggerTypes` is populated from `execution_runs.provider` — the
224
235
  * orchestrator has no dedicated trigger-event column, so `provider`
225
- * (`github` / `generic` / `internal`) is the closest available
236
+ * (`github` / `generic` / `local`) is the closest available
226
237
  * discriminator the customer filter can offer.
227
238
  *
228
239
  * Attribution uses the handler-bound `orgId` / `routingKey` directly — the
@@ -251,6 +262,19 @@ export declare class DashboardHandler {
251
262
  * Looks up the step's log_path and reads content from LogStorage.
252
263
  */
253
264
  handleStepLogs(msg: DashboardStepLogsRequest): Promise<void>;
265
+ /**
266
+ * Read the attestation rows for a run, joined to `execution_jobs` for the
267
+ * job name. Ordered oldest-first so the dashboard table is stable.
268
+ */
269
+ private resolveAttestationsForRun;
270
+ /**
271
+ * Handle a dashboard.attestations.list request: list the run's provenance
272
+ * attestations, inlining each stored bundle from object storage so the
273
+ * dashboard verifies it client-side. Mirrors `handleStepLogs` (per-run org
274
+ * resolution + `recordAccess` + `send`). Rows whose bundle can't be read are
275
+ * skipped (best-effort) rather than failing the whole list.
276
+ */
277
+ handleAttestationsList(msg: DashboardAttestationsListRequest): Promise<void>;
254
278
  /**
255
279
  * Handle a dashboard.payload request.
256
280
  * Reads the webhook payload from log storage for the given runId.
@@ -0,0 +1,13 @@
1
+ import { IfFailedPolicy } from '@kici-dev/engine';
2
+ /** A single resolved upstream dependency edge for a job. */
3
+ export interface JobNeedEdge {
4
+ upstreamName: string;
5
+ ifFailed: IfFailedPolicy;
6
+ }
7
+ /** Group raw execution_job_needs rows by downstream job_name. */
8
+ export declare function groupNeedsByJobName(rows: ReadonlyArray<{
9
+ job_name: string;
10
+ upstream_name: string;
11
+ if_failed: string;
12
+ }>): Map<string, JobNeedEdge[]>;
13
+ //# sourceMappingURL=needs-edges.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Pending workflow dispatch context — backs resume of a workflow whose install
4
+ * gate held. One row per held run: the serializable dispatch inputs needed to
5
+ * rebuild the WorkflowDispatchContext when the hold releases (reviewer approve,
6
+ * wait-timer expiry, concurrency slot free). The row is deleted once the resume
7
+ * dispatch has been kicked off.
8
+ */
9
+ export declare function up(db: Kysely<unknown>): Promise<void>;
10
+ export declare function down(db: Kysely<unknown>): Promise<void>;
11
+ //# sourceMappingURL=035_pending_workflow_contexts.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add the `attestations` table for build-provenance bundles.
4
+ *
5
+ * When a workflow step calls `ctx.attestProvenance`, the agent uploads the
6
+ * signed KiCI bundle to object storage under
7
+ * `provenance/{run_id}/{job_id}/{subject_digest}.kici.json` and the
8
+ * orchestrator records one row here so the dashboard can list and fetch
9
+ * attestations per run/job.
10
+ *
11
+ * Idempotent: a re-run on a DB that already has the table is a no-op.
12
+ */
13
+ export declare function up(db: Kysely<unknown>): Promise<void>;
14
+ export declare function down(db: Kysely<unknown>): Promise<void>;
15
+ //# sourceMappingURL=036_attestations.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Replace the `generic_webhook_sources.provider_type` CHECK constraint so it
4
+ * permits `'local'` instead of `'internal'`, and migrate any carried-over
5
+ * `'internal'` rows to `'local'`.
6
+ *
7
+ * The local filesystem (`file://`) provider is `provider_type='local'`; the
8
+ * value used to be `'internal'`. The original CHECK constraint
9
+ * (`generic_webhook_sources_provider_type_check`) allowed only
10
+ * `{'generic','internal'}`, so writing a `'local'` row fails. Universal-git
11
+ * sources keep `provider_type='generic'` (discriminated by `git_config`), so
12
+ * the new allowed set is `{'generic','local'}`.
13
+ *
14
+ * Idempotent: the constraint is dropped IF EXISTS and recreated; the data
15
+ * backfill is a plain UPDATE that is a no-op once no `'internal'` rows remain.
16
+ */
17
+ export declare function up(db: Kysely<unknown>): Promise<void>;
18
+ export declare function down(db: Kysely<unknown>): Promise<void>;
19
+ //# sourceMappingURL=037_generic_sources_provider_type_local.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * `remote_sources` anchors a Platform-relayed `kici run remote` to its real
4
+ * org. One row per org served by this orchestrator: routing key
5
+ * `remote:<orgId>` maps to the canonical org id, so `resolveOrgId` resolves
6
+ * the real tenant through the same local-source path a webhook takes — no
7
+ * GitHub App, no webhook secret, no manual setup. The row is auto-provisioned
8
+ * on Platform auth from the canonical org id carried on `auth.success`.
9
+ *
10
+ * Idempotent: a re-run on a DB that already has the table is a no-op.
11
+ */
12
+ export declare function up(db: Kysely<unknown>): Promise<void>;
13
+ export declare function down(db: Kysely<unknown>): Promise<void>;
14
+ //# sourceMappingURL=038_remote_sources.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * `host_roster` is KiCI's declared inventory: one durable row per agent the
4
+ * cluster has ever enrolled, reconciled from the in-memory AgentRegistry on
5
+ * every register/unregister. `lifecycle_class` (snapshot of the auth token's
6
+ * agent_type) drives reaping — `ephemeral` rows are GC'd past their TTL,
7
+ * `static` rows persist and read as `unreachable` when their heartbeat goes
8
+ * stale. `connected_instance_id` records which orchestrator holds the live WS
9
+ * (cluster liveness + the host-fanout reroute target); NULL = disconnected.
10
+ *
11
+ * The roster lives in the shared cluster DB (one table, all instances). Status
12
+ * is derived at read from the shared `last_seen` + `connected_instance_id`, so
13
+ * every instance agrees regardless of which one holds the agent's live WS.
14
+ *
15
+ * Idempotent: a re-run on a DB that already has the table is a no-op.
16
+ */
17
+ export declare function up(db: Kysely<unknown>): Promise<void>;
18
+ export declare function down(db: Kysely<unknown>): Promise<void>;
19
+ //# sourceMappingURL=039_host_roster.d.ts.map
@@ -0,0 +1,4 @@
1
+ import { type Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ export declare function down(db: Kysely<unknown>): Promise<void>;
4
+ //# sourceMappingURL=040_runsonall_pin.d.ts.map
@@ -0,0 +1,4 @@
1
+ import { type Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ export declare function down(db: Kysely<unknown>): Promise<void>;
4
+ //# sourceMappingURL=041_wave_gated.d.ts.map
@@ -0,0 +1,4 @@
1
+ import { type Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ export declare function down(db: Kysely<unknown>): Promise<void>;
4
+ //# sourceMappingURL=042_dispatch_queue_patterns.d.ts.map
@@ -39,6 +39,7 @@ export interface Database {
39
39
  org_settings: OrgSettingsTable;
40
40
  execution_job_needs: ExecutionJobNeedsTable;
41
41
  pending_job_contexts: PendingJobContextsTable;
42
+ pending_workflow_contexts: PendingWorkflowContextsTable;
42
43
  event_log: EventLogTable;
43
44
  access_log: AccessLogTable;
44
45
  cold_store_chunk_counts: ColdStoreChunkCountsTable;
@@ -47,6 +48,9 @@ export interface Database {
47
48
  scaler_spawning_agents: ScalerSpawningAgentsTable;
48
49
  scaler_agent_jobs: ScalerAgentJobsTable;
49
50
  scaler_reservations: ScalerReservationsTable;
51
+ attestations: AttestationsTable;
52
+ remote_sources: RemoteSourcesTable;
53
+ host_roster: HostRosterTable;
50
54
  }
51
55
  /**
52
56
  * Cluster metadata table
@@ -135,6 +139,10 @@ export interface DispatchQueueTable {
135
139
  request_id: string | null;
136
140
  /** JSONB array of exclusion labels. Default '[]'. */
137
141
  exclude_labels: Generated<string>;
142
+ /** Regex matchers (LabelMatcher[]) the job requires; JS post-filter on top of runs_on_labels. Default '[]'. */
143
+ runs_on_patterns: Generated<string>;
144
+ /** Regex matchers (LabelMatcher[]) that disqualify an agent; JS post-filter. Default '[]'. */
145
+ exclude_patterns: Generated<string>;
138
146
  /** Routing key (e.g. "github:12345") so dispatch can pick the right
139
147
  * per-app provider bundle in multi-app setups. Required (NOT NULL). */
140
148
  routing_key: string;
@@ -170,6 +178,12 @@ export interface DispatchQueueTable {
170
178
  ack_deadline: ColumnType<Date | null, Date | null | undefined, Date | null>;
171
179
  /** Agent the dispatch was sent to (for ack-timeout disconnect + logging). */
172
180
  ack_agent_id: ColumnType<string | null, string | null | undefined, string | null>;
181
+ /**
182
+ * For a runsOnAll host-fanout child: the agent this job is pinned to. The
183
+ * dispatcher routes it only to that agent and the queue drain never hands it
184
+ * to another. NULL for normal label-routed jobs.
185
+ */
186
+ pinned_agent_id: ColumnType<string | null, string | null | undefined, string | null>;
173
187
  }
174
188
  /**
175
189
  * Deduplication cache table
@@ -351,6 +365,23 @@ export interface ExecutionJobTable {
351
365
  ready_at: Date | null;
352
366
  /** Dynamic group membership tag (NULL for static jobs). */
353
367
  group_name: string | null;
368
+ /** Base (logical) job name for a fan-out child. NULL for non-fanned jobs. */
369
+ base_job_name: string | null;
370
+ /** Fan-out kind for a child: 'matrix' | 'host'. NULL for non-fanned jobs. */
371
+ variant_kind: string | null;
372
+ /** Fan-out label for a child: matrix suffix or hostname. NULL for non-fanned jobs. */
373
+ variant_label: string | null;
374
+ /**
375
+ * Wave gate: a fan-out child beyond the job's `maxParallel` window is held
376
+ * (`true`) instead of dispatched. Cleared by the wave-scheduler when a sibling
377
+ * reaches terminal and an in-flight slot frees up. NULL/false for any job not
378
+ * held by a rolling wave.
379
+ */
380
+ wave_gated: Generated<boolean>;
381
+ /** The fan-out base's `maxParallel` wave width, stamped on every child. NULL = no bounded wave. */
382
+ wave_max_parallel: number | null;
383
+ /** The fan-out base's `failFast` policy, stamped on every child. NULL = no bounded wave. */
384
+ wave_fail_fast: boolean | null;
354
385
  /** When this record was created */
355
386
  created_at: Generated<Date>;
356
387
  /**
@@ -925,15 +956,17 @@ export interface GenericWebhookSourceTable {
925
956
  deleted_at: Date | null;
926
957
  /** Provider implementation to route this source through.
927
958
  * 'generic' (default) → GenericWebhookNormalizer (Stripe-shaped payloads).
928
- * 'internal' → InternalWebhookNormalizer (github-shaped push/PR payloads
929
- * for E2E tests and the staging smoke test). */
959
+ * 'local' → LocalWebhookNormalizer (github-shaped push/PR payloads for a
960
+ * git repository present on the agent filesystem, cloned via file://). */
930
961
  provider_type: Generated<string>;
931
- /** Universal-git config (JSONB) for sources that also know how to build
932
- * clone URLs, fetch lock files, and issue clone credentials against a
933
- * user-supplied git server (Forgejo / Gitea / Gogs / GitLab repo-webhook /
934
- * plain GitHub repo-webhook). null for legacy generic / internal sources
935
- * that don't drive git operations. Shape validated by
936
- * `UniversalGitConfigSchema` at the application layer. */
962
+ /** Git config (JSONB), dual-purpose discriminated by `provider_type`:
963
+ * - universal-git sources ('generic' with git_config) store a
964
+ * `UniversalGitConfig` (clone URLs, lock-file fetch, clone credentials
965
+ * against Forgejo / Gitea / Gogs / GitLab repo-webhook / GitHub repo).
966
+ * - local sources ('local') store a `LocalSourceConfig`
967
+ * (`{ repoBasePath, cloneUrlBase? }`).
968
+ * null for plain generic sources that don't drive git operations. Shape
969
+ * validated by the matching Zod schema at the application layer. */
937
970
  git_config: ColumnType<string | Record<string, unknown> | null, string | null, string | null>;
938
971
  }
939
972
  export type GenericWebhookSource = Selectable<GenericWebhookSourceTable>;
@@ -1271,6 +1304,24 @@ export interface PendingJobContextsTable {
1271
1304
  }
1272
1305
  export type PendingJobContextRow = Selectable<PendingJobContextsTable>;
1273
1306
  export type NewPendingJobContext = Insertable<PendingJobContextsTable>;
1307
+ /**
1308
+ * Pending workflow dispatch contexts table.
1309
+ * Persists the serializable WorkflowDispatchContext inputs needed to resume a
1310
+ * workflow whose install gate held. One row per held run (keyed by run_id).
1311
+ * Created at hold time, deleted once the resume dispatch is kicked off.
1312
+ */
1313
+ export interface PendingWorkflowContextsTable {
1314
+ /** Run identifier (one pending dispatch per run) */
1315
+ run_id: string;
1316
+ /** Organization id */
1317
+ org_id: string;
1318
+ /** Serializable WorkflowDispatchContext inputs as JSONB */
1319
+ context: ColumnType<Record<string, unknown>, string, string>;
1320
+ /** When this context was stored */
1321
+ created_at: Generated<Date>;
1322
+ }
1323
+ export type PendingWorkflowContextRow = Selectable<PendingWorkflowContextsTable>;
1324
+ export type NewPendingWorkflowContext = Insertable<PendingWorkflowContextsTable>;
1274
1325
  /**
1275
1326
  * Inbound webhook delivery event log table.
1276
1327
  * One row per inbound webhook delivery (relay or direct), regardless of
@@ -1289,7 +1340,7 @@ export interface EventLogTable {
1289
1340
  org_id: string;
1290
1341
  /** Provider delivery ID (unique within org) */
1291
1342
  delivery_id: string;
1292
- /** Routing key (e.g. "github:42", "generic:<org>:<src>", "internal:...") */
1343
+ /** Routing key (e.g. "github:42", "generic:<org>:<src>") */
1293
1344
  routing_key: string;
1294
1345
  /** Provider event name (X-GitHub-Event / X-Event-Type / 'default') */
1295
1346
  event: string;
@@ -1297,7 +1348,7 @@ export interface EventLogTable {
1297
1348
  action: string | null;
1298
1349
  /** Where the delivery arrived: 'relay' (Platform WS) or 'direct' (HTTP) */
1299
1350
  source: string;
1300
- /** Provider type ('github' / 'generic' / 'internal') */
1351
+ /** Provider type ('github' / 'generic' / 'local') */
1301
1352
  provider: string;
1302
1353
  /** owner/repo when extractable */
1303
1354
  repo_identifier: string | null;
@@ -1456,6 +1507,89 @@ export interface CheckRunTrackingTable {
1456
1507
  export type CheckRunTrackingRow = Selectable<CheckRunTrackingTable>;
1457
1508
  export type NewCheckRunTrackingRow = Insertable<CheckRunTrackingTable>;
1458
1509
  export type CheckRunTrackingUpdate = Updateable<CheckRunTrackingTable>;
1510
+ /**
1511
+ * Build-provenance attestation table.
1512
+ *
1513
+ * One row per `ctx.attestProvenance` bundle uploaded to object storage. The
1514
+ * dashboard lists/fetches attestations by `(run_id, job_id)`.
1515
+ */
1516
+ export interface AttestationsTable {
1517
+ /** Random id (primary key). */
1518
+ id: string;
1519
+ /** KiCI run this attestation belongs to. */
1520
+ run_id: string;
1521
+ /** KiCI job this attestation was produced by. */
1522
+ job_id: string;
1523
+ /** Caller-supplied artifact name (e.g. an npm package coordinate). */
1524
+ subject_name: string;
1525
+ /** Primary subject digest (lowercase hex) used in the storage key. */
1526
+ subject_digest: string;
1527
+ /** Object-storage key the bundle was written to. */
1528
+ storage_key: string;
1529
+ /** Signing mode ('kici' for Mode A). */
1530
+ mode: string;
1531
+ /** Bundle media type. */
1532
+ media_type: string;
1533
+ /** When this row was inserted. */
1534
+ created_at: Generated<Date>;
1535
+ }
1536
+ export type AttestationRow = Selectable<AttestationsTable>;
1537
+ export type NewAttestationRow = Insertable<AttestationsTable>;
1538
+ /**
1539
+ * Remote-source table (remote_sources).
1540
+ *
1541
+ * Anchors a Platform-relayed `kici run remote` to its real org: routing key
1542
+ * `remote:<orgId>` maps to the canonical org id so `resolveOrgId` resolves the
1543
+ * real tenant through the same local-source path a webhook takes. One row per
1544
+ * org served by this orchestrator, auto-provisioned on Platform auth.
1545
+ */
1546
+ export interface RemoteSourcesTable {
1547
+ /** Canonical org id (`org_<…>`) this anchor resolves to. */
1548
+ customer_id: string;
1549
+ /** Deterministic routing key `remote:<orgId>`. */
1550
+ routing_key: string;
1551
+ /** Cluster id this orchestrator deployment reports, or null. */
1552
+ cluster_id: string | null;
1553
+ /** When this row was first provisioned. */
1554
+ created_at: Generated<Date>;
1555
+ /** When this row was last upserted. */
1556
+ updated_at: Generated<Date>;
1557
+ }
1558
+ export type RemoteSourceRow = Selectable<RemoteSourcesTable>;
1559
+ export type NewRemoteSourceRow = Insertable<RemoteSourcesTable>;
1560
+ /**
1561
+ * Host roster table (host_roster).
1562
+ *
1563
+ * KiCI's declared inventory: one durable row per agent the cluster has ever
1564
+ * enrolled, reconciled from the in-memory AgentRegistry on register/unregister.
1565
+ * `lifecycle_class` (snapshot of the auth token's `agent_type`) drives reaping;
1566
+ * `connected_instance_id` records which orchestrator holds the live WS (cluster
1567
+ * liveness + the host-fanout reroute target), null when disconnected. Status is
1568
+ * derived at read from the shared `last_seen` + `connected_instance_id`.
1569
+ */
1570
+ export interface HostRosterTable {
1571
+ /** UUID primary key */
1572
+ id: Generated<string>;
1573
+ /** The agent identity the pin targets; unique. */
1574
+ agent_id: string;
1575
+ /** FK to agent_tokens.id (provenance), or null when auth mode is none. */
1576
+ token_id: string | null;
1577
+ /** Snapshot of the token's agent_type: 'static' | 'ephemeral'. */
1578
+ lifecycle_class: string;
1579
+ /** JSON-encoded string[] of the post-Gate-1 validated labels. */
1580
+ labels: string;
1581
+ hostname: string | null;
1582
+ platform: string | null;
1583
+ arch: string | null;
1584
+ /** Which orchestrator instance holds the live WS; null = disconnected. */
1585
+ connected_instance_id: string | null;
1586
+ last_seen: ColumnType<Date, Date | string | undefined, Date | string>;
1587
+ created_at: Generated<Date>;
1588
+ updated_at: ColumnType<Date, Date | string | undefined, Date | string>;
1589
+ }
1590
+ export type HostRosterRow = Selectable<HostRosterTable>;
1591
+ export type NewHostRosterRow = Insertable<HostRosterTable>;
1592
+ export type HostRosterUpdate = Updateable<HostRosterTable>;
1459
1593
  /**
1460
1594
  * Scaler spawning-agents table (scaler_spawning_agents).
1461
1595
  *
@@ -12,9 +12,9 @@ import { z } from 'zod';
12
12
  /** Per-node collection outcome recorded in the fleet manifest. */
13
13
  export declare const FleetNodeStatus: z.ZodEnum<{
14
14
  error: "error";
15
+ unreachable: "unreachable";
15
16
  ok: "ok";
16
17
  timeout: "timeout";
17
- unreachable: "unreachable";
18
18
  }>;
19
19
  export type FleetNodeStatus = z.infer<typeof FleetNodeStatus>;
20
20
  export interface FleetCollectorDeps {
@@ -5,14 +5,13 @@
5
5
  * Both entry points import these helpers instead of maintaining separate copies.
6
6
  */
7
7
  import { type SourceProvider, SourceSubtype } from '@kici-dev/engine';
8
- import { ProviderRegistry } from './provider-registry.js';
9
8
  /**
10
9
  * A provider source for Platform registration.
11
10
  *
12
11
  * `provider` is the coarse-grained Platform-routing family (`github` /
13
12
  * `gitlab` / `bitbucket` / `generic`). `subtype` is the fine-grained
14
13
  * source kind that survives all the way to the dashboard so universal-git,
15
- * generic_webhook, and internal sources stay distinguishable. `name` is
14
+ * generic_webhook, and local sources stay distinguishable. `name` is
16
15
  * the human-readable label shown in the runs view + sources tab.
17
16
  */
18
17
  export interface ProviderSource {
@@ -50,24 +49,6 @@ export declare function diffProviderSources(oldSources: ProviderSource[], newSou
50
49
  added: ProviderSource[];
51
50
  removed: ProviderSource[];
52
51
  };
53
- /**
54
- * Register the internal provider bundle for E2E testing when the
55
- * `internalProviderRepoPath` config field (KICI_INTERNAL_PROVIDER_REPO_PATH)
56
- * is set.
57
- *
58
- * When the field is present, this overwrites the previously-registered
59
- * `generic:default` bundle with one that has a lockFileFetcher and
60
- * repoUrlBuilder (needed for the webhook pipeline to match triggers and
61
- * dispatch jobs).
62
- *
63
- * When the field is not set, does nothing (leaves `generic:default` as-is).
64
- *
65
- * @returns true if the internal provider was registered, false otherwise
66
- */
67
- export declare function registerInternalProviderIfConfigured(registry: ProviderRegistry, config: {
68
- internalProviderRepoPath?: string;
69
- internalProviderCloneUrl?: string;
70
- }): boolean;
71
52
  /**
72
53
  * Decide whether the local orchestrator can serve a `generic_webhook_sources`
73
54
  * row with the given `provider_type`. Used at boot (and any future reload) to
@@ -79,15 +60,12 @@ export declare function registerInternalProviderIfConfigured(registry: ProviderR
79
60
  * - `'generic'` / `'universal-git'` → always servable (the bundles register
80
61
  * without filesystem dependencies; per-row failures during universal-git
81
62
  * bundle registration are already isolated in orchestrator-core.ts).
82
- * - `'internal'` → only servable when `internalProviderRepoPath` is set, the
83
- * path exists on disk, and is a directory. This mirrors the fail-fast
84
- * guard in `registerInternalProviderIfConfigured`. The check is binary
85
- * per peer: a single `KICI_INTERNAL_PROVIDER_REPO_PATH` backs every
86
- * internal source on that peer (per-source `repoIdentifier`s nest under
87
- * the same base dir).
63
+ * - `'local'` → only servable when the ROW's own `git_config.repoBasePath`
64
+ * exists as a directory on THIS peer. The check is per-source and per-peer:
65
+ * a local repo present on one HA peer may be absent on another, so only the
66
+ * peer that hosts the repo advertises the routing key. Mirrors the statSync
67
+ * gate in `registerProviderBundleForSource`.
88
68
  * - Any unknown provider_type returns false to fail closed.
89
69
  */
90
- export declare function canServeGenericProviderType(providerType: string, config: {
91
- internalProviderRepoPath?: string;
92
- }): boolean;
70
+ export declare function canServeGenericProviderType(providerType: string, gitConfig?: string | Record<string, unknown> | null): boolean;
93
71
  //# sourceMappingURL=entry-helpers.d.ts.map
@@ -45,6 +45,12 @@ export interface CreateHoldData {
45
45
  environmentId?: string | null;
46
46
  /** Queue type: 'environment' (default) or 'security'. */
47
47
  queueType?: 'environment' | 'security';
48
+ /**
49
+ * Held-run `hold_type` discriminator. Defaults to `'approval'` (reviewer
50
+ * holds). The workflow install gate sets `'wait_timer'` / `'concurrency'` so
51
+ * the automated release sweeps can find their rows.
52
+ */
53
+ holdType?: string;
48
54
  }
49
55
  /** A single decision to record against a hold. */
50
56
  export interface RecordDecisionData {
@@ -65,6 +71,15 @@ export interface ReleaseSignal {
65
71
  scope: HoldScope;
66
72
  /** Set only for step-scoped holds. */
67
73
  stepIndex: number | null;
74
+ /**
75
+ * What kind of gate created the hold. `explicit` (SDK `requireApproval`) holds
76
+ * a real root job and resumes by re-dispatching it; `environment` covers the
77
+ * workflow install-gate (wait-timer / concurrency / env approval) which resumes
78
+ * by rebuilding the workflow dispatch context. The resume router keys off this
79
+ * so a workflow-scoped explicit hold goes through the job re-dispatch path
80
+ * rather than the install-gate path (which has no pending workflow context).
81
+ */
82
+ triggerSource: TriggerSource;
68
83
  }
69
84
  /** Options for listing held runs. */
70
85
  export interface ListHeldRunsOptions {
@@ -121,6 +136,24 @@ export declare class HeldRunStore {
121
136
  * holds notify the waiting agent; job/workflow holds fail the run).
122
137
  */
123
138
  listOverdue(): Promise<HeldRun[]>;
139
+ /**
140
+ * Release overdue workflow `wait_timer` holds. The install-gate wait action
141
+ * pauses the workflow as a held run; on timer expiry it must RESUME (not
142
+ * fail like a reviewer-hold expiry). Flips each overdue pending
143
+ * `hold_type='wait_timer'`, `hold_scope='workflow'` row to `released` and
144
+ * returns a `ReleaseSignal` per row so the caller can resume the workflow.
145
+ * Runs BEFORE `expireOverdue()` so these rows leave the pending pool before
146
+ * the expire-and-fail sweep sees them.
147
+ */
148
+ releaseDueWaitHolds(): Promise<ReleaseSignal[]>;
149
+ /**
150
+ * Release the oldest pending workflow `concurrency` hold for a group. Called
151
+ * when a concurrency slot frees on run completion. Flips the oldest matching
152
+ * pending row to `released` and returns its `ReleaseSignal`, or null when no
153
+ * concurrency hold is queued for the group. `group` matches the held row's
154
+ * `environment_id` (workflow-level install concurrency keys on the env id).
155
+ */
156
+ releaseConcurrencyHold(orgId: string, group: string): Promise<ReleaseSignal | null>;
124
157
  /**
125
158
  * Expire overdue pending runs. Called by the stale detector.
126
159
  * Sets status to 'expired' and resolved_at to now() for all
package/dist/index.js CHANGED
@@ -991,6 +991,7 @@ const DEFAULT_EXPIRY_DAYS = 90;
991
991
  * Used by the coordinator to manage credentials for all cluster members.
992
992
  */
993
993
  var PeerCredentialStore = class {
994
+ db;
994
995
  constructor(db) {
995
996
  this.db = db;
996
997
  }