@kici-dev/orchestrator 0.3.0 → 0.5.0

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 (68) hide show
  1. package/dist/__test-helpers__/mock-db.d.ts +4 -0
  2. package/dist/agent/agent-version.d.ts +34 -0
  3. package/dist/agent/dispatcher.d.ts +70 -0
  4. package/dist/app.d.ts +23 -1
  5. package/dist/cache/global-eval-round-cache.d.ts +88 -0
  6. package/dist/cache/index.d.ts +3 -0
  7. package/dist/cache/pending-global-evals.d.ts +42 -0
  8. package/dist/cache/pending-inits.d.ts +10 -0
  9. package/dist/cli/commands/cluster-settings.d.ts +41 -3
  10. package/dist/cli/commands/runs.d.ts +1 -0
  11. package/dist/cli.js +1252 -623
  12. package/dist/cluster/cluster-settings-reader.d.ts +53 -1
  13. package/dist/config.d.ts +27 -0
  14. package/dist/content-requirements-cache.d.ts +55 -0
  15. package/dist/db/migrations/109_cluster_settings_cache_knobs.d.ts +4 -0
  16. package/dist/db/migrations/110_cluster_settings_global_eval_knobs.d.ts +4 -0
  17. package/dist/db/migrations/111_cluster_settings_global_eval_wait.d.ts +4 -0
  18. package/dist/db/migrations/112_execution_runs_workflow_repo.d.ts +4 -0
  19. package/dist/db/migrations/113_execution_runs_workflow_repo_index.d.ts +30 -0
  20. package/dist/db/migrations/114_ingest_queue_claim.d.ts +4 -0
  21. package/dist/db/migrations/115_global_workflows_cluster_switch.d.ts +9 -0
  22. package/dist/db/types.d.ts +60 -2
  23. package/dist/metrics/agent-metrics-aggregator.d.ts +2 -2
  24. package/dist/metrics/prometheus.d.ts +59 -0
  25. package/dist/orchestrator-core.d.ts +12 -1
  26. package/dist/pipeline/content-filter.d.ts +71 -0
  27. package/dist/pipeline/dispatch-matched-workflow.d.ts +247 -8
  28. package/dist/pipeline/global-eval-round.d.ts +293 -0
  29. package/dist/pipeline/job-contexts.d.ts +16 -17
  30. package/dist/pipeline/process-webhook.d.ts +7 -0
  31. package/dist/pipeline/processor.d.ts +56 -2
  32. package/dist/pipeline/route-or-dispatch-jobs.d.ts +6 -0
  33. package/dist/pipeline/test-pipeline.d.ts +12 -0
  34. package/dist/pipeline/webhook-payload-store.d.ts +20 -0
  35. package/dist/provenance/backfill-run.d.ts +10 -1
  36. package/dist/provider-registry.d.ts +38 -3
  37. package/dist/providers/github/check-status-poster.d.ts +22 -3
  38. package/dist/providers/github/commit-message.d.ts +20 -0
  39. package/dist/providers/github/file-contents.d.ts +40 -0
  40. package/dist/providers/github/index.d.ts +2 -0
  41. package/dist/providers/universal-git/config.d.ts +2 -0
  42. package/dist/providers/universal-git/normalizer.d.ts +10 -0
  43. package/dist/queue/cleanup.d.ts +7 -1
  44. package/dist/queue/job-queue.d.ts +69 -6
  45. package/dist/queue/terminalize-unroutable.d.ts +13 -0
  46. package/dist/registration/registration-run-match.d.ts +47 -0
  47. package/dist/reporting/check-run-reporter.d.ts +52 -1
  48. package/dist/reporting/execution-tracker.d.ts +117 -7
  49. package/dist/reporting/log-chunk-sink.d.ts +8 -5
  50. package/dist/routes/admin-org-settings.d.ts +5 -0
  51. package/dist/routes/admin.d.ts +6 -0
  52. package/dist/scaler/manager.d.ts +10 -8
  53. package/dist/secrets/pg-secret-store.d.ts +20 -2
  54. package/dist/security/global-workflow-policy.d.ts +52 -12
  55. package/dist/server.js +27579 -23783
  56. package/dist/standalone.js +5865 -2211
  57. package/dist/webhook/ingest-accept.d.ts +70 -0
  58. package/dist/webhook/ingest-overflow-buffer.d.ts +35 -4
  59. package/dist/webhook/ingest-overflow-replayer.d.ts +50 -6
  60. package/dist/ws/agent-handler.d.ts +3 -0
  61. package/dist/ws/dashboard-global-workflows-handler.d.ts +30 -9
  62. package/dist/ws/execution-status-frame.d.ts +32 -0
  63. package/dist/ws/platform-client.d.ts +14 -0
  64. package/dist/ws/test-relay-handlers.d.ts +35 -10
  65. package/installer-image-digests.json +3 -3
  66. package/package.json +4 -4
  67. package/sbom.spdx.json +57 -52
  68. package/dist/pipeline/inline-eval.d.ts +0 -44
@@ -1,9 +1,46 @@
1
1
  import type { Kysely } from 'kysely';
2
2
  import type { Database } from '../db/types.js';
3
3
  /** Numeric columns on cluster_settings readable via {@link ClusterSettingsReader}. */
4
- export type ClusterNumberColumn = 'max_github_payload_bytes' | 'event_log_max_payload_bytes' | 'lock_file_max_bytes' | 'webhook_dedup_ttl_ms' | 'contributor_cache_ttl_ms' | 'event_router_event_ttl_seconds' | 'event_router_max_dispatch_attempts' | 'queue_max_depth' | 'reroute_flap_grace_ms' | 'max_fanout_hosts' | 'event_router_rate_limit_per_workflow_per_minute' | 'cache_max_tarball_bytes' | 'cache_ttl_days' | 'check_run_tracking_ttl_days' | 'concurrency_wait_timeout_ms' | 'agent_token_ttl_ms' | 'ownership_db_check_timeout_ms' | 'unroutable_grace_ms';
4
+ export type ClusterNumberColumn = 'max_github_payload_bytes' | 'event_log_max_payload_bytes' | 'lock_file_max_bytes' | 'webhook_dedup_ttl_ms' | 'contributor_cache_ttl_ms' | 'event_router_event_ttl_seconds' | 'event_router_max_dispatch_attempts' | 'queue_max_depth' | 'reroute_flap_grace_ms' | 'max_fanout_hosts' | 'event_router_rate_limit_per_workflow_per_minute' | 'cache_max_tarball_bytes' | 'cache_ttl_days' | 'lockfile_cache_max' | 'lockfile_cache_max_bytes' | 'lockfile_cache_ttl_ms' | 'content_cache_max' | 'content_cache_max_bytes' | 'content_cache_ttl_ms' | 'global_eval_round_timeout_ms' | 'global_eval_candidate_timeout_ms' | 'global_eval_cache_max' | 'global_eval_wait_timeout_ms' | 'check_run_tracking_ttl_days' | 'concurrency_wait_timeout_ms' | 'agent_token_ttl_ms' | 'ownership_db_check_timeout_ms' | 'unroutable_grace_ms' | 'ingest_overflow_claim_timeout_ms';
5
5
  /** Text columns on cluster_settings readable via {@link ClusterSettingsReader}. */
6
6
  export type ClusterStringColumn = 'dashboard_verified_issuer';
7
+ /** Boolean columns on cluster_settings readable via {@link ClusterSettingsReader}. */
8
+ export type ClusterBooleanColumn = 'global_workflows_enabled';
9
+ /**
10
+ * Ceiling for the three LRU entry-count knobs — `lockfile_cache_max`,
11
+ * `content_cache_max`, and `global_eval_cache_max`.
12
+ *
13
+ * This is a boot-safety bound, not a policy preference. The underlying LRU
14
+ * allocates its index arrays eagerly from `max` — several typed arrays plus the
15
+ * TTL and size arrays the caches ask for — so the cost is paid at construction
16
+ * with zero entries cached. Measured on the shipped version: an empty cache at
17
+ * `max` 5,000,000 costs ~191 MB, and at 5,000,000,000 the constructor throws
18
+ * `RangeError: Invalid array length`.
19
+ *
20
+ * That throw is what makes an unbounded knob dangerous rather than merely
21
+ * wasteful. All three caches are built inside `bootstrapOrchestrator`, so a bad
22
+ * stored value crashes the orchestrator before its admin API is listening —
23
+ * and the admin API is the only way `kici-admin cluster-settings` can reach the
24
+ * stored value. The knob would brick the very tool needed to un-brick it.
25
+ *
26
+ * 100,000 is 200x the shipped default of 500 and costs ~4 MB per empty cache,
27
+ * so it is far above any plausible operator setting while keeping boot bounded.
28
+ */
29
+ export declare const CACHE_MAX_ENTRIES_CEILING = 100000;
30
+ /**
31
+ * Bound a stored entry-count knob to something the LRU constructor survives.
32
+ *
33
+ * Applied at the read site rather than only at the write site, because the
34
+ * write-side validation cannot reach a value that is already in the database —
35
+ * set before the ceiling shipped, or written by any path other than the admin
36
+ * route. Clamping here is what actually guarantees a stored value cannot
37
+ * prevent boot.
38
+ *
39
+ * A value that is not a usable positive count (NaN, non-finite, below 1) falls
40
+ * back to the configured default rather than clamping to 1: a 1-entry cache
41
+ * thrashes silently, which is harder to diagnose than simply ignoring garbage.
42
+ */
43
+ export declare function clampCacheMaxEntries(value: number, fallback: number): number;
7
44
  /**
8
45
  * A knob read that keeps "the operator never set this" separate from "we could
9
46
  * not read it".
@@ -64,6 +101,21 @@ export declare class ClusterSettingsReader {
64
101
  * An empty stored string is unset, matching {@link getString}.
65
102
  */
66
103
  tryGetString(column: ClusterStringColumn): Promise<ClusterSettingRead<string>>;
104
+ /**
105
+ * Resolve a fleet-wide boolean knob without collapsing a read failure into a
106
+ * default.
107
+ *
108
+ * There is deliberately no `getBoolean(column, fallback)` companion. The
109
+ * security gate that consumes this must tell the reader's outcomes apart
110
+ * itself: a stored `false` and a NULL both deny today, but they deny for
111
+ * different reasons, and `{ ok: false }` must deny regardless of what the
112
+ * configured default happens to be. A convenience wrapper that folded the
113
+ * failure into the default would be safe only for as long as that default
114
+ * stayed `false`. A caller that only needs a display value (the dashboard
115
+ * status badge) folds `{ ok: false }` into the default explicitly at its own
116
+ * site, where getting it wrong misreports a badge rather than opening a gate.
117
+ */
118
+ tryGetBoolean(column: ClusterBooleanColumn): Promise<ClusterSettingRead<boolean>>;
67
119
  /**
68
120
  * The cluster_settings row version last seen in the cache (0 if never loaded).
69
121
  *
package/dist/config.d.ts CHANGED
@@ -56,9 +56,17 @@ declare const configSchema: z.ZodObject<{
56
56
  lockfileCacheMax: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
57
57
  lockfileCacheTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
58
58
  lockfileCacheMaxBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
59
+ contentCacheMax: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
60
+ contentCacheTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
61
+ contentCacheMaxBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
62
+ globalEvalRoundTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
63
+ globalEvalCandidateTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
64
+ globalEvalCacheMax: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
65
+ globalEvalWaitTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
59
66
  queueMaxDepth: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
60
67
  queueTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
61
68
  unroutableGraceMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
69
+ globalWorkflowsEnabled: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>, z.ZodTransform<boolean, string | boolean>>;
62
70
  queueBackpressureThreshold: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
63
71
  workerConcurrency: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
64
72
  concurrencyWaitTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -81,6 +89,7 @@ declare const configSchema: z.ZodObject<{
81
89
  ingestOverflowReplayIntervalMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
82
90
  ingestOverflowReplayBatch: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
83
91
  ingestOverflowMaxAttempts: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
92
+ ingestOverflowClaimTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
84
93
  rerouteSpawnWindowMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
85
94
  rerouteAckTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
86
95
  rerouteMaxHops: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -250,9 +259,17 @@ export declare const packagingConfigSchema: z.ZodObject<{
250
259
  lockfileCacheMax: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
251
260
  lockfileCacheTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
252
261
  lockfileCacheMaxBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
262
+ contentCacheMax: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
263
+ contentCacheTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
264
+ contentCacheMaxBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
265
+ globalEvalRoundTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
266
+ globalEvalCandidateTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
267
+ globalEvalCacheMax: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
268
+ globalEvalWaitTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
253
269
  queueMaxDepth: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
254
270
  queueTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
255
271
  unroutableGraceMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
272
+ globalWorkflowsEnabled: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>, z.ZodTransform<boolean, string | boolean>>;
256
273
  queueBackpressureThreshold: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
257
274
  workerConcurrency: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
258
275
  concurrencyWaitTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -275,6 +292,7 @@ export declare const packagingConfigSchema: z.ZodObject<{
275
292
  ingestOverflowReplayIntervalMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
276
293
  ingestOverflowReplayBatch: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
277
294
  ingestOverflowMaxAttempts: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
295
+ ingestOverflowClaimTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
278
296
  rerouteSpawnWindowMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
279
297
  rerouteAckTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
280
298
  rerouteMaxHops: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -477,9 +495,17 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
477
495
  lockfileCacheMax: number;
478
496
  lockfileCacheTtlMs: number;
479
497
  lockfileCacheMaxBytes: number;
498
+ contentCacheMax: number;
499
+ contentCacheTtlMs: number;
500
+ contentCacheMaxBytes: number;
501
+ globalEvalRoundTimeoutMs: number;
502
+ globalEvalCandidateTimeoutMs: number;
503
+ globalEvalCacheMax: number;
504
+ globalEvalWaitTimeoutMs: number;
480
505
  queueMaxDepth: number;
481
506
  queueTimeoutMs: number;
482
507
  unroutableGraceMs: number;
508
+ globalWorkflowsEnabled: boolean;
483
509
  queueBackpressureThreshold: number;
484
510
  workerConcurrency: number;
485
511
  concurrencyWaitTimeoutMs: number;
@@ -499,6 +525,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
499
525
  ingestOverflowReplayIntervalMs: number;
500
526
  ingestOverflowReplayBatch: number;
501
527
  ingestOverflowMaxAttempts: number;
528
+ ingestOverflowClaimTimeoutMs: number;
502
529
  rerouteSpawnWindowMs: number;
503
530
  rerouteAckTimeoutMs: number;
504
531
  rerouteMaxHops: number;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Provider-agnostic LRU cache for source-file contents fetched at a ref.
3
+ *
4
+ * Wraps any {@link FileContentsFetcher} with an in-memory LRU cache keyed by
5
+ * `(provider, repo, sha, path)`. The key is content-addressable by
6
+ * construction: a commit SHA pins immutable bytes, so a cached entry for a
7
+ * `(repo, sha, path)` triple can never go stale within the SHA. When the caller
8
+ * passes a mutable ref (a branch name) instead of a SHA, staleness is bounded by
9
+ * the TTL, exactly as the lock-file cache handles the same case.
10
+ *
11
+ * Mirrors {@link ../lockfile-cache.ts LockFileCache}: same LRU + in-flight
12
+ * coalescing shape so N concurrent misses for one key run a single fetch.
13
+ */
14
+ import type { FileContentsFetcher } from '@kici-dev/engine';
15
+ /**
16
+ * One fetched file's presence + bytes. `present: true` with `bytes: undefined`
17
+ * is the size-limit case (the file exists but the provider returned no inline
18
+ * content, e.g. a GitHub file over 1 MiB) — callers MUST treat it as
19
+ * "content unavailable for matching", never as an absent or empty file.
20
+ */
21
+ export interface FileContentEntry {
22
+ readonly present: boolean;
23
+ readonly bytes?: string;
24
+ }
25
+ export declare class ContentRequirementsCache {
26
+ private readonly cache;
27
+ private readonly inFlight;
28
+ private hits;
29
+ private misses;
30
+ constructor(options: {
31
+ max: number;
32
+ ttl: number;
33
+ maxBytes?: number;
34
+ });
35
+ /**
36
+ * Fetch a file's contents at a ref, using the provided fetcher and LRU
37
+ * caching. Each distinct `(provider, repo, sha, path)` is fetched at most
38
+ * once; concurrent misses coalesce onto one fetch.
39
+ *
40
+ * @param fetcher - Provider-specific file-contents fetcher.
41
+ * @param repo - Provider repo identifier ("owner/repo").
42
+ * @param sha - Git ref (commit SHA, or a branch/tag name).
43
+ * @param path - Repo-relative file path.
44
+ */
45
+ get(fetcher: FileContentsFetcher, repo: string, sha: string, path: string): Promise<FileContentEntry>;
46
+ private fetchAndCache;
47
+ /** Cache statistics for metrics/monitoring. */
48
+ getStats(): {
49
+ hits: number;
50
+ misses: number;
51
+ size: number;
52
+ calculatedSize: number;
53
+ };
54
+ }
55
+ //# sourceMappingURL=content-requirements-cache.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=109_cluster_settings_cache_knobs.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=110_cluster_settings_global_eval_knobs.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=111_cluster_settings_global_eval_wait.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=112_execution_runs_workflow_repo.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add the partial index `idx_execution_runs_workflow_repo` on
4
+ * `execution_runs (workflow_repo_identifier) WHERE workflow_repo_identifier IS NOT NULL`.
5
+ *
6
+ * Matching a workflow registration to its runs keys on the repository that
7
+ * DEFINES the workflow — `workflow_repo_identifier ?? repo_identifier` — which
8
+ * the orchestrator expresses as
9
+ * `workflow_repo_identifier IN (…) OR (workflow_repo_identifier IS NULL AND repo_identifier IN (…))`
10
+ * (`registration/registration-run-match.ts`). The second arm rides
11
+ * `idx_execution_runs_repo` from migration 001; the first arm had no index at
12
+ * all, because migration 112 added the column without one. A planner cannot
13
+ * use an index for only half a disjunction, so the whole predicate degraded to
14
+ * a sequential scan over a table that grows with every run — on
15
+ * `dashboard.registrations.list`, which runs its grouped MAX on every list
16
+ * request.
17
+ *
18
+ * With both arms indexed the planner can BitmapOr them.
19
+ *
20
+ * Partial by design, and the partiality is what makes it cheap: the column is
21
+ * non-null only for a cross-repository organization-wide run, a small minority
22
+ * of rows, so the index stays a fraction of the table's size and skips every
23
+ * per-repository run. It also matches the predicate exactly — the first arm
24
+ * can never match a NULL row, since `NULL IN (…)` is never true.
25
+ *
26
+ * Idempotent: `IF NOT EXISTS` makes a re-run a no-op.
27
+ */
28
+ export declare function up(db: Kysely<unknown>): Promise<void>;
29
+ export declare function down(db: Kysely<unknown>): Promise<void>;
30
+ //# sourceMappingURL=113_execution_runs_workflow_repo_index.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=114_ingest_queue_claim.d.ts.map
@@ -0,0 +1,9 @@
1
+ import { type Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ /**
4
+ * Restores the org column with its original NOT NULL DEFAULT false. A rollback
5
+ * cannot recover the per-org values the up-migration dropped — they are gone —
6
+ * so every org comes back at the opt-in default.
7
+ */
8
+ export declare function down(db: Kysely<unknown>): Promise<void>;
9
+ //# sourceMappingURL=115_global_workflows_cluster_switch.d.ts.map
@@ -352,6 +352,12 @@ export interface IngestOverflowBufferTable {
352
352
  status: Generated<string>;
353
353
  /** Last replay error, or null. */
354
354
  last_error: string | null;
355
+ /**
356
+ * When the row moved `buffered` → `replaying`; null while it is buffered.
357
+ * Separate from `captured_at` (which orders the FIFO drain) so a long-buffered
358
+ * row is not reclaimed the instant a worker picks it up.
359
+ */
360
+ claimed_at: ColumnType<Date | null, Date | null | undefined, Date | null>;
355
361
  }
356
362
  export type DispatchQueueItem = Selectable<DispatchQueueTable>;
357
363
  export type NewDispatchQueueItem = Insertable<DispatchQueueTable>;
@@ -471,6 +477,20 @@ export interface ExecutionRunTable {
471
477
  * when resolving the actor to a KiCI user.
472
478
  */
473
479
  trigger_actor_user_id: string | null;
480
+ /**
481
+ * The repository that DEFINES this run's workflow, when that is not the
482
+ * repository the run acted on.
483
+ *
484
+ * `repo_identifier` is the repository the run acted on and whose code its
485
+ * jobs check out. For an organization-wide workflow dispatched against
486
+ * another repository, the workflow was authored somewhere else, and that
487
+ * authoring repository is recorded here. NULL means the two are the same —
488
+ * true of every per-repository run.
489
+ *
490
+ * Read by the rerun path, which must resolve the workflow from the
491
+ * repository that defines it rather than from the one it ran against.
492
+ */
493
+ workflow_repo_identifier: string | null;
474
494
  /** Human-readable reason why the run failed (null for non-failed runs). */
475
495
  failure_reason: string | null;
476
496
  /**
@@ -1514,8 +1534,6 @@ export interface OrgSettingsRepoPatternEntry {
1514
1534
  export interface OrgSettingsTable {
1515
1535
  /** Customer/org identifier (primary key) */
1516
1536
  customer_id: string;
1517
- /** Whether global workflows are enabled for this org */
1518
- global_workflows_enabled: ColumnType<boolean, boolean | undefined, boolean>;
1519
1537
  /** Repos allowed to author global workflows (null = any repo can author) */
1520
1538
  global_workflow_allowed_repos: ColumnType<OrgSettingsRepoPatternEntry[] | null, OrgSettingsRepoPatternEntry[] | null | undefined | string, OrgSettingsRepoPatternEntry[] | null | string>;
1521
1539
  /** Repos with elevated trust for global workflow execution (null = none) */
@@ -1710,6 +1728,32 @@ export interface ClusterSettingsTable {
1710
1728
  event_router_rate_limit_per_workflow_per_minute: ColumnType<number | null, number | null | undefined, number | null>;
1711
1729
  cache_max_tarball_bytes: ColumnType<string | null, number | null | undefined, number | null>;
1712
1730
  cache_ttl_days: ColumnType<number | null, number | null | undefined, number | null>;
1731
+ /** LRU entry ceiling for the lock-file cache. NULL ⇒ the configured default. Applies at next restart. */
1732
+ lockfile_cache_max: ColumnType<string | null, number | null | undefined, number | null>;
1733
+ /** Byte ceiling for the lock-file cache. NULL ⇒ the configured default. Applies at next restart. */
1734
+ lockfile_cache_max_bytes: ColumnType<string | null, number | null | undefined, number | null>;
1735
+ /** Entry TTL (ms) for the lock-file cache. NULL ⇒ the configured default. Applies at next restart. */
1736
+ lockfile_cache_ttl_ms: ColumnType<string | null, number | null | undefined, number | null>;
1737
+ /** LRU entry ceiling for the Tier-1 content cache. NULL ⇒ the configured default. Applies at next restart. */
1738
+ content_cache_max: ColumnType<string | null, number | null | undefined, number | null>;
1739
+ /** Byte ceiling for the Tier-1 content cache. NULL ⇒ the configured default. Applies at next restart. */
1740
+ content_cache_max_bytes: ColumnType<string | null, number | null | undefined, number | null>;
1741
+ /** Entry TTL (ms) for the Tier-1 content cache. NULL ⇒ the configured default. Applies at next restart. */
1742
+ content_cache_ttl_ms: ColumnType<string | null, number | null | undefined, number | null>;
1743
+ /** Wall-clock budget (ms) for a whole Tier-2 global eval round. NULL ⇒ the configured default. Read per round. */
1744
+ global_eval_round_timeout_ms: ColumnType<string | null, number | null | undefined, number | null>;
1745
+ /** Wall-clock budget (ms) for one candidate inside a round. NULL ⇒ the configured default. Read per round. */
1746
+ global_eval_candidate_timeout_ms: ColumnType<string | null, number | null | undefined, number | null>;
1747
+ /** LRU entry ceiling for the round-result cache. NULL ⇒ the configured default. Applies at next restart. */
1748
+ global_eval_cache_max: ColumnType<string | null, number | null | undefined, number | null>;
1749
+ /**
1750
+ * Orchestrator-side ceiling (ms) on waiting for one eval round to settle.
1751
+ * Distinct from the two budgets above, which the agent enforces on itself: a
1752
+ * round that never reaches an agent — or an agent that wedges before its own
1753
+ * budget starts — is only bounded here. NULL ⇒ the configured default. Read
1754
+ * per round.
1755
+ */
1756
+ global_eval_wait_timeout_ms: ColumnType<string | null, number | null | undefined, number | null>;
1713
1757
  /**
1714
1758
  * Retention window in days for `check_run_tracking` rows. The hourly cleanup
1715
1759
  * sweep deletes rows untouched for longer than this; 0 disables the sweep.
@@ -1722,6 +1766,20 @@ export interface ClusterSettingsTable {
1722
1766
  * 0 disables fast-fail, leaving `queue_timeout_ms` as the only backstop.
1723
1767
  */
1724
1768
  unroutable_grace_ms: ColumnType<number | null, number | null | undefined, number | null>;
1769
+ /**
1770
+ * Fleet-wide master switch for global workflows. NULL ⇒ the orchestrator's
1771
+ * configured default (`KICI_GLOBAL_WORKFLOWS_ENABLED`, default false). Read
1772
+ * per policy decision through `ClusterSettingsReader.tryGetBoolean`, which
1773
+ * reports an unreadable row separately so the gate can fail closed.
1774
+ */
1775
+ global_workflows_enabled: ColumnType<boolean | null, boolean | null | undefined, boolean | null>;
1776
+ /**
1777
+ * How long a `replaying` claim on `ingest_overflow_buffer` may stand before
1778
+ * the drain pass reclaims the row for another worker. NULL ⇒ the
1779
+ * orchestrator's configured default. Must exceed the longest a single
1780
+ * delivery's pipeline can legitimately run.
1781
+ */
1782
+ ingest_overflow_claim_timeout_ms: ColumnType<number | null, number | null | undefined, number | null>;
1725
1783
  concurrency_wait_timeout_ms: ColumnType<string | null, number | null | undefined, number | null>;
1726
1784
  agent_token_ttl_ms: ColumnType<string | null, number | null | undefined, number | null>;
1727
1785
  /**
@@ -47,10 +47,10 @@ export declare class AgentMetricsAggregator {
47
47
  private store;
48
48
  private defaultRetentionMs;
49
49
  /**
50
- * Optional lookup: agentId → scaler backend name (e.g. `container`,
50
+ * Optional lookup: agentId → scaler backend type (e.g. `container`,
51
51
  * `firecracker`, `bare-metal`). Returning null marks the agent as
52
52
  * static / stateful — we then stamp the `scaler` label as `'stateful'`.
53
- * Wired from ScalerManager.getBackendName in production. Tests can
53
+ * Wired from ScalerManager.getBackendType in production. Tests can
54
54
  * leave it undefined; in that case the `scaler` label is omitted.
55
55
  */
56
56
  private readonly getScalerForAgent?;
@@ -93,6 +93,13 @@ export declare const ingestShedTotal: import("@opentelemetry/api").Counter<impor
93
93
  export declare const stateReplayBreakerTripsTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
94
94
  /** Total webhook-ingest admissions granted. */
95
95
  export declare const ingestAdmittedTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
96
+ /**
97
+ * Webhook pipelines that threw. Labels:
98
+ * - phase: post_ack — the delivery was already acknowledged, so the failure
99
+ * could not be reported in the response and is only visible here, in the
100
+ * event log, and in the queue row.
101
+ */
102
+ export declare const webhookPipelineFailuresTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
96
103
  /** Set the current buffered-row depth (called by the buffer + replayer). */
97
104
  export declare function setIngestOverflowBuffered(value: number): void;
98
105
  /** Current buffered-row depth (test helper). */
@@ -291,6 +298,58 @@ export declare const universalGitRegistrationErrorsTotal: import("@opentelemetry
291
298
  * 'id_mismatch' (a username-matched link has a different provider_user_id)
292
299
  */
293
300
  export declare const trustMatchRefusedNoIdTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
301
+ /**
302
+ * Candidates handed to a global eval round.
303
+ *
304
+ * Equal to `sum(kici_orch_global_eval_verdicts_total)` by construction: every
305
+ * candidate receives exactly one verdict, including on the fail-closed path
306
+ * where the round never runs. Kept as its own series because it is the natural
307
+ * top-line rate and the denominator the outcome breakdown divides — a
308
+ * divergence between the two would be a bug in the round, never a condition an
309
+ * operator should watch for.
310
+ */
311
+ export declare const globalEvalCandidatesTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
312
+ /**
313
+ * Per-candidate verdicts a global eval round produced.
314
+ *
315
+ * Labels:
316
+ * - outcome: run (the filter admitted it) | filtered (the filter excluded it) | indeterminate (the round could not decide — a failed round, a budget breach, or a verdict that never arrived)
317
+ */
318
+ export declare const globalEvalVerdictsTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
319
+ /**
320
+ * Wall-clock duration of one dispatched global eval round, from dispatch to
321
+ * settled verdicts. A cache hit records nothing — it never dispatches a round.
322
+ *
323
+ * Buckets: 1s to 10min, covering a fast round through the default 120s agent
324
+ * budget and the 240s orchestrator wait ceiling.
325
+ *
326
+ * Labels:
327
+ * - result: success (the round settled) | error (dispatch rejected, the wait ceiling fired, or the round decided nothing)
328
+ */
329
+ export declare const globalEvalRoundDurationSeconds: import("@opentelemetry/api").Histogram<import("@opentelemetry/api").Attributes>;
330
+ /**
331
+ * Jobs a round's `DynamicJobFn` generators produced, summed over the round's
332
+ * candidates. Recorded once per settled round, including zero.
333
+ *
334
+ * Buckets cover the common small fan-outs through a large matrix expansion.
335
+ */
336
+ export declare const globalEvalJobsGenerated: import("@opentelemetry/api").Histogram<import("@opentelemetry/api").Attributes>;
337
+ /**
338
+ * Global eval round cache lookups by outcome.
339
+ *
340
+ * Three outcomes, not two. An input that cannot be serialized yields no cache
341
+ * key at all, so it never reaches the cache and registers neither a hit nor a
342
+ * miss — counting only hits and misses would drop those lookups from the
343
+ * denominator and make the hit rate read higher than it is.
344
+ *
345
+ * Expect a hit rate near zero on a healthy cluster: the key covers the whole
346
+ * event, so every real push is a miss by construction. This cache is a
347
+ * redelivery guard, not a steady-state optimization.
348
+ *
349
+ * Labels:
350
+ * - result: hit | miss | unkeyable (the round input could not be serialized)
351
+ */
352
+ export declare const globalEvalCacheLookupsTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
294
353
  /**
295
354
  * Internal events delivered to all matching workflows successfully (after the
296
355
  * lease commits processed=true).
@@ -19,6 +19,7 @@ import { EventLogWriter } from './webhook/event-log.js';
19
19
  import { AccessLogWriter } from './audit/access-log.js';
20
20
  import { Dispatcher } from './agent/dispatcher.js';
21
21
  import { LockFileCache } from './lockfile-cache.js';
22
+ import { ContentRequirementsCache } from './content-requirements-cache.js';
22
23
  import { DedupCache } from './webhook/dedup.js';
23
24
  import { ObserverRegistry } from './ws/observer-registry.js';
24
25
  import { IngestAdmissionController } from './webhook/ingest-admission.js';
@@ -34,7 +35,7 @@ import type { ScalerConfig } from './scaler/index.js';
34
35
  import type { CacheStorage } from './storage/index.js';
35
36
  import { type ProvenanceTrustRoot } from './provenance/trust-root.js';
36
37
  import { type ResolvedDashboardEncryptionKey } from './secrets/dashboard-encryption-key.js';
37
- import { SourceCache, BuildCoordinator, DepCache, UserCache, DispatchCacheRefTracker, PendingBuildTracker, PendingInitTracker, PendingDynamicTracker } from './cache/index.js';
38
+ import { SourceCache, BuildCoordinator, DepCache, UserCache, DispatchCacheRefTracker, PendingBuildTracker, PendingInitTracker, PendingDynamicTracker, PendingGlobalEvalTracker, GlobalEvalRoundCache } from './cache/index.js';
38
39
  import { ArtifactStore } from './artifacts/artifact-store.js';
39
40
  import { CheckRunReporter } from './reporting/check-run-reporter.js';
40
41
  import { StepLogBuffer } from './reporting/step-log-buffer.js';
@@ -98,6 +99,7 @@ export interface OrchestratorSubsystems {
98
99
  pendingBuilds: PendingBuildTracker | undefined;
99
100
  pendingInits: PendingInitTracker;
100
101
  pendingDynamics: PendingDynamicTracker;
102
+ pendingGlobalEvals: PendingGlobalEvalTracker;
101
103
  checkRunReporter: CheckRunReporter;
102
104
  stepLogBuffer: StepLogBuffer;
103
105
  sourceLocationStore: SourceLocationStore;
@@ -119,6 +121,8 @@ export interface OrchestratorSubsystems {
119
121
  tokenStore: AgentTokenStore;
120
122
  ownershipTracker: OwnershipTracker;
121
123
  lockFileCache: LockFileCache;
124
+ contentRequirementsCache: ContentRequirementsCache;
125
+ globalEvalCache: GlobalEvalRoundCache;
122
126
  dedup: DedupCache;
123
127
  eventRouter: EventRouter;
124
128
  /**
@@ -129,6 +133,13 @@ export interface OrchestratorSubsystems {
129
133
  eventStore: EventStore;
130
134
  eventEmitter: EventEmitter;
131
135
  genericSourceManager: GenericSourceManager;
136
+ /**
137
+ * Re-register a generic source's provider bundle from its database row.
138
+ * The webhook pipeline calls it on an exact registry miss so the read side
139
+ * settles against server truth rather than a stand-in bundle. Shared by the
140
+ * relay path (server.ts) and the direct-ingress path (app.ts).
141
+ */
142
+ ensureProviderBundle: (routingKey: string) => Promise<boolean>;
132
143
  trustStore: TrustStore;
133
144
  registrationStore: RegistrationStore;
134
145
  registrationIndex: RegistrationIndex;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Tier-1 declarative content-requirements filter (execution-purity model, Part B).
3
+ *
4
+ * After Tier-0 trigger matching selects candidate workflows, this step fetches
5
+ * the source files each candidate's `requires` names — once per distinct
6
+ * `(repo, sha, path)` via the LRU cache — evaluates the (pure DATA) requirements
7
+ * with the engine matcher, and drops any candidate that FAILS or is
8
+ * INDETERMINATE before dispatch. No author code is executed: `requires` is a
9
+ * declarative query the orchestrator interprets, which is what keeps content
10
+ * filtering inside the orchestrator under the purity model.
11
+ *
12
+ * Fail-visible: a dropped candidate (definite negative, oversize/unparseable
13
+ * content, a fetch error, or no fetcher/cache available) is logged with the
14
+ * concrete reason. An indeterminate file NEVER passes silently.
15
+ */
16
+ import type { FileContentsFetcher, LockContentRequirement } from '@kici-dev/engine';
17
+ import type { ContentRequirementsCache } from '../content-requirements-cache.js';
18
+ /** A workflow candidate carrying the `requires` of the trigger that matched the event. */
19
+ export interface ContentFilterCandidate {
20
+ readonly name: string;
21
+ readonly requires?: readonly LockContentRequirement[];
22
+ }
23
+ /** The event the requirements are evaluated against: source repo + ref to fetch files at. */
24
+ export interface ContentFilterEvent {
25
+ /** Provider repo identifier ("owner/repo"). */
26
+ readonly repo: string;
27
+ /** Git ref (commit SHA, or branch/tag) at which files are read. */
28
+ readonly sha: string;
29
+ }
30
+ export interface ContentFilterOptions {
31
+ /** Per-delivery file-contents fetcher; `undefined` when the provider has none. */
32
+ readonly fetcher: FileContentsFetcher | undefined;
33
+ /** Shared LRU cache; `undefined` when the deployment did not wire one. */
34
+ readonly cache: ContentRequirementsCache | undefined;
35
+ /** Delivery id for log correlation. */
36
+ readonly deliveryId?: string;
37
+ }
38
+ /**
39
+ * One dropped candidate.
40
+ *
41
+ * `indeterminate` separates the two reasons a drop happens, which mean opposite
42
+ * things to a workflow author: a definite content mismatch is the requirement
43
+ * doing its job, while an unreadable file, a fetch error, or a provider with no
44
+ * file-contents fetcher is the requirement never having been evaluated at all.
45
+ * Both drop the candidate — the filter is fail-visible, so an unevaluable file
46
+ * never passes — but a trace that renders the second as "excluded" tells an
47
+ * author their requirement said no when nothing ever read it.
48
+ */
49
+ export interface DroppedCandidate {
50
+ readonly name: string;
51
+ readonly reason: string;
52
+ /** True when the requirement could not be evaluated, rather than evaluating to false. */
53
+ readonly indeterminate?: boolean;
54
+ }
55
+ export interface ContentFilterResult<C extends ContentFilterCandidate> {
56
+ readonly survivors: C[];
57
+ readonly dropped: DroppedCandidate[];
58
+ }
59
+ /**
60
+ * Filter candidates by their declarative content requirements.
61
+ *
62
+ * Gathers each distinct `(repo, sha, path)` once via the cache, evaluates every
63
+ * candidate's `requires`, and returns the survivors (pass) split from the
64
+ * dropped (fail / indeterminate, each logged with its reason). A candidate with
65
+ * no `requires` always survives without any fetch (the fast path — like an empty
66
+ * `paths` filter). When a candidate has `requires` but no fetcher/cache is
67
+ * available, it is dropped as indeterminate (fail-visible), never dispatched
68
+ * unfiltered.
69
+ */
70
+ export declare function filterByContentRequirements<C extends ContentFilterCandidate>(candidates: readonly C[], event: ContentFilterEvent, opts: ContentFilterOptions): Promise<ContentFilterResult<C>>;
71
+ //# sourceMappingURL=content-filter.d.ts.map