@kici-dev/orchestrator 0.1.23 → 0.1.25

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 (86) hide show
  1. package/dist/agent/token-store.d.ts +4 -0
  2. package/dist/app.d.ts +27 -0
  3. package/dist/audit/access-log.d.ts +4 -0
  4. package/dist/cache/pending-inits.d.ts +3 -2
  5. package/dist/cancel/cancel-run.d.ts +2 -0
  6. package/dist/cli/api-client.d.ts +10 -0
  7. package/dist/cli/commands/attestations-list.d.ts +49 -0
  8. package/dist/cli/commands/attestations-retry.d.ts +28 -0
  9. package/dist/cli/commands/attestations-reverify.d.ts +18 -0
  10. package/dist/cli/commands/attestations.d.ts +4 -0
  11. package/dist/cli/commands/cluster.d.ts +3 -0
  12. package/dist/cli/commands/shared/versioned-upgrade.d.ts +52 -0
  13. package/dist/cli/local-github-ingress-url.d.ts +8 -0
  14. package/dist/cli.js +3308 -1011
  15. package/dist/cluster/cluster-identity.d.ts +11 -0
  16. package/dist/cluster/reconcile-identity.d.ts +50 -0
  17. package/dist/cold-store/load-access-log-range.d.ts +4 -0
  18. package/dist/config.d.ts +4 -0
  19. package/dist/dashboard/attestation-filters.d.ts +131 -0
  20. package/dist/dashboard/handler.d.ts +56 -4
  21. package/dist/db/migrations/054_local_working_tree.d.ts +15 -0
  22. package/dist/db/migrations/055_agent_token_mandatory_labels.d.ts +18 -0
  23. package/dist/db/migrations/056_execution_jobs_environments.d.ts +17 -0
  24. package/dist/db/migrations/057_step_concurrency.d.ts +4 -0
  25. package/dist/db/migrations/058_access_log_agent_label.d.ts +4 -0
  26. package/dist/db/migrations/059_attestation_verdict.d.ts +4 -0
  27. package/dist/db/migrations/060_run_trigger_actor.d.ts +4 -0
  28. package/dist/db/migrations/061_execution_runs_environment_id.d.ts +4 -0
  29. package/dist/db/migrations/062_execution_runs_agent_label.d.ts +4 -0
  30. package/dist/db/migrations/063_access_log_agent_label_index.d.ts +10 -0
  31. package/dist/db/migrations/064_execution_jobs_skipped_environments.d.ts +17 -0
  32. package/dist/db/migrations/065_pending_attestations.d.ts +22 -0
  33. package/dist/db/migrations/066_pending_attestations_rejected.d.ts +13 -0
  34. package/dist/db/types.d.ts +113 -0
  35. package/dist/environments/protection/aggregate.d.ts +43 -0
  36. package/dist/environments/protection/satisfiability.d.ts +69 -0
  37. package/dist/events/circuit-breaker.d.ts +8 -5
  38. package/dist/events/event-router.d.ts +12 -4
  39. package/dist/events/event-store.d.ts +21 -3
  40. package/dist/events/trust-store.d.ts +6 -1
  41. package/dist/events/types.d.ts +6 -1
  42. package/dist/helpers/rate-limiter.d.ts +17 -2
  43. package/dist/index.js +11 -1
  44. package/dist/metrics/prometheus.d.ts +6 -0
  45. package/dist/orchestrator-core.d.ts +8 -0
  46. package/dist/pipeline/dispatch-matched-workflow.d.ts +8 -0
  47. package/dist/pipeline/flatten-lock-steps.d.ts +11 -0
  48. package/dist/pipeline/inline-eval.d.ts +2 -1
  49. package/dist/pipeline/job-environments.d.ts +71 -0
  50. package/dist/pipeline/manual-schedule.d.ts +23 -1
  51. package/dist/pipeline/process-webhook.d.ts +14 -1
  52. package/dist/pipeline/rerun.d.ts +1 -1
  53. package/dist/pipeline/test-pipeline.d.ts +15 -0
  54. package/dist/provenance/attestation-retrier.d.ts +92 -0
  55. package/dist/provenance/backfill-run.d.ts +41 -0
  56. package/dist/provenance/pending-attestations-repo.d.ts +64 -0
  57. package/dist/provenance/trust-root.d.ts +24 -0
  58. package/dist/provenance/verify-at-ingest.d.ts +31 -0
  59. package/dist/reporting/agent-failure-category.d.ts +27 -0
  60. package/dist/reporting/agent-run-result-mapper.d.ts +13 -0
  61. package/dist/reporting/execution-tracker.d.ts +38 -6
  62. package/dist/reporting/run-aggregator.d.ts +165 -0
  63. package/dist/reporting/step-log-reader.d.ts +39 -0
  64. package/dist/routes/admin-access-log.d.ts +2 -1
  65. package/dist/routes/admin-registrations.d.ts +8 -0
  66. package/dist/routes/admin-runs.d.ts +7 -0
  67. package/dist/routes/admin.d.ts +14 -0
  68. package/dist/routes/github-webhook.d.ts +37 -0
  69. package/dist/routes/health.d.ts +9 -0
  70. package/dist/routes/webhooks.d.ts +2 -4
  71. package/dist/secrets/index.d.ts +1 -1
  72. package/dist/server.js +5326 -2100
  73. package/dist/sources/source-store.d.ts +6 -0
  74. package/dist/stale-detector/workflow-deadline-detector.d.ts +25 -0
  75. package/dist/standalone.js +19787 -17304
  76. package/dist/storage/loopback-guard.d.ts +49 -0
  77. package/dist/webhook/dedup.d.ts +14 -0
  78. package/dist/ws/agent-handler.d.ts +38 -1
  79. package/dist/ws/oidc-token-relay.d.ts +31 -21
  80. package/dist/ws/orch-rpc.d.ts +9 -0
  81. package/dist/ws/platform-client.d.ts +53 -1
  82. package/dist/ws/test-relay-handlers.d.ts +2 -0
  83. package/installer-image-digests.json +3 -3
  84. package/package.json +6 -8
  85. package/sbom.spdx.json +62 -57
  86. package/dist/secrets/crypto.d.ts +0 -49
@@ -49,6 +49,7 @@ export interface Database {
49
49
  scaler_agent_jobs: ScalerAgentJobsTable;
50
50
  scaler_reservations: ScalerReservationsTable;
51
51
  attestations: AttestationsTable;
52
+ pending_attestations: PendingAttestationsTable;
52
53
  remote_sources: RemoteSourcesTable;
53
54
  host_roster: HostRosterTable;
54
55
  }
@@ -271,6 +272,8 @@ export interface ExecutionRunTable {
271
272
  provider_context: Generated<string>;
272
273
  /** Whether this is a CLI-initiated test run */
273
274
  is_test_run: Generated<boolean>;
275
+ /** True when the run executed an uploaded local working tree (`kici run remote`). */
276
+ local_working_tree: Generated<boolean>;
274
277
  /** Fixture ID for test runs (null for real webhook runs) */
275
278
  fixture_id: string | null;
276
279
  /** Parent run ID for re-run lineage (null for original runs). */
@@ -279,16 +282,38 @@ export interface ExecutionRunTable {
279
282
  original_run_id: string | null;
280
283
  /** User identity that triggered this re-run (null for webhook-triggered). Format: "user:email" or "key:name". */
281
284
  triggered_by: string | null;
285
+ /** Agent provenance label when the run was triggered through an agent credential (null otherwise). */
286
+ triggered_by_agent_label: string | null;
282
287
  /** User identity that cancelled this run (null for non-cancelled). */
283
288
  cancelled_by: string | null;
289
+ /** Agent provenance label when the run was cancelled through an agent credential (null otherwise). */
290
+ cancelled_by_agent_label: string | null;
284
291
  /** Environment name for this run (null if no environment applies) */
285
292
  environment: string | null;
293
+ /** Matched environment id for this run (null if no/unresolved environment). */
294
+ environment_id: string | null;
286
295
  /** Trust tier of the contributor for PR runs (null for non-PR events) */
287
296
  trust_tier: string | null;
288
297
  /** Lock file source: 'head' or 'base' (null for non-PR events) */
289
298
  lock_file_source: string | null;
290
299
  /** Username of the contributor (null for non-PR events) */
291
300
  contributor_username: string | null;
301
+ /**
302
+ * Origin provider of the triggering actor (`github` today). Provider-generic
303
+ * so GitLab/Bitbucket extend later. Null when no actor was captured.
304
+ */
305
+ trigger_actor_provider: string | null;
306
+ /**
307
+ * Provider login of the person who triggered the run (pusher / PR author).
308
+ * Captured for ALL event types, unlike the PR-only `contributor_username`.
309
+ */
310
+ trigger_actor_username: string | null;
311
+ /**
312
+ * Immutable provider user id of the triggering actor (mirrors
313
+ * `identity_links.provider_user_id`). Preferred over the mutable username
314
+ * when resolving the actor to a KiCI user.
315
+ */
316
+ trigger_actor_user_id: string | null;
292
317
  /** Human-readable reason why the run failed (null for non-failed runs). */
293
318
  failure_reason: string | null;
294
319
  /**
@@ -371,6 +396,19 @@ export interface ExecutionJobTable {
371
396
  dispatched_contexts: Generated<string>;
372
397
  /** Aggregated step outputs JSONB (step-keyed map of outputs). Populated on job success. */
373
398
  outputs: string | null;
399
+ /**
400
+ * Ordered bound deployment-environment names for this job, JSON-encoded
401
+ * `string[]` (null when the job binds none). Written at dispatch and
402
+ * overwritten with the agent-resolved list for dynamic environments.
403
+ */
404
+ environments: string | null;
405
+ /**
406
+ * Bound environments skipped on a test/local run (non-test or unconfigured),
407
+ * JSON-encoded `string[]`. NULL = nothing skipped.
408
+ */
409
+ skipped_environments: string | null;
410
+ /** User-visible warning naming the skipped test-run environments. NULL = none. */
411
+ env_warning: string | null;
374
412
  /** Whether all upstream needs edges are satisfied (dispatch gate). */
375
413
  needs_satisfied: Generated<boolean>;
376
414
  /** Timestamp when needs_satisfied first flipped to true. */
@@ -453,6 +491,13 @@ export interface ExecutionStepTable {
453
491
  drift_summary: string | null;
454
492
  /** Structured drift value returned by `check()` (JSONB). NULL when no drift. */
455
493
  drift: ColumnType<unknown | null, unknown, unknown>;
494
+ /**
495
+ * Parallel step-group concurrency role (`sequential` | `parallel-child` |
496
+ * `parallel-group`). NULL for an ordinary sequential step.
497
+ */
498
+ concurrency_kind: string | null;
499
+ /** Parallel-group correlation id shared by a group's children. NULL for sequential steps. */
500
+ group_id: string | null;
456
501
  /** When this record was created */
457
502
  created_at: Generated<Date>;
458
503
  /**
@@ -804,6 +849,12 @@ export interface AccessLogTable {
804
849
  source: string;
805
850
  outcome: string;
806
851
  error_message: string | null;
852
+ /**
853
+ * Human-set agent name, when the actor authenticated with an agent-kind PAT.
854
+ * NULL for ordinary human / API-key / system actors. Queryable so the access
855
+ * log can be filtered by agent.
856
+ */
857
+ agent_label: string | null;
807
858
  created_at: Generated<Date>;
808
859
  /**
809
860
  * Set inside the archive transaction before the row is DELETEd.
@@ -857,6 +908,14 @@ export interface AgentTokenTable {
857
908
  token_prefix: string;
858
909
  /** JSON-encoded string[] of agent labels (null = any) */
859
910
  labels: string | null;
911
+ /**
912
+ * JSON-encoded string[] of mandatory labels (a Kubernetes-taint-style gate):
913
+ * a static agent registering with this token only accepts a job when every
914
+ * label here appears in the job's required labels. null = no taint (the
915
+ * default; the agent accepts any job its advertised labels match).
916
+ * Authorized at mint time alongside `labels`.
917
+ */
918
+ mandatory_labels: string | null;
860
919
  /** Token type: 'ephemeral' (scaler-issued) or 'static' (CLI-created) */
861
920
  agent_type: string;
862
921
  /** When this token was created */
@@ -1585,9 +1644,63 @@ export interface AttestationsTable {
1585
1644
  media_type: string;
1586
1645
  /** When this row was inserted. */
1587
1646
  created_at: Generated<Date>;
1647
+ /**
1648
+ * Server-side verification verdict computed at ingest. One of
1649
+ * `verified` / `failed` / `unverifiable` / `pending`
1650
+ * (`attestationVerifyStatusSchema.enum.*`). DB default is `pending`.
1651
+ */
1652
+ verify_status: Generated<string>;
1653
+ /** First failure code from `verifyKiciBundle`, or NULL when verified/pending. */
1654
+ verify_reason: string | null;
1655
+ /** When the verdict was recorded (explicitly set, not DB-generated). */
1656
+ verified_at: Date | null;
1588
1657
  }
1589
1658
  export type AttestationRow = Selectable<AttestationsTable>;
1590
1659
  export type NewAttestationRow = Insertable<AttestationsTable>;
1660
+ /**
1661
+ * Deferred-attestation outbox: a build whose provenance mint failed transiently
1662
+ * freezes its DSSE-signed statement here; a leader-only retrier mints the token
1663
+ * later and records the fulfilled `attestations` row.
1664
+ */
1665
+ export interface PendingAttestationsTable {
1666
+ /** Random id (primary key). */
1667
+ id: string;
1668
+ /** KiCI run this deferred attestation belongs to. */
1669
+ run_id: string;
1670
+ /** KiCI job this deferred attestation was produced by. */
1671
+ job_id: string;
1672
+ /** Caller-supplied artifact name. */
1673
+ subject_name: string;
1674
+ /** Primary subject digest (lowercase hex). */
1675
+ subject_digest: string;
1676
+ /** Requested token audience. */
1677
+ audience: string;
1678
+ /** The frozen, agent-signed DSSE envelope (JSONB). */
1679
+ dsse_envelope: unknown;
1680
+ /** The ephemeral public-key JWK (JSONB). */
1681
+ public_key: unknown;
1682
+ /** Bundle media type. */
1683
+ media_type: string;
1684
+ /** SHA-256 of the frozen statement payload — the later-mint binding. */
1685
+ statement_hash: string;
1686
+ /** Non-`live` AttestationOrigin: `deferred` | `offline-backfill`. */
1687
+ origin_kind: string;
1688
+ /** Retry attempts so far. */
1689
+ attempt_count: Generated<number>;
1690
+ /** First-capture time = the true build-time anchor. */
1691
+ created_at: Generated<Date>;
1692
+ /** When fulfilment was last attempted. */
1693
+ last_attempt_at: Date | null;
1694
+ /** Last fulfilment error, or NULL. */
1695
+ last_error: string | null;
1696
+ /**
1697
+ * Terminal-rejection time: the Platform definitively rejected the mint
1698
+ * (run/job absent). NULL while the row is still pending a later mint.
1699
+ */
1700
+ rejected_at: Date | null;
1701
+ }
1702
+ export type PendingAttestationRow = Selectable<PendingAttestationsTable>;
1703
+ export type NewPendingAttestationRow = Insertable<PendingAttestationsTable>;
1591
1704
  /**
1592
1705
  * Remote-source table (remote_sources).
1593
1706
  *
@@ -0,0 +1,43 @@
1
+ import { EnvGateRejectReason, type Environment } from '@kici-dev/engine';
2
+ import type { JobDispatchContext } from './pipeline.js';
3
+ /** A single environment's rejection under all-must-pass aggregation. */
4
+ export interface EnvGateRejection {
5
+ environment: string;
6
+ reason: EnvGateRejectReason;
7
+ detail: string;
8
+ }
9
+ /** Effective protection parameters after most-restrictive aggregation. */
10
+ export interface EffectiveProtection {
11
+ minimumTrust?: 'known' | 'trusted';
12
+ requiredReviewers: string[];
13
+ waitTimerSeconds: number | null;
14
+ holdExpirySeconds: number;
15
+ concurrencyLimit: number | null;
16
+ concurrencyStrategy: 'queue' | 'cancel-pending';
17
+ }
18
+ /**
19
+ * Evaluate each environment's hard reject gates against the run context. A name
20
+ * with no `Environment` record yields an `env_not_found` rejection. Returns all
21
+ * rejections (empty = every environment passed the reject gates).
22
+ */
23
+ export declare function evaluateMultiEnvGates(envs: ReadonlyArray<{
24
+ name: string;
25
+ env: Environment | undefined;
26
+ }>, ctx: JobDispatchContext): EnvGateRejection[];
27
+ /**
28
+ * Aggregate hold/wait/queue parameters across all bound environments, most
29
+ * restrictive wins: trust = max tier, reviewers = sorted dedup union, wait timer
30
+ * = max, hold expiry = min, concurrency limit = min (tightest). The concurrency
31
+ * strategy follows the primary (first) environment.
32
+ */
33
+ export declare function aggregateProtectionParams(envs: ReadonlyArray<Environment>): EffectiveProtection;
34
+ /**
35
+ * Build a synthetic `Environment` carrying the aggregated protection parameters,
36
+ * so the existing per-rule gate functions (trust/concurrency/reviewer/wait) can
37
+ * evaluate the all-must-pass holds in one pass. Branch/trigger/repo/enabled are
38
+ * already handled by `evaluateMultiEnvGates`, so they are neutralized here.
39
+ */
40
+ export declare function buildEffectiveEnvironment(primary: Environment, eff: EffectiveProtection): Environment;
41
+ /** Format a human-readable rejection reason naming the env(s) and rule(s). */
42
+ export declare function formatMultiEnvRejection(rejections: ReadonlyArray<EnvGateRejection>): string;
43
+ //# sourceMappingURL=aggregate.d.ts.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Registration-time satisfiability check for multi-environment job bindings.
3
+ *
4
+ * When a job binds several environments (`environments: [...]`), every
5
+ * environment's protection gates must pass for the job to ever dispatch
6
+ * (all-must-pass aggregation — see `aggregate.ts`). Some of those gates are
7
+ * decidable statically: if two bound environments restrict to disjoint fixed
8
+ * branch sets, no run can ever satisfy both, so the binding is provably
9
+ * unsatisfiable and should be rejected at registration rather than failing
10
+ * silently at every future dispatch.
11
+ *
12
+ * This module intersects the statically-decidable set rules (branch, trigger
13
+ * type, repository — only when every pattern is a literal, never a glob) plus
14
+ * the `enabled` gate across the **resolved** bound environments, and reports the
15
+ * first provably-empty intersection. Bound names that resolve to no environment
16
+ * record are lenient — skipped, never rejected — matching the dispatch-time
17
+ * behavior in `dispatch-matched-workflow.ts`. Any glob in a rule makes that rule
18
+ * undecidable, so it is skipped here and left to the dispatch-time catch-all
19
+ * (`evaluateMultiEnvGates`).
20
+ */
21
+ import { z } from 'zod';
22
+ import type { Environment } from '@kici-dev/engine';
23
+ /** Which decidable rule made a binding unsatisfiable. */
24
+ export declare const UnsatisfiableRule: z.ZodEnum<{
25
+ enabled: "enabled";
26
+ repo: "repo";
27
+ branch: "branch";
28
+ trigger: "trigger";
29
+ }>;
30
+ export type UnsatisfiableRule = z.infer<typeof UnsatisfiableRule>;
31
+ /** A provably-unsatisfiable multi-environment binding, naming the rule + reason. */
32
+ export interface UnsatisfiableBinding {
33
+ jobName: string;
34
+ environments: string[];
35
+ rule: UnsatisfiableRule;
36
+ message: string;
37
+ }
38
+ /**
39
+ * Returns a precise problem when the bound environments can NEVER be jointly
40
+ * satisfied (a disabled environment, or mutually-exclusive fixed restrictions
41
+ * among the resolved environments), else `null`. Missing (unresolved) names are
42
+ * skipped — a bound name with no environment record contributes no protection
43
+ * rules and is lenient at dispatch, so it is not rejected here. Glob /
44
+ * undecidable cases also return `null` and are caught at dispatch by
45
+ * `evaluateMultiEnvGates`.
46
+ *
47
+ * `envs[i]` is the resolved `Environment` for `envNames[i]` (undefined when the
48
+ * name has no environment record). Only the statically-known (non-dynamic) bound
49
+ * names should be passed — dynamic elements are unknown at registration and the
50
+ * all-must-pass semantics make the static subset's exclusivity still sound.
51
+ */
52
+ export declare function checkBindingSatisfiable(jobName: string, envs: ReadonlyArray<Environment | undefined>, envNames: readonly string[]): UnsatisfiableBinding | null;
53
+ /** Minimal lock-workflow shape needed to walk its jobs for satisfiability. */
54
+ interface SatisfiabilityLockWorkflow {
55
+ jobs?: readonly unknown[];
56
+ }
57
+ /**
58
+ * Walk every workflow's static jobs and reject the registration when a bound
59
+ * environment list is provably unsatisfiable (a disabled environment, or
60
+ * mutually-exclusive fixed branch/trigger/repo restrictions among the resolved
61
+ * environments — missing names are lenient, never rejected). Dynamic elements
62
+ * are skipped (unresolvable at registration); the all-must-pass semantics keep
63
+ * the static subset's exclusivity sound. Throws the first
64
+ * `UnsatisfiableBinding.message` so the registration route / direct helper
65
+ * surfaces it to the caller.
66
+ */
67
+ export declare function assertWorkflowsSatisfiable(workflows: ReadonlyArray<SatisfiabilityLockWorkflow>, resolveEnv: (name: string) => Promise<Environment | null>): Promise<void>;
68
+ export {};
69
+ //# sourceMappingURL=satisfiability.d.ts.map
@@ -1,10 +1,11 @@
1
1
  import type { EventRouterConfig } from './types.js';
2
2
  /**
3
- * Circuit breaker for event loop detection and per-workflow rate limiting.
3
+ * Circuit breaker for event loop detection and user-event rate limiting.
4
4
  *
5
5
  * Two-layer protection:
6
6
  * 1. Chain depth: Rejects events exceeding configurable max chain depth.
7
- * 2. Rate limiting: Sliding-window per-workflow rate limiter.
7
+ * 2. Rate limiting: Sliding-window limiter keyed per (source routing key +
8
+ * event name). System events (`__`-prefixed) are exempt upstream.
8
9
  */
9
10
  export declare class EventCircuitBreaker {
10
11
  private readonly config;
@@ -18,10 +19,12 @@ export declare class EventCircuitBreaker {
18
19
  reason?: string;
19
20
  };
20
21
  /**
21
- * Check if a workflow is within its per-minute rate limit.
22
- * Uses a sliding window of 60 seconds.
22
+ * Check whether a rate-limit key is within its per-minute allowance, using a
23
+ * sliding 60-second window. Callers key user events by
24
+ * `<sourceRoutingKey>:<eventName>`; system events (`__`-prefixed) are exempt
25
+ * upstream and never reach here.
23
26
  */
24
- checkRateLimit(workflowKey: string): {
27
+ checkRateLimit(rateKey: string): {
25
28
  allowed: boolean;
26
29
  retryAfterMs?: number;
27
30
  };
@@ -159,11 +159,19 @@ export declare class EventRouter {
159
159
  */
160
160
  private buildSimulatedEvent;
161
161
  /**
162
- * Catch-up: process unprocessed events missed during downtime.
162
+ * Catch-up: process every unprocessed event missed during downtime.
163
163
  *
164
- * Uses lease-based dispatch identical to the live path, so a catch-up
165
- * dispatch failure schedules a retry via the leader-only scanner instead
166
- * of being silently dropped.
164
+ * Pages through the backlog with a keyset cursor: fetch a batch, dispatch
165
+ * each event (lease-based, identical failure semantics to the live path),
166
+ * advance the cursor to the last event of the batch, and repeat until a
167
+ * batch returns fewer than EVENT_CATCHUP_BATCH_SIZE rows. Without this loop
168
+ * only the oldest page (100 events) would be dispatched and the remainder
169
+ * would sit unprocessed — invisible to both the live NOTIFY path and the
170
+ * retry scanner — until the TTL cleanup deleted them undelivered.
171
+ *
172
+ * The store's cursor is strictly monotone over (created_at, id), so a
173
+ * still-unprocessed retrying event at or before the cursor is never
174
+ * re-fetched — that strict advance is what guarantees this loop terminates.
167
175
  */
168
176
  private catchUp;
169
177
  }
@@ -3,6 +3,14 @@ import type { Database } from '../db/types.js';
3
3
  import type { DlqReason, EventRouterConfig, StoredEvent } from './types.js';
4
4
  /** Either a Kysely DB handle or an active transaction. */
5
5
  export type DbExecutor = Kysely<Database> | Transaction<Database>;
6
+ /**
7
+ * Default page size for unprocessed-event catch-up scans.
8
+ *
9
+ * Shared with EventRouter.catchUp so the paginating loop's "a short batch
10
+ * means we're done" termination check compares against the same value the
11
+ * store slices by.
12
+ */
13
+ export declare const EVENT_CATCHUP_BATCH_SIZE = 100;
6
14
  /**
7
15
  * Input shape for writing a new event. Excludes columns the DB fills in
8
16
  * itself (id, processed, created_at, attempts, claimed_*, last_error,
@@ -58,9 +66,19 @@ export declare class EventStore {
58
66
  */
59
67
  getById(id: string): Promise<StoredEvent | null>;
60
68
  /**
61
- * Get unprocessed events for catch-up on reconnect.
62
- * If sinceId is null, returns all unprocessed events.
63
- * Ordered by created_at ASC. Excludes DLQ rows.
69
+ * Get a page of unprocessed events for catch-up on start.
70
+ *
71
+ * Rows are ordered by a deterministic keyset `(created_at ASC, id ASC)` and
72
+ * exclude processed + DLQ rows. When `sinceId` is null, returns the first
73
+ * page from the oldest event. When `sinceId` is given, returns the page
74
+ * strictly after that event's `(created_at, id)` — a composite cursor so
75
+ * that events sharing the reference event's `created_at` are NOT skipped
76
+ * (a bare `created_at > ref` predicate drops same-timestamp siblings) and a
77
+ * still-unprocessed retrying event at or before the cursor is never
78
+ * re-fetched (which is what makes the caller's pagination loop terminate).
79
+ *
80
+ * The caller advances `sinceId` to the last event of each returned page and
81
+ * re-queries until a page returns fewer than `limit` rows.
64
82
  */
65
83
  getUnprocessedSince(sinceId: string | null, limit?: number): Promise<StoredEvent[]>;
66
84
  /**
@@ -17,7 +17,12 @@ interface TrustEntry {
17
17
  *
18
18
  * Same-repo events (same routing key) are always trusted without a DB lookup.
19
19
  * Cross-repo events require an explicit enabled row in the cross_repo_trust table.
20
- * Glob-based event filtering is supported via the allowed_events column.
20
+ * Glob-based event filtering is supported via the allowed_events column, whose
21
+ * value carries a three-way meaning:
22
+ * - `null` — filter unset: every event name is allowed (allow-all).
23
+ * - `'[]'` — an explicit empty allow-list: no event is allowed (deny-all).
24
+ * - `'["glob"]'` — only event names matching one of the globs are allowed.
25
+ * Malformed (unparseable / non-array) allowed_events fails closed (deny).
21
26
  */
22
27
  export declare class TrustStore {
23
28
  private readonly db;
@@ -7,7 +7,12 @@
7
7
  export interface EventRouterConfig {
8
8
  /** Maximum allowed chain depth before circuit breaker trips (default: 10) */
9
9
  maxChainDepth: number;
10
- /** Maximum event emissions per workflow per minute (default: 100) */
10
+ /**
11
+ * Max user-event emissions per (source routing key + event name) per minute
12
+ * (sliding 60s window). System events (`__`-prefixed, e.g. __workflow_complete)
13
+ * are EXEMPT -- they are orchestrator-emitted once per completion and cannot
14
+ * loop, so the storm guard does not apply. Default 100.
15
+ */
11
16
  rateLimitPerWorkflowPerMinute: number;
12
17
  /** TTL for persisted events in seconds (default: 604800 = 7 days) */
13
18
  eventTtlSeconds: number;
@@ -1,13 +1,21 @@
1
1
  /**
2
2
  * Sliding-window rate limiter.
3
3
  *
4
- * Tracks timestamps of recent events per key and rejects new events
5
- * once the count within the window exceeds the configured maximum.
4
+ * Tracks timestamps of recent events per key and rejects new events once the
5
+ * count within the window exceeds the configured maximum.
6
+ *
7
+ * The internal Map stays bounded to the recently-active key set. A checked key
8
+ * whose window has fully expired is dropped before this call decides whether to
9
+ * record a new timestamp, so a call that records nothing (for example when the
10
+ * limit is zero) leaves no residual bucket. Keys that are never checked again
11
+ * are reclaimed by an amortized sweep that runs at most once per window, so the
12
+ * per-call hot path stays O(1) amortized even though a single sweep is O(n).
6
13
  */
7
14
  export declare class SlidingWindowRateLimiter {
8
15
  private readonly maxPerWindow;
9
16
  private readonly windowMs;
10
17
  private state;
18
+ private lastSweepAt;
11
19
  constructor(maxPerWindow: number, windowMs?: number);
12
20
  /**
13
21
  * Check if a new event is allowed for the given key.
@@ -18,6 +26,13 @@ export declare class SlidingWindowRateLimiter {
18
26
  allowed: boolean;
19
27
  retryAfterMs?: number;
20
28
  };
29
+ /**
30
+ * Evict buckets whose newest timestamp is outside the window. Runs at most
31
+ * once per window so the amortized cost per check() stays O(1).
32
+ */
33
+ private maybeSweep;
34
+ /** Number of tracked keys currently held in the internal map. */
35
+ get size(): number;
21
36
  reset(): void;
22
37
  }
23
38
  //# sourceMappingURL=rate-limiter.d.ts.map
package/dist/index.js CHANGED
@@ -16,7 +16,6 @@ import { Octokit } from "@octokit/rest";
16
16
  import { createAppAuth } from "@octokit/auth-app";
17
17
  import { createServer } from "node:http";
18
18
  import { exec } from "node:child_process";
19
- import.meta.url;
20
19
  //#endregion
21
20
  //#region src/storage/s3.ts
22
21
  /**
@@ -1779,6 +1778,15 @@ var AdminApiClient = class {
1779
1778
  return this.request("GET", `/api/v1/admin/runs/${encodeURIComponent(runId)}/jobs${qs}`);
1780
1779
  }
1781
1780
  /**
1781
+ * Fetch the machine-first, provenance-tagged structured run result: typed
1782
+ * job DAG, per-step exit codes / durations / statuses, derived failure
1783
+ * category. Untrusted fields are envelope-tagged; secret values are never
1784
+ * returned (only secret-output key names).
1785
+ */
1786
+ async getRunStructured(runId) {
1787
+ return this.request("GET", `/api/v1/admin/runs/${encodeURIComponent(runId)}/structured`);
1788
+ }
1789
+ /**
1782
1790
  * Fetch the scrub status of the run's ephemeral key. Never returns the
1783
1791
  * key material itself — only `{ exists, createdAt }`.
1784
1792
  */
@@ -1833,6 +1841,8 @@ var AdminApiClient = class {
1833
1841
  if (opts?.from) params.set("from", opts.from);
1834
1842
  if (opts?.to) params.set("to", opts.to);
1835
1843
  if (opts?.q) params.set("q", opts.q);
1844
+ if (opts?.agentLabel) params.set("agentLabel", opts.agentLabel);
1845
+ if (opts?.agentOnly) params.set("agentOnly", "true");
1836
1846
  if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
1837
1847
  if (opts?.cursor) params.set("cursor", opts.cursor);
1838
1848
  const qs = params.toString();
@@ -6,6 +6,12 @@ export declare function setConfigVersion(value: number): void;
6
6
  export declare function setDeclaredHostsUnreachable(value: number): void;
7
7
  /** Set the current number of stale runs detected. */
8
8
  export declare function setStaleRunsCurrent(value: number): void;
9
+ /** Set the current number of deferred attestations awaiting a later mint. */
10
+ export declare function setPendingAttestations(value: number): void;
11
+ /** Set the age (seconds) of the oldest deferred attestation in the outbox. */
12
+ export declare function setPendingAttestationOldestAgeSeconds(value: number): void;
13
+ /** Set the current number of terminally-rejected deferred attestations. */
14
+ export declare function setRejectedAttestations(value: number): void;
9
15
  interface ScalerUsageRow {
10
16
  scaler: string;
11
17
  /** Backend type for this scaler (rollup dimension). `__global__` for the orchestrator-wide row. */
@@ -27,6 +27,7 @@ import { ExecutionJobStatus, type LabelMatcher, type PeerHeartbeat, type PeerLog
27
27
  import { ScalerManager } from './scaler/index.js';
28
28
  import type { ScalerConfig } from './scaler/index.js';
29
29
  import type { CacheStorage } from './storage/index.js';
30
+ import { type ProvenanceTrustRoot } from './provenance/trust-root.js';
30
31
  import { SourceCache, BuildCoordinator, DepCache, UserCache, DispatchCacheRefTracker, PendingBuildTracker, PendingInitTracker, PendingDynamicTracker } from './cache/index.js';
31
32
  import { CheckRunReporter } from './reporting/check-run-reporter.js';
32
33
  import { StepLogBuffer } from './reporting/step-log-buffer.js';
@@ -70,6 +71,12 @@ export interface OrchestratorSubsystems {
70
71
  scalerManager: ScalerManager | null;
71
72
  scalerConfig: ScalerConfig | null;
72
73
  cacheStorage: CacheStorage | undefined;
74
+ /**
75
+ * Provenance trust root used to verify build-provenance bundles at ingest.
76
+ * The mode-specific hook (server.ts) wires the live issuer onto it from the
77
+ * Platform `auth.success` message via `onProvenanceIssuer`.
78
+ */
79
+ provenanceTrustRoot: ProvenanceTrustRoot;
73
80
  sourceCache: SourceCache | undefined;
74
81
  depCache: DepCache | undefined;
75
82
  userCache: UserCache | undefined;
@@ -315,6 +322,7 @@ export declare function mergeUpstreamOutputs(db: Kysely<Database>, runId: string
315
322
  upstreamJobOutputs: Record<string, Record<string, unknown>> | undefined;
316
323
  upstreamJobStatuses: Record<string, ExecutionJobStatus> | undefined;
317
324
  }>;
325
+ export declare function buildInternalJobConfigForWorkflow(workflow: any, job: any): Record<string, unknown>;
318
326
  export declare function bootstrapOrchestrator(config: AppConfig, hooks: OrchestratorHooks, options?: {
319
327
  otelSdk?: {
320
328
  shutdown(): Promise<void>;
@@ -71,6 +71,8 @@ export interface WorkflowDispatchContext {
71
71
  runId: string;
72
72
  trustResolution: TrustResolution | undefined;
73
73
  lockFileSource: string | undefined;
74
+ /** True when this run executes an uploaded local working tree (CLI remote run). */
75
+ localWorkingTree: boolean;
74
76
  /** True only when invoked from the cross-source dispatch shell. */
75
77
  crossSource: boolean;
76
78
  /** Composite dedup key `${info.deliveryId}:${reg.id}` (cross-source only). */
@@ -136,6 +138,12 @@ export interface DispatchMatchedWorkflowResult {
136
138
  dispatchedJobIds: string[];
137
139
  /** True when the workflow install gate paused the dispatch (held run). */
138
140
  held?: boolean;
141
+ /**
142
+ * User-visible warnings aggregated from dispatched jobs — today, one per job
143
+ * whose bound test-run environment(s) were unavailable and skipped. Surfaced
144
+ * on the accepted trigger response so the CLI can print them.
145
+ */
146
+ envWarnings?: string[];
139
147
  }
140
148
  /** Options controlling a (re-)dispatch of a matched workflow. */
141
149
  export interface DispatchMatchedWorkflowOptions {
@@ -0,0 +1,11 @@
1
+ import { type LockStep, type LockStepEntry } from '@kici-dev/engine';
2
+ /**
3
+ * Flatten a lock job's `steps` into the flat sequential list the orchestrator
4
+ * iterates by `stepIndex`. A `parallel` group's children are inlined in array
5
+ * order and the group wrapper is dropped (it consumes no flat index). This keeps
6
+ * the orchestrator's enumeration aligned with the agent's `extractAndNormalizeSteps`
7
+ * — the flat-stepIndex invariant: `flattenLockSteps(job.steps)[i]` is the step at
8
+ * agent `stepIndex i`.
9
+ */
10
+ export declare function flattenLockSteps(steps: readonly LockStepEntry[]): readonly LockStep[];
11
+ //# sourceMappingURL=flatten-lock-steps.d.ts.map
@@ -36,7 +36,8 @@ export declare function evaluateInlineRecord(expression: string, event: object):
36
36
  * failures are immediate dispatch failures (no init-job fallback).
37
37
  */
38
38
  export declare function evaluateInlineFields(lockJob: LockJob, event: object): {
39
- inlineEnvironmentName: string | undefined;
39
+ /** Resolved name per `environments` element, aligned by index; undefined for static or impure-dynamic elements. */
40
+ inlineEnvironmentNames: Array<string | undefined>;
40
41
  inlineEnv: Record<string, string> | undefined;
41
42
  inlineConcurrencyGroup: string | undefined;
42
43
  };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Multi-environment resolution helpers for the dispatch path.
3
+ *
4
+ * A job binds an ordered list of environments (`LockJob.environments`). This
5
+ * module resolves that list into concrete environment names (static values
6
+ * verbatim, pure-inline dynamic elements evaluated against the event) and folds
7
+ * the per-environment secrets/variables last-wins. It keeps the heavy fold logic
8
+ * out of `dispatchMatchedWorkflow`, which must stay under the function-length cap.
9
+ */
10
+ import { type Environment, type HostFacts, type LockJob } from '@kici-dev/engine';
11
+ import type { SecretResolverApi } from '../secrets/secret-resolver.js';
12
+ import type { VariableStore } from '../environments/variable-store.js';
13
+ /**
14
+ * Placeholder written into the persisted bound-env list for an impure dynamic
15
+ * element the orchestrator cannot resolve at dispatch. The agent's init eval
16
+ * later overwrites the list with the resolved name.
17
+ */
18
+ export declare const DYNAMIC_ENV_PLACEHOLDER = "(dynamic)";
19
+ /** Ordered resolved environment names plus whether any element still needs agent init. */
20
+ export interface ResolvedJobEnvironments {
21
+ /** Resolved static + pure-inline names, in order. */
22
+ names: string[];
23
+ /** True when an impure dynamic element must be resolved by an agent init job. */
24
+ needsInit: boolean;
25
+ }
26
+ /**
27
+ * Resolve the ordered bound-environment names from a lock job. Static elements
28
+ * use their value verbatim; pure-inline dynamic elements use the matching
29
+ * pre-evaluated inline name (aligned by index); an impure dynamic element cannot
30
+ * be resolved here and flags `needsInit`.
31
+ */
32
+ export declare function resolveJobEnvironmentNames(lockJob: LockJob, inlineNames: ReadonlyArray<string | undefined>): ResolvedJobEnvironments;
33
+ /**
34
+ * Build the ordered bound-environment display list for persistence at dispatch.
35
+ * Unlike {@link resolveJobEnvironmentNames}, this never drops an unresolved
36
+ * element: a static element uses its value, a pure-inline element uses its
37
+ * resolved name when known, and any element the orchestrator cannot resolve at
38
+ * dispatch (impure dynamic, or an unresolved pure-inline) becomes the
39
+ * `(dynamic)` placeholder — so the persisted column reflects every declared
40
+ * slot in order. Returns an empty array when the job binds no environment.
41
+ */
42
+ export declare function buildJobEnvironmentDisplayNames(lockJob: LockJob, inlineNames: ReadonlyArray<string | undefined>): string[];
43
+ /** Merged secrets/variables across an ordered list of resolved environments. */
44
+ export interface MultiEnvMergedData {
45
+ environmentVars?: Record<string, string>;
46
+ jobSecrets?: Record<string, string>;
47
+ jobNamespacedSecrets?: Record<string, Record<string, string>>;
48
+ }
49
+ /**
50
+ * Resolve and fold variables + secrets across the ordered list of matched
51
+ * environments, last-wins. Each environment is resolved with the existing
52
+ * single-env logic (longest-scope-path-wins preserved within each environment),
53
+ * then folded in array order so a later environment overrides an earlier key.
54
+ * Secrets are also returned namespaced per environment so qualified
55
+ * `<env>:<secret>` references still resolve. `entries` carries the matched
56
+ * `Environment` for each name (in order); variables resolve by environment id.
57
+ */
58
+ export declare function resolveMultiEnvMergedData(args: {
59
+ deps: {
60
+ variableStore?: VariableStore;
61
+ secretResolver?: SecretResolverApi;
62
+ };
63
+ orgId: string;
64
+ entries: ReadonlyArray<{
65
+ name: string;
66
+ env: Environment;
67
+ }>;
68
+ hostCtx?: HostFacts;
69
+ routingKey?: string;
70
+ }): Promise<MultiEnvMergedData>;
71
+ //# sourceMappingURL=job-environments.d.ts.map