@kici-dev/orchestrator 0.1.16 → 0.1.17

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 (58) hide show
  1. package/dist/agent/dispatcher.d.ts +18 -1
  2. package/dist/approvals/apply-decision.d.ts +11 -1
  3. package/dist/cache/pending-inits.d.ts +5 -0
  4. package/dist/cli/api-client.d.ts +11 -0
  5. package/dist/cli/commands/local-hook.d.ts +15 -0
  6. package/dist/cli/commands/local-trigger.d.ts +24 -0
  7. package/dist/cli/commands/remote-source.d.ts +17 -0
  8. package/dist/cli/commands/shared/versioned-upgrade.d.ts +14 -0
  9. package/dist/cli/kici-admin.d.ts +13 -0
  10. package/dist/cli.js +726 -177
  11. package/dist/config.d.ts +0 -4
  12. package/dist/dashboard/handler.d.ts +26 -2
  13. package/dist/dashboard/needs-edges.d.ts +13 -0
  14. package/dist/db/migrations/035_pending_workflow_contexts.d.ts +11 -0
  15. package/dist/db/migrations/036_attestations.d.ts +15 -0
  16. package/dist/db/migrations/037_generic_sources_provider_type_local.d.ts +19 -0
  17. package/dist/db/migrations/038_remote_sources.d.ts +14 -0
  18. package/dist/db/types.d.ts +83 -10
  19. package/dist/diagnostics/fleet-collector.d.ts +1 -1
  20. package/dist/entry-helpers.d.ts +7 -29
  21. package/dist/environments/held-runs.d.ts +24 -0
  22. package/dist/index.js +1 -0
  23. package/dist/metrics/prometheus.d.ts +4 -2
  24. package/dist/orchestrator-core.d.ts +48 -0
  25. package/dist/pipeline/dispatch-matched-workflow.d.ts +18 -1
  26. package/dist/pipeline/install-secrets-resolver.d.ts +32 -5
  27. package/dist/pipeline/needs-scheduler.d.ts +12 -10
  28. package/dist/pipeline/pending-workflow-context.d.ts +44 -0
  29. package/dist/pipeline/processor.d.ts +6 -4
  30. package/dist/pipeline/remote-source-store.d.ts +21 -0
  31. package/dist/pipeline/resume-workflow.d.ts +26 -0
  32. package/dist/providers/local/index.d.ts +33 -0
  33. package/dist/providers/local/local-source-config.d.ts +17 -0
  34. package/dist/providers/{internal → local}/lock-file-fetcher.d.ts +6 -6
  35. package/dist/providers/{internal → local}/normalizer.d.ts +28 -28
  36. package/dist/providers/{internal → local}/repo-url-builder.d.ts +6 -6
  37. package/dist/reporting/execution-tracker.d.ts +33 -0
  38. package/dist/routes/admin-events.d.ts +3 -4
  39. package/dist/routes/uploads.d.ts +36 -26
  40. package/dist/server.js +27910 -26483
  41. package/dist/sources/build-platform-sources.d.ts +4 -2
  42. package/dist/stale-detector/stale-run-detector.d.ts +14 -1
  43. package/dist/standalone.js +15708 -15619
  44. package/dist/webhook/generic-sources-listener.d.ts +4 -0
  45. package/dist/webhook/generic-sources.d.ts +37 -11
  46. package/dist/webhook/register-source-bundle.d.ts +14 -4
  47. package/dist/ws/agent-handler.d.ts +17 -0
  48. package/dist/ws/dashboard-dispatch-guard.d.ts +21 -0
  49. package/dist/ws/dashboard-env-handler.d.ts +13 -1
  50. package/dist/ws/oidc-token-relay.d.ts +59 -0
  51. package/dist/ws/platform-client.d.ts +30 -1
  52. package/dist/ws/test-relay-handlers.d.ts +112 -0
  53. package/installer-image-digests.json +3 -3
  54. package/package.json +4 -4
  55. package/sbom.spdx.json +98 -48
  56. package/dist/providers/internal/index.d.ts +0 -32
  57. package/dist/routes/test-trigger.d.ts +0 -41
  58. package/dist/ws/observer-handler.d.ts +0 -42
package/dist/config.d.ts CHANGED
@@ -105,8 +105,6 @@ declare const configSchema: z.ZodObject<{
105
105
  production: "production";
106
106
  test: "test";
107
107
  }>>;
108
- internalProviderRepoPath: z.ZodOptional<z.ZodString>;
109
- internalProviderCloneUrl: z.ZodOptional<z.ZodString>;
110
108
  autoMigrate: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<boolean, string>>;
111
109
  agentMaxReconnectDelayMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
112
110
  skipS3SentinelValidation: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<boolean, string>>;
@@ -262,8 +260,6 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
262
260
  secretKeyFileOld?: string | undefined;
263
261
  bootstrapAdminToken?: string | undefined;
264
262
  testEventFailFirstN?: string | undefined;
265
- internalProviderRepoPath?: string | undefined;
266
- internalProviderCloneUrl?: string | undefined;
267
263
  otelExporterOtlpEndpoint?: string | undefined;
268
264
  clusterName?: string | undefined;
269
265
  }>;
@@ -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
@@ -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,8 @@ 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;
50
53
  }
51
54
  /**
52
55
  * Cluster metadata table
@@ -925,15 +928,17 @@ export interface GenericWebhookSourceTable {
925
928
  deleted_at: Date | null;
926
929
  /** Provider implementation to route this source through.
927
930
  * 'generic' (default) → GenericWebhookNormalizer (Stripe-shaped payloads).
928
- * 'internal' → InternalWebhookNormalizer (github-shaped push/PR payloads
929
- * for E2E tests and the staging smoke test). */
931
+ * 'local' → LocalWebhookNormalizer (github-shaped push/PR payloads for a
932
+ * git repository present on the agent filesystem, cloned via file://). */
930
933
  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. */
934
+ /** Git config (JSONB), dual-purpose discriminated by `provider_type`:
935
+ * - universal-git sources ('generic' with git_config) store a
936
+ * `UniversalGitConfig` (clone URLs, lock-file fetch, clone credentials
937
+ * against Forgejo / Gitea / Gogs / GitLab repo-webhook / GitHub repo).
938
+ * - local sources ('local') store a `LocalSourceConfig`
939
+ * (`{ repoBasePath, cloneUrlBase? }`).
940
+ * null for plain generic sources that don't drive git operations. Shape
941
+ * validated by the matching Zod schema at the application layer. */
937
942
  git_config: ColumnType<string | Record<string, unknown> | null, string | null, string | null>;
938
943
  }
939
944
  export type GenericWebhookSource = Selectable<GenericWebhookSourceTable>;
@@ -1271,6 +1276,24 @@ export interface PendingJobContextsTable {
1271
1276
  }
1272
1277
  export type PendingJobContextRow = Selectable<PendingJobContextsTable>;
1273
1278
  export type NewPendingJobContext = Insertable<PendingJobContextsTable>;
1279
+ /**
1280
+ * Pending workflow dispatch contexts table.
1281
+ * Persists the serializable WorkflowDispatchContext inputs needed to resume a
1282
+ * workflow whose install gate held. One row per held run (keyed by run_id).
1283
+ * Created at hold time, deleted once the resume dispatch is kicked off.
1284
+ */
1285
+ export interface PendingWorkflowContextsTable {
1286
+ /** Run identifier (one pending dispatch per run) */
1287
+ run_id: string;
1288
+ /** Organization id */
1289
+ org_id: string;
1290
+ /** Serializable WorkflowDispatchContext inputs as JSONB */
1291
+ context: ColumnType<Record<string, unknown>, string, string>;
1292
+ /** When this context was stored */
1293
+ created_at: Generated<Date>;
1294
+ }
1295
+ export type PendingWorkflowContextRow = Selectable<PendingWorkflowContextsTable>;
1296
+ export type NewPendingWorkflowContext = Insertable<PendingWorkflowContextsTable>;
1274
1297
  /**
1275
1298
  * Inbound webhook delivery event log table.
1276
1299
  * One row per inbound webhook delivery (relay or direct), regardless of
@@ -1289,7 +1312,7 @@ export interface EventLogTable {
1289
1312
  org_id: string;
1290
1313
  /** Provider delivery ID (unique within org) */
1291
1314
  delivery_id: string;
1292
- /** Routing key (e.g. "github:42", "generic:<org>:<src>", "internal:...") */
1315
+ /** Routing key (e.g. "github:42", "generic:<org>:<src>") */
1293
1316
  routing_key: string;
1294
1317
  /** Provider event name (X-GitHub-Event / X-Event-Type / 'default') */
1295
1318
  event: string;
@@ -1297,7 +1320,7 @@ export interface EventLogTable {
1297
1320
  action: string | null;
1298
1321
  /** Where the delivery arrived: 'relay' (Platform WS) or 'direct' (HTTP) */
1299
1322
  source: string;
1300
- /** Provider type ('github' / 'generic' / 'internal') */
1323
+ /** Provider type ('github' / 'generic' / 'local') */
1301
1324
  provider: string;
1302
1325
  /** owner/repo when extractable */
1303
1326
  repo_identifier: string | null;
@@ -1456,6 +1479,56 @@ export interface CheckRunTrackingTable {
1456
1479
  export type CheckRunTrackingRow = Selectable<CheckRunTrackingTable>;
1457
1480
  export type NewCheckRunTrackingRow = Insertable<CheckRunTrackingTable>;
1458
1481
  export type CheckRunTrackingUpdate = Updateable<CheckRunTrackingTable>;
1482
+ /**
1483
+ * Build-provenance attestation table.
1484
+ *
1485
+ * One row per `ctx.attestProvenance` bundle uploaded to object storage. The
1486
+ * dashboard lists/fetches attestations by `(run_id, job_id)`.
1487
+ */
1488
+ export interface AttestationsTable {
1489
+ /** Random id (primary key). */
1490
+ id: string;
1491
+ /** KiCI run this attestation belongs to. */
1492
+ run_id: string;
1493
+ /** KiCI job this attestation was produced by. */
1494
+ job_id: string;
1495
+ /** Caller-supplied artifact name (e.g. an npm package coordinate). */
1496
+ subject_name: string;
1497
+ /** Primary subject digest (lowercase hex) used in the storage key. */
1498
+ subject_digest: string;
1499
+ /** Object-storage key the bundle was written to. */
1500
+ storage_key: string;
1501
+ /** Signing mode ('kici' for Mode A). */
1502
+ mode: string;
1503
+ /** Bundle media type. */
1504
+ media_type: string;
1505
+ /** When this row was inserted. */
1506
+ created_at: Generated<Date>;
1507
+ }
1508
+ export type AttestationRow = Selectable<AttestationsTable>;
1509
+ export type NewAttestationRow = Insertable<AttestationsTable>;
1510
+ /**
1511
+ * Remote-source table (remote_sources).
1512
+ *
1513
+ * Anchors a Platform-relayed `kici run remote` to its real org: routing key
1514
+ * `remote:<orgId>` maps to the canonical org id so `resolveOrgId` resolves the
1515
+ * real tenant through the same local-source path a webhook takes. One row per
1516
+ * org served by this orchestrator, auto-provisioned on Platform auth.
1517
+ */
1518
+ export interface RemoteSourcesTable {
1519
+ /** Canonical org id (`org_<…>`) this anchor resolves to. */
1520
+ customer_id: string;
1521
+ /** Deterministic routing key `remote:<orgId>`. */
1522
+ routing_key: string;
1523
+ /** Cluster id this orchestrator deployment reports, or null. */
1524
+ cluster_id: string | null;
1525
+ /** When this row was first provisioned. */
1526
+ created_at: Generated<Date>;
1527
+ /** When this row was last upserted. */
1528
+ updated_at: Generated<Date>;
1529
+ }
1530
+ export type RemoteSourceRow = Selectable<RemoteSourcesTable>;
1531
+ export type NewRemoteSourceRow = Insertable<RemoteSourcesTable>;
1459
1532
  /**
1460
1533
  * Scaler spawning-agents table (scaler_spawning_agents).
1461
1534
  *
@@ -13,8 +13,8 @@ import { z } from 'zod';
13
13
  export declare const FleetNodeStatus: z.ZodEnum<{
14
14
  error: "error";
15
15
  ok: "ok";
16
- timeout: "timeout";
17
16
  unreachable: "unreachable";
17
+ timeout: "timeout";
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 {
@@ -121,6 +127,24 @@ export declare class HeldRunStore {
121
127
  * holds notify the waiting agent; job/workflow holds fail the run).
122
128
  */
123
129
  listOverdue(): Promise<HeldRun[]>;
130
+ /**
131
+ * Release overdue workflow `wait_timer` holds. The install-gate wait action
132
+ * pauses the workflow as a held run; on timer expiry it must RESUME (not
133
+ * fail like a reviewer-hold expiry). Flips each overdue pending
134
+ * `hold_type='wait_timer'`, `hold_scope='workflow'` row to `released` and
135
+ * returns a `ReleaseSignal` per row so the caller can resume the workflow.
136
+ * Runs BEFORE `expireOverdue()` so these rows leave the pending pool before
137
+ * the expire-and-fail sweep sees them.
138
+ */
139
+ releaseDueWaitHolds(): Promise<ReleaseSignal[]>;
140
+ /**
141
+ * Release the oldest pending workflow `concurrency` hold for a group. Called
142
+ * when a concurrency slot frees on run completion. Flips the oldest matching
143
+ * pending row to `released` and returns its `ReleaseSignal`, or null when no
144
+ * concurrency hold is queued for the group. `group` matches the held row's
145
+ * `environment_id` (workflow-level install concurrency keys on the env id).
146
+ */
147
+ releaseConcurrencyHold(orgId: string, group: string): Promise<ReleaseSignal | null>;
124
148
  /**
125
149
  * Expire overdue pending runs. Called by the stale detector.
126
150
  * 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
  }
@@ -264,6 +264,8 @@ export declare const InstallSecretsDecisionReason: {
264
264
  readonly ProtectionRuleBlock: "protection_rule_block";
265
265
  readonly MissingToken: "missing_token";
266
266
  readonly MissingInstallEnv: "missing_install_env";
267
+ /** A protection gate held the workflow install (hold / wait / queue). */
268
+ readonly Held: "held";
267
269
  };
268
270
  export type InstallSecretsDecisionReason = (typeof InstallSecretsDecisionReason)[keyof typeof InstallSecretsDecisionReason];
269
271
  /**
@@ -280,8 +282,8 @@ export type InstallSecretsChannel = (typeof InstallSecretsChannel)[keyof typeof
280
282
  * Total install-secrets resolution decisions.
281
283
  *
282
284
  * Labels:
283
- * - decision: pass | reject
284
- * - reason: ok (only with `pass`), or one of the reject reasons enumerated in `InstallSecretsDecisionReason`
285
+ * - decision: pass | reject | hold
286
+ * - reason: ok (only with `pass`), held (only with `hold`), or one of the reject reasons enumerated in `InstallSecretsDecisionReason`
285
287
  *
286
288
  * Pass series are emitted exactly once per resolver call that does any work
287
289
  * (workflows declaring neither `registries:` nor `installEnv:` are NOT
@@ -210,6 +210,54 @@ export interface OrchestratorHooks {
210
210
  */
211
211
  startupLogMessage: (port: number) => string;
212
212
  }
213
+ /**
214
+ * Normalize a lock job's `needs` array (strings, NeedsEntry objects, or
215
+ * NeedsGroupEntry objects) to the set of upstream BASE job names. Group entries
216
+ * are skipped here — group fan-in is resolved separately by the scheduler.
217
+ */
218
+ export declare function upstreamBaseNamesFromNeeds(needs: unknown): string[];
219
+ /**
220
+ * Build the downstream `upstreamJobOutputs` map keyed by BASE name. A base name
221
+ * that fanned into matrix children (rows with `matrix_values`) gets the
222
+ * `{ byMatrix, merged }` envelope; a single non-fanned row keeps the flat shape.
223
+ * Returns undefined when no upstream produced outputs.
224
+ */
225
+ export declare function buildUpstreamOutputsByBase(baseNames: string[], rows: Array<{
226
+ job_name: string;
227
+ outputs: unknown;
228
+ matrix_values: unknown;
229
+ }>): Record<string, Record<string, unknown>> | undefined;
230
+ /**
231
+ * Group an upstream's child rows into the matrix outputs envelope
232
+ * `{ byMatrix: { '<suffix>': outputs }, merged: <last-write-wins> }`. The suffix
233
+ * is the text inside the `(...)` of each expanded child name; children are
234
+ * merged in name order (deterministic, matching dispatch order).
235
+ */
236
+ export declare function buildMatrixOutputsEnvelope(baseName: string, children: Array<{
237
+ job_name: string;
238
+ parsed: Record<string, unknown>;
239
+ }>): {
240
+ byMatrix: Record<string, Record<string, unknown>>;
241
+ merged: Record<string, unknown>;
242
+ };
243
+ /**
244
+ * Merge plain `outputs` and decrypted `secret outputs` from upstream jobs
245
+ * into the dispatch envelope's `secrets` + `upstreamJobOutputs` fields.
246
+ *
247
+ * For a fanned (matrix) upstream, the downstream sees a keyed envelope
248
+ * `{ byMatrix: { '<suffix>': outputs }, merged: <last-write-wins> }` under the
249
+ * BASE name; a non-fanned upstream keeps the flat outputs shape.
250
+ *
251
+ * The needs-aware scheduler guarantees upstreams are
252
+ * terminal before the downstream is dispatched, so the lookups below
253
+ * always find final values. Errors are swallowed (warn-logged) so a flaky
254
+ * upstream-outputs read doesn't block dispatch — the agent will still get
255
+ * its declared secrets, just without the merged upstream additions.
256
+ */
257
+ export declare function mergeUpstreamOutputs(db: Kysely<Database>, runId: string, jobName: string, needs: unknown, dispatchSecrets: Record<string, string> | undefined, secretKey: string): Promise<{
258
+ mergedSecrets: Record<string, string> | undefined;
259
+ upstreamJobOutputs: Record<string, Record<string, unknown>> | undefined;
260
+ }>;
213
261
  export declare function bootstrapOrchestrator(config: AppConfig, hooks: OrchestratorHooks, options?: {
214
262
  otelSdk?: {
215
263
  shutdown(): Promise<void>;
@@ -91,6 +91,23 @@ export interface WorkflowDispatchContext {
91
91
  export interface DispatchMatchedWorkflowResult {
92
92
  /** Number of jobs successfully dispatched (non-rejected). */
93
93
  dispatchedJobCount: number;
94
+ /** True when the workflow install gate paused the dispatch (held run). */
95
+ held?: boolean;
96
+ }
97
+ /** Options controlling a (re-)dispatch of a matched workflow. */
98
+ export interface DispatchMatchedWorkflowOptions {
99
+ /**
100
+ * Resume path: skip the workflow install protection gate (already satisfied)
101
+ * so secrets resolve directly and the dispatch flows into job dispatch.
102
+ */
103
+ skipInstallProtectionGate?: boolean;
104
+ /** The released held-run id being resumed (for logging / correlation). */
105
+ reuseHeldRunId?: string;
106
+ /**
107
+ * The run id whose `held` execution_runs row should be reused (flipped to
108
+ * pending) instead of inserting a fresh row.
109
+ */
110
+ reuseRunId?: string;
94
111
  }
95
112
  /**
96
113
  * Dispatch a single matched workflow.
@@ -107,5 +124,5 @@ export interface DispatchMatchedWorkflowResult {
107
124
  * I. deferred init dispatch (fire-and-forget per job)
108
125
  * J. deferred dynamic dispatch (fire-and-forget per dynamic entry)
109
126
  */
110
- export declare function dispatchMatchedWorkflow(ctx: WorkflowDispatchContext): Promise<DispatchMatchedWorkflowResult>;
127
+ export declare function dispatchMatchedWorkflow(ctx: WorkflowDispatchContext, opts?: DispatchMatchedWorkflowOptions): Promise<DispatchMatchedWorkflowResult>;
111
128
  //# sourceMappingURL=dispatch-matched-workflow.d.ts.map
@@ -8,10 +8,13 @@
8
8
  *
9
9
  * 1. Parses every qualified ref and groups by environment name.
10
10
  * 2. Fires the per-environment protection-rule pipeline once per unique env
11
- * (branch / trust / concurrency / reviewer / wait-timer). Any non-pass
12
- * result rejects the whole workflow dispatch v1 does not yet support
13
- * workflow-scoped held-runs, so a `hold` from the reviewer-gate is
14
- * surfaced as a reject with a clear reason.
11
+ * (branch / trust / concurrency / reviewer / wait-timer). A `reject`
12
+ * result fails the whole workflow dispatch with a clear reason; a
13
+ * `hold` / `wait` / `queue` result returns a structured `hold` decision
14
+ * so the caller can pause the workflow dispatch as a workflow-scoped held
15
+ * run and resume it when the gate clears. On the resume path the caller
16
+ * sets `skipProtectionGate` so the gate (already satisfied) is bypassed
17
+ * and secrets are resolved directly.
15
18
  * 3. Resolves secrets per environment via `secretResolver.resolveForJob`
16
19
  * (which writes its own audit log lines).
17
20
  * 4. Validates each registry URL scheme: HTTPS always allowed; `http://`
@@ -26,7 +29,7 @@
26
29
  * Pure helper: no imports of the dispatch giant. Tested in isolation in
27
30
  * `install-secrets-resolver.test.ts`.
28
31
  */
29
- import type { LockRegistry } from '@kici-dev/engine';
32
+ import type { ApproverClause, LockRegistry } from '@kici-dev/engine';
30
33
  import type { TrustResolution } from '../security/trust-resolver.js';
31
34
  import type { SecretResolver } from '../secrets/secret-resolver.js';
32
35
  import type { EnvironmentStore } from '../environments/environment-store.js';
@@ -47,6 +50,18 @@ export interface ResolveInstallSecretsArgs {
47
50
  environmentStore: EnvironmentStore | undefined;
48
51
  secretResolver: SecretResolver | undefined;
49
52
  protectionContext: JobDispatchContext;
53
+ /**
54
+ * Resume path: skip the protection-rule gate (already satisfied) and resolve
55
+ * secrets directly. The untrusted-contributor strip still runs first.
56
+ */
57
+ skipProtectionGate?: boolean;
58
+ }
59
+ /** Normalized requirement carried on a `hold` decision. */
60
+ export interface InstallHoldRequirement {
61
+ clauses: ApproverClause[];
62
+ /** ISO timestamp after which the hold expires. */
63
+ expiresAt: string;
64
+ reason: string;
50
65
  }
51
66
  export type ResolveInstallSecretsResult = {
52
67
  decision: 'pass';
@@ -56,6 +71,18 @@ export type ResolveInstallSecretsResult = {
56
71
  } | {
57
72
  decision: 'reject';
58
73
  reason: string;
74
+ } | {
75
+ decision: 'hold';
76
+ /** The gate action that paused the dispatch. */
77
+ action: 'hold' | 'wait' | 'queue';
78
+ /** The environment whose install gate held. */
79
+ envName: string;
80
+ /** Resolved environment id (for the held row). */
81
+ environmentId: string;
82
+ /** Discriminates the release trigger: 'reviewer' | 'wait_timer' | 'concurrency' | 'security'. */
83
+ holdType: string;
84
+ queueType: 'environment' | 'security';
85
+ requirement: InstallHoldRequirement;
59
86
  };
60
87
  /** Parse `<environment>:<secret-name>`. Returns null on malformed input. */
61
88
  export declare function parseQualifiedSecretRef(ref: string): {
@@ -19,25 +19,28 @@
19
19
  */
20
20
  import type { Kysely } from 'kysely';
21
21
  import type { Database } from '../db/types.js';
22
- import type { NeedsEntry, NeedsGroupEntry } from '@kici-dev/engine';
22
+ import type { MaterializedJob } from '@kici-dev/engine';
23
23
  /** Result of evaluating downstream jobs after an upstream completes. */
24
24
  export interface SchedulerResult {
25
25
  jobName: string;
26
26
  action: 'dispatch' | 'skip';
27
27
  reason?: string;
28
28
  }
29
- /** Minimal lock job shape needed by insertEdgesForRun. */
30
- interface LockJobLike {
31
- readonly name: string;
32
- readonly needs: readonly (string | NeedsEntry | NeedsGroupEntry)[];
33
- readonly dependsOnGroups?: readonly string[];
34
- }
35
29
  /**
36
30
  * Insert dependency edges at run start for static-to-static needs.
31
+ *
32
+ * Operates on materialized jobs (matrix-expanded children) and an expansion map
33
+ * (base job name -> the expanded child names). An upstream reference to a base
34
+ * name `test` that fanned into `test (a)` / `test (b)` produces one edge per
35
+ * child, so a downstream `needs: ['test']` waits for ALL children to terminate.
36
+ * Each materialized child likewise inherits the full upstream edge set under its
37
+ * own expanded name.
38
+ *
37
39
  * NeedsGroupEntry items are skipped (resolved later via resolveGroupEdges).
38
- * Root jobs (no needs, no dependsOnGroups) are marked needs_satisfied=true.
40
+ * Root jobs (no needs, no dependsOnGroups) are marked needs_satisfied=true,
41
+ * keyed by expanded name.
39
42
  */
40
- export declare function insertEdgesForRun(db: Kysely<Database>, runId: string, lockJobs: readonly LockJobLike[]): Promise<void>;
43
+ export declare function insertEdgesForRun(db: Kysely<Database>, runId: string, jobs: readonly MaterializedJob[], expansionMap: ReadonlyMap<string, readonly string[]>): Promise<void>;
41
44
  /**
42
45
  * Resolve dynamic group edges after eval completion.
43
46
  *
@@ -81,5 +84,4 @@ export declare function checkSchedulerInvariant(db: Kysely<Database>, runId: str
81
84
  * due to failure propagation. Only follows edges where if_failed='skip'.
82
85
  */
83
86
  export declare function getFailurePropagationTargets(db: Kysely<Database>, runId: string, failedJobName: string): Promise<string[]>;
84
- export {};
85
87
  //# sourceMappingURL=needs-scheduler.d.ts.map