@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.
- package/dist/__test-helpers__/mock-db.d.ts +4 -0
- package/dist/agent/agent-version.d.ts +34 -0
- package/dist/agent/dispatcher.d.ts +70 -0
- package/dist/app.d.ts +23 -1
- package/dist/cache/global-eval-round-cache.d.ts +88 -0
- package/dist/cache/index.d.ts +3 -0
- package/dist/cache/pending-global-evals.d.ts +42 -0
- package/dist/cache/pending-inits.d.ts +10 -0
- package/dist/cli/commands/cluster-settings.d.ts +41 -3
- package/dist/cli/commands/runs.d.ts +1 -0
- package/dist/cli.js +1252 -623
- package/dist/cluster/cluster-settings-reader.d.ts +53 -1
- package/dist/config.d.ts +27 -0
- package/dist/content-requirements-cache.d.ts +55 -0
- package/dist/db/migrations/109_cluster_settings_cache_knobs.d.ts +4 -0
- package/dist/db/migrations/110_cluster_settings_global_eval_knobs.d.ts +4 -0
- package/dist/db/migrations/111_cluster_settings_global_eval_wait.d.ts +4 -0
- package/dist/db/migrations/112_execution_runs_workflow_repo.d.ts +4 -0
- package/dist/db/migrations/113_execution_runs_workflow_repo_index.d.ts +30 -0
- package/dist/db/migrations/114_ingest_queue_claim.d.ts +4 -0
- package/dist/db/migrations/115_global_workflows_cluster_switch.d.ts +9 -0
- package/dist/db/types.d.ts +60 -2
- package/dist/metrics/agent-metrics-aggregator.d.ts +2 -2
- package/dist/metrics/prometheus.d.ts +59 -0
- package/dist/orchestrator-core.d.ts +12 -1
- package/dist/pipeline/content-filter.d.ts +71 -0
- package/dist/pipeline/dispatch-matched-workflow.d.ts +247 -8
- package/dist/pipeline/global-eval-round.d.ts +293 -0
- package/dist/pipeline/job-contexts.d.ts +16 -17
- package/dist/pipeline/process-webhook.d.ts +7 -0
- package/dist/pipeline/processor.d.ts +56 -2
- package/dist/pipeline/route-or-dispatch-jobs.d.ts +6 -0
- package/dist/pipeline/test-pipeline.d.ts +12 -0
- package/dist/pipeline/webhook-payload-store.d.ts +20 -0
- package/dist/provenance/backfill-run.d.ts +10 -1
- package/dist/provider-registry.d.ts +38 -3
- package/dist/providers/github/check-status-poster.d.ts +22 -3
- package/dist/providers/github/commit-message.d.ts +20 -0
- package/dist/providers/github/file-contents.d.ts +40 -0
- package/dist/providers/github/index.d.ts +2 -0
- package/dist/providers/universal-git/config.d.ts +2 -0
- package/dist/providers/universal-git/normalizer.d.ts +10 -0
- package/dist/queue/cleanup.d.ts +7 -1
- package/dist/queue/job-queue.d.ts +69 -6
- package/dist/queue/terminalize-unroutable.d.ts +13 -0
- package/dist/registration/registration-run-match.d.ts +47 -0
- package/dist/reporting/check-run-reporter.d.ts +52 -1
- package/dist/reporting/execution-tracker.d.ts +117 -7
- package/dist/reporting/log-chunk-sink.d.ts +8 -5
- package/dist/routes/admin-org-settings.d.ts +5 -0
- package/dist/routes/admin.d.ts +6 -0
- package/dist/scaler/manager.d.ts +10 -8
- package/dist/secrets/pg-secret-store.d.ts +20 -2
- package/dist/security/global-workflow-policy.d.ts +52 -12
- package/dist/server.js +27579 -23783
- package/dist/standalone.js +5865 -2211
- package/dist/webhook/ingest-accept.d.ts +70 -0
- package/dist/webhook/ingest-overflow-buffer.d.ts +35 -4
- package/dist/webhook/ingest-overflow-replayer.d.ts +50 -6
- package/dist/ws/agent-handler.d.ts +3 -0
- package/dist/ws/dashboard-global-workflows-handler.d.ts +30 -9
- package/dist/ws/execution-status-frame.d.ts +32 -0
- package/dist/ws/platform-client.d.ts +14 -0
- package/dist/ws/test-relay-handlers.d.ts +35 -10
- package/installer-image-digests.json +3 -3
- package/package.json +4 -4
- package/sbom.spdx.json +57 -52
- package/dist/pipeline/inline-eval.d.ts +0 -44
|
@@ -72,6 +72,10 @@ export interface MockDbMocks {
|
|
|
72
72
|
insertExecute: ReturnType<typeof vi.fn>;
|
|
73
73
|
insertExecuteTakeFirstOrThrow: ReturnType<typeof vi.fn>;
|
|
74
74
|
onConflict: ReturnType<typeof vi.fn>;
|
|
75
|
+
/** The upsert's ON CONFLICT DO UPDATE SET payload — shared across both the
|
|
76
|
+
* column() and columns() shapes, so a test can assert what an existing row
|
|
77
|
+
* is actually updated with. */
|
|
78
|
+
doUpdateSet: ReturnType<typeof vi.fn>;
|
|
75
79
|
updateTable: ReturnType<typeof vi.fn>;
|
|
76
80
|
updateSet: ReturnType<typeof vi.fn>;
|
|
77
81
|
updateWhere: ReturnType<typeof vi.fn>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparing a self-reported agent version against a feature's minimum.
|
|
3
|
+
*
|
|
4
|
+
* An agent reports its own package version in `agent.register`
|
|
5
|
+
* (`agentRegisterSchema.version`), and the field is optional — an agent old
|
|
6
|
+
* enough to omit it predates every feature this module gates on. The
|
|
7
|
+
* orchestrator and the agent are deployed and upgraded independently
|
|
8
|
+
* (`.claude/rules/compatibility.md`), so a feature the orchestrator ships is
|
|
9
|
+
* routinely dispatched to a fleet that cannot run it, and the version is the
|
|
10
|
+
* only fact the orchestrator has about what the fleet understands.
|
|
11
|
+
*/
|
|
12
|
+
/** A version's release triple, with any prerelease suffix dropped. */
|
|
13
|
+
type VersionBase = [number, number, number];
|
|
14
|
+
/**
|
|
15
|
+
* Parse `MAJOR.MINOR.PATCH`, ignoring any `-prerelease` suffix, or `null` when
|
|
16
|
+
* the string is not a version at all.
|
|
17
|
+
*
|
|
18
|
+
* The prerelease suffix is dropped rather than ordered because the suffixes in
|
|
19
|
+
* play are build counters from the dev registry (`0.5.0-9159`), not semver
|
|
20
|
+
* release candidates. Strict semver orders those BELOW `0.5.0`, which would
|
|
21
|
+
* read every staging agent as too old for a feature it in fact carries.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseVersionBase(version: string): VersionBase | null;
|
|
24
|
+
/**
|
|
25
|
+
* True when `version` is at least `minimum`, comparing release bases only.
|
|
26
|
+
*
|
|
27
|
+
* An absent or unparseable version returns `false`: the orchestrator learns
|
|
28
|
+
* nothing from it, and a feature that needs a specific agent build must not
|
|
29
|
+
* treat "we cannot tell" as "yes". Callers decide what an all-unknown fleet
|
|
30
|
+
* means for them — this function only reports what the version proves.
|
|
31
|
+
*/
|
|
32
|
+
export declare function agentVersionAtLeast(version: string | null | undefined, minimum: string): boolean;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=agent-version.d.ts.map
|
|
@@ -2,6 +2,26 @@ import type { AgentRegistry } from './registry.js';
|
|
|
2
2
|
import { type JobQueue, type QueuedJob, type QueuedJobInput } from '../queue/job-queue.js';
|
|
3
3
|
import type { ScaleResult, ScalerRedispatchTrigger } from '../scaler/types.js';
|
|
4
4
|
import type { ResourceRequest } from '@kici-dev/engine';
|
|
5
|
+
/**
|
|
6
|
+
* Why a freed agent's queue drain claimed nothing.
|
|
7
|
+
*
|
|
8
|
+
* Named rather than inlined because the value travels: it is the `reason`
|
|
9
|
+
* field of the drain-declined log line and the assertion subject of the tests
|
|
10
|
+
* that pin each exit. The five members are the five ways
|
|
11
|
+
* `Dispatcher.drainForAgent` can return without a dispatch.
|
|
12
|
+
*/
|
|
13
|
+
export declare enum AgentDrainDecline {
|
|
14
|
+
/** The agent id is not in the in-memory registry (already disconnected). */
|
|
15
|
+
NotRegistered = "agent-not-registered",
|
|
16
|
+
/** This coordinator is draining and must not claim new work. */
|
|
17
|
+
CoordinatorDraining = "coordinator-draining",
|
|
18
|
+
/** The agent is already at `maxConcurrency`. */
|
|
19
|
+
NoCapacity = "no-capacity",
|
|
20
|
+
/** The host is flagged reboot-pending, so anything dispatched would be lost. */
|
|
21
|
+
RebootPending = "reboot-pending",
|
|
22
|
+
/** Nothing in the queue matched this agent's labels or pin. */
|
|
23
|
+
NoMatchingJob = "no-matching-job"
|
|
24
|
+
}
|
|
5
25
|
/**
|
|
6
26
|
* Default per-pass cap on how many pending jobs a capacity-freed re-drive
|
|
7
27
|
* re-offers to the scaler. Bounds the burst so a single free event cannot storm
|
|
@@ -85,6 +105,12 @@ export declare class Dispatcher {
|
|
|
85
105
|
* the same tick.
|
|
86
106
|
*/
|
|
87
107
|
private redriveInFlight;
|
|
108
|
+
/**
|
|
109
|
+
* Single-flight guard for `redrivePendingToConnectedAgents`. The per-coord
|
|
110
|
+
* safety-net tick must not overlap itself, so a slow re-drive can't be
|
|
111
|
+
* re-entered by the next interval fire and double-scan the same pending rows.
|
|
112
|
+
*/
|
|
113
|
+
private pendingRedriveInFlight;
|
|
88
114
|
/**
|
|
89
115
|
* Tracks which jobs are dispatched to which agents.
|
|
90
116
|
* Used for marking jobs as failed on agent disconnect.
|
|
@@ -268,6 +294,12 @@ export declare class Dispatcher {
|
|
|
268
294
|
*/
|
|
269
295
|
releaseRebootPending(agentId: string): Promise<void>;
|
|
270
296
|
onAgentAvailable(agentId: string): Promise<void>;
|
|
297
|
+
/**
|
|
298
|
+
* Claim at most one queued job for a freed agent.
|
|
299
|
+
*
|
|
300
|
+
* Returns the reason nothing was dispatched, or null when a job was.
|
|
301
|
+
*/
|
|
302
|
+
private drainForAgent;
|
|
271
303
|
/** Record that a job began executing on its agent. */
|
|
272
304
|
markJobStarted(jobId: string): void;
|
|
273
305
|
/**
|
|
@@ -316,6 +348,44 @@ export declare class Dispatcher {
|
|
|
316
348
|
* fresh agent is spawned bound to this job.
|
|
317
349
|
*/
|
|
318
350
|
private redispatch;
|
|
351
|
+
/**
|
|
352
|
+
* Safety-net re-drive: deliver every pending job that a currently-connected,
|
|
353
|
+
* idle, matching agent could take, through the same atomic claim the drain
|
|
354
|
+
* uses (`dispatchBoundJob` → `dequeueById`).
|
|
355
|
+
*
|
|
356
|
+
* Runs per-coordinator and is NOT leader-gated: each coordinator drains the
|
|
357
|
+
* shared queue onto its OWN connected agents, and the atomic claim guarantees
|
|
358
|
+
* at most one agent (on any coordinator) wins each job, so concurrent ticks
|
|
359
|
+
* cannot double-dispatch.
|
|
360
|
+
*
|
|
361
|
+
* It closes the requeue re-drive gap. A job requeued by `handleAckExpiry` /
|
|
362
|
+
* `onJobRejected` / the leader ack sweep gets exactly one delivery attempt —
|
|
363
|
+
* `redispatch`'s single `findAvailable` + `dispatchBoundJob`. An idle matching
|
|
364
|
+
* agent whose own drain trigger (registration / completion / status) already
|
|
365
|
+
* fired before the requeue has no further trigger, so if that one attempt
|
|
366
|
+
* transiently misses the agent — the agent is momentarily at capacity while an
|
|
367
|
+
* in-flight drain holds its eagerly-claimed slot, or the agent is connected to
|
|
368
|
+
* a different coordinator than the one that ran the expiry — the requeued
|
|
369
|
+
* pending job would otherwise sit undelivered until it expired. This tick
|
|
370
|
+
* re-attempts delivery onto connected agents so the miss recovers on the next
|
|
371
|
+
* sweep.
|
|
372
|
+
*
|
|
373
|
+
* It never consults the scaler (that is `retryPendingScaleRequests`) and never
|
|
374
|
+
* spawns: it only places jobs onto agents already connected here.
|
|
375
|
+
*
|
|
376
|
+
* A failure propagates to the caller rather than being swallowed here — the
|
|
377
|
+
* per-coord interval wrapper is the single error-log site, matching the
|
|
378
|
+
* sibling recovery/ack sweeps that share its cadence. The `finally` only
|
|
379
|
+
* releases the single-flight guard.
|
|
380
|
+
*/
|
|
381
|
+
redrivePendingToConnectedAgents(maxJobs?: number): Promise<number>;
|
|
382
|
+
/**
|
|
383
|
+
* Pick a connected agent that may take a pending job, or null. A pinned
|
|
384
|
+
* host-fanout child may run ONLY on its pinned agent — and `dequeueById`
|
|
385
|
+
* ignores the pin — so a pinned job is routed to its own agent and never
|
|
386
|
+
* offered to `findAvailable`, which would mis-deliver it to any label match.
|
|
387
|
+
*/
|
|
388
|
+
private selectConnectedTargetForPending;
|
|
319
389
|
/**
|
|
320
390
|
* Handle agent disconnect. Starts per-job recovery timers instead of
|
|
321
391
|
* immediately failing jobs, giving the agent a grace period to reconnect.
|
package/dist/app.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { PeerRegistry } from './cluster/peer-registry.js';
|
|
|
26
26
|
import type { JobQueue } from './queue/job-queue.js';
|
|
27
27
|
import type { DedupCache } from './webhook/dedup.js';
|
|
28
28
|
import type { LockFileCache } from './lockfile-cache.js';
|
|
29
|
+
import type { ContentRequirementsCache } from './content-requirements-cache.js';
|
|
29
30
|
import type { PlatformClient } from './ws/platform-client.js';
|
|
30
31
|
import type { ScalerManager } from './scaler/manager.js';
|
|
31
32
|
import type { ProviderRegistry } from './provider-registry.js';
|
|
@@ -37,7 +38,8 @@ import type { ArtifactStore } from './artifacts/artifact-store.js';
|
|
|
37
38
|
import type { DispatchCacheRefTracker } from './cache/index.js';
|
|
38
39
|
import type { PendingBuildTracker } from './cache/index.js';
|
|
39
40
|
import type { PendingInitTracker } from './cache/index.js';
|
|
40
|
-
import type { PendingDynamicTracker } from './cache/index.js';
|
|
41
|
+
import type { PendingDynamicTracker, PendingGlobalEvalTracker } from './cache/index.js';
|
|
42
|
+
import type { GlobalEvalRoundCache } from './cache/index.js';
|
|
41
43
|
import type { CacheStorage } from './storage/types.js';
|
|
42
44
|
import type { ProvenanceTrustRoot } from './provenance/trust-root.js';
|
|
43
45
|
import type { LocalSigner } from './oidc/local-dev-signer.js';
|
|
@@ -66,6 +68,7 @@ import type { CronScheduler } from './cron/cron-scheduler.js';
|
|
|
66
68
|
import { type ConfigRouteDeps } from './routes/admin-config.js';
|
|
67
69
|
import { WebhookIngestOutcome } from './pipeline/process-webhook.js';
|
|
68
70
|
import type { IngestOverflowBuffer } from './webhook/ingest-overflow-buffer.js';
|
|
71
|
+
import type { IngestOverflowReplayer } from './webhook/ingest-overflow-replayer.js';
|
|
69
72
|
import { type OverflowDelivery } from './webhook/ingest-overflow-types.js';
|
|
70
73
|
import type { IngestAdmissionController } from './webhook/ingest-admission.js';
|
|
71
74
|
import type { OrgIngestCapReader } from './webhook/org-ingest-cap-reader.js';
|
|
@@ -108,6 +111,7 @@ export interface AppDependencies {
|
|
|
108
111
|
jobQueue: JobQueue;
|
|
109
112
|
dedup: DedupCache;
|
|
110
113
|
lockFileCache: LockFileCache;
|
|
114
|
+
contentRequirementsCache: ContentRequirementsCache;
|
|
111
115
|
providerRegistry: ProviderRegistry;
|
|
112
116
|
platformClient?: PlatformClient;
|
|
113
117
|
/**
|
|
@@ -126,6 +130,13 @@ export interface AppDependencies {
|
|
|
126
130
|
* for later replay; absent → capture disabled.
|
|
127
131
|
*/
|
|
128
132
|
ingestOverflowBuffer?: IngestOverflowBuffer;
|
|
133
|
+
/**
|
|
134
|
+
* Drain loop for the durable ingest queue. The accept path borrows its
|
|
135
|
+
* claim-release so a failed pipeline hands the row back for retry through the
|
|
136
|
+
* same attempt ceiling the drain uses; absent → the accept path degrades to
|
|
137
|
+
* inline ingestion.
|
|
138
|
+
*/
|
|
139
|
+
ingestOverflowReplayer?: IngestOverflowReplayer;
|
|
129
140
|
/**
|
|
130
141
|
* Fulfil deferred attestations on demand (mints in this process, which owns
|
|
131
142
|
* the Platform WS). Backs `POST /api/v1/admin/attestations/retry`. Wired only
|
|
@@ -208,6 +219,8 @@ export interface AppDependencies {
|
|
|
208
219
|
pendingInits?: PendingInitTracker;
|
|
209
220
|
/** Pending dynamic tracker for DynamicJobFn evaluation coordination. */
|
|
210
221
|
pendingDynamics?: PendingDynamicTracker;
|
|
222
|
+
pendingGlobalEvals?: PendingGlobalEvalTracker;
|
|
223
|
+
globalEvalCache?: GlobalEvalRoundCache;
|
|
211
224
|
/** Commit status reporter for setting pending/success/failure/error on commits. Optional. */
|
|
212
225
|
checkRunReporter?: CheckRunReporter;
|
|
213
226
|
/** Execution tracker for DB persistence of execution state. Optional — requires database. */
|
|
@@ -250,6 +263,15 @@ export interface AppDependencies {
|
|
|
250
263
|
eventEmitter?: EventEmitter;
|
|
251
264
|
/** Generic webhook source manager. Optional -- if not set, generic webhooks are disabled. */
|
|
252
265
|
genericSourceManager?: GenericSourceManager;
|
|
266
|
+
/**
|
|
267
|
+
* Re-register a generic source's provider bundle from its database row,
|
|
268
|
+
* resolving to true when a per-routing-key bundle is now registered.
|
|
269
|
+
*
|
|
270
|
+
* Built in orchestrator-core so it shares the exact dependency bag the
|
|
271
|
+
* startup enumeration and the LISTEN/NOTIFY drain already register with.
|
|
272
|
+
* Optional -- wirings without one keep the previous behaviour.
|
|
273
|
+
*/
|
|
274
|
+
ensureProviderBundle?: (routingKey: string) => Promise<boolean>;
|
|
253
275
|
/** GitHub direct-ingress route deps (hybrid/independent only). Optional -- if not set, the direct GitHub ingress route is not mounted. */
|
|
254
276
|
githubSourceStore?: SourceStore;
|
|
255
277
|
githubVerifyDeps?: VerifyInboundDeps;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LRU for completed Tier-2 global eval round results.
|
|
3
|
+
*
|
|
4
|
+
* **This cache only helps webhook redelivery and same-input re-deliveries.** Its
|
|
5
|
+
* key pins the source commit and a digest of the whole round input, and every
|
|
6
|
+
* real push carries a new source SHA — so a developer pushing twice gets two
|
|
7
|
+
* rounds, by design. Read it as a duplicate-delivery guard, never as a
|
|
8
|
+
* steady-state optimization: sizing it up buys nothing, and a hit rate near zero
|
|
9
|
+
* on a healthy cluster is the expected shape, not a defect.
|
|
10
|
+
*
|
|
11
|
+
* There is deliberately no TTL. A round verdict is a pure function of its
|
|
12
|
+
* inputs, and {@link globalEvalRoundCacheKey} covers all of them, so an entry
|
|
13
|
+
* cannot go stale within its own key — the same content-addressable argument the
|
|
14
|
+
* lock-file and content caches make for their SHA-keyed entries, without their
|
|
15
|
+
* branch-name escape hatch.
|
|
16
|
+
*/
|
|
17
|
+
import type { GlobalEvalRoundResult } from '@kici-dev/engine';
|
|
18
|
+
/**
|
|
19
|
+
* Key one round over **every input its verdict depends on**.
|
|
20
|
+
*
|
|
21
|
+
* The three SHAs are not enough, and the gap is a wrong-answer bug rather than a
|
|
22
|
+
* missed-hit one. `createFilterContext` and `buildGeneratorContext` both receive
|
|
23
|
+
* the whole event, so a filter can branch on `event.type`, `targetBranch`,
|
|
24
|
+
* `changedFiles`, or anything in the raw payload. A push to `main` at commit X
|
|
25
|
+
* and a pull-request synchronize whose head is commit X share all three SHAs and
|
|
26
|
+
* genuinely deserve different verdicts: their events differ, and their
|
|
27
|
+
* changed-file sets are computed over different ranges. Two branches pointing at
|
|
28
|
+
* one commit collide the same way. `workflowRoutingKey` is in the key for the
|
|
29
|
+
* reason `groupCandidates` keeps it in the group key — it selects the provider
|
|
30
|
+
* bundle that mints the clone credentials.
|
|
31
|
+
*
|
|
32
|
+
* The candidate list and the event are folded in as a SHA-256 digest of their
|
|
33
|
+
* JSON, so the key stays a bounded string no matter how large a payload is. Both
|
|
34
|
+
* are rebuilt by the same code from the same delivery, so a genuine redelivery
|
|
35
|
+
* reproduces the digest; anything else is a miss, which costs a round rather
|
|
36
|
+
* than a wrong verdict.
|
|
37
|
+
*
|
|
38
|
+
* Returns `null` when the inputs cannot be serialized. A round that cannot be
|
|
39
|
+
* keyed is simply not cached — never keyed on a partial input, which is how a
|
|
40
|
+
* cache starts answering questions it was not asked.
|
|
41
|
+
*/
|
|
42
|
+
export declare function globalEvalRoundCacheKey(args: {
|
|
43
|
+
workflowRepoIdentifier: string;
|
|
44
|
+
workflowSha: string;
|
|
45
|
+
workflowRoutingKey: string;
|
|
46
|
+
sourceSha: string;
|
|
47
|
+
/** The exact per-candidate payload the round job carries. */
|
|
48
|
+
candidates: unknown;
|
|
49
|
+
/** The exact event the round job carries. */
|
|
50
|
+
event: unknown;
|
|
51
|
+
}): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* A round that left ANY candidate undecided is not a result worth replaying.
|
|
54
|
+
*
|
|
55
|
+
* An agent-side budget breach reports `success` with the affected candidates
|
|
56
|
+
* marked indeterminate — the whole round on a round-budget breach, one workflow
|
|
57
|
+
* on a candidate-budget breach. Caching either turns one slow round into a
|
|
58
|
+
* permanently replayed failure for the whole key: a webhook redelivery, which
|
|
59
|
+
* is exactly when an operator is retrying, would be served the stored failure
|
|
60
|
+
* instead of running again, and the round's own retry sits behind this cache
|
|
61
|
+
* read so a stored breach short-circuits that too.
|
|
62
|
+
*
|
|
63
|
+
* Requiring EVERY candidate to be decided costs almost no hit rate — an
|
|
64
|
+
* indeterminate verdict is the exceptional path — and buys a guarantee that is
|
|
65
|
+
* easy to state: nothing a redelivery replays was ever undecided.
|
|
66
|
+
*
|
|
67
|
+
* An empty candidate list is not cacheable either. For a non-empty group it
|
|
68
|
+
* means every candidate is about to be recorded as "no verdict", which is the
|
|
69
|
+
* same failure by another route.
|
|
70
|
+
*/
|
|
71
|
+
export declare function isCacheableRoundResult(result: GlobalEvalRoundResult): boolean;
|
|
72
|
+
export declare class GlobalEvalRoundCache {
|
|
73
|
+
private readonly cache;
|
|
74
|
+
private hits;
|
|
75
|
+
private misses;
|
|
76
|
+
constructor(options: {
|
|
77
|
+
max: number;
|
|
78
|
+
});
|
|
79
|
+
get(key: string): GlobalEvalRoundResult | undefined;
|
|
80
|
+
set(key: string, value: GlobalEvalRoundResult): void;
|
|
81
|
+
/** Hit/miss counters for metrics and debugging. */
|
|
82
|
+
stats(): {
|
|
83
|
+
hits: number;
|
|
84
|
+
misses: number;
|
|
85
|
+
size: number;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=global-eval-round-cache.d.ts.map
|
package/dist/cache/index.d.ts
CHANGED
|
@@ -15,4 +15,7 @@ export { PendingBuildTracker } from './pending-builds.js';
|
|
|
15
15
|
export { PendingInitTracker } from './pending-inits.js';
|
|
16
16
|
export type { InitResult } from './pending-inits.js';
|
|
17
17
|
export { PendingDynamicTracker } from './pending-dynamics.js';
|
|
18
|
+
export { PendingGlobalEvalTracker } from './pending-global-evals.js';
|
|
19
|
+
export { GlobalEvalRoundCache, globalEvalRoundCacheKey, isCacheableRoundResult, } from './global-eval-round-cache.js';
|
|
20
|
+
export { parseGlobalEvalResult } from './pending-global-evals.js';
|
|
18
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending tracker for the Tier-2 global eval round.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrator dispatches one round job per (event × workflow repo) and
|
|
5
|
+
* waits for the agent to return a verdict per candidate workflow. The
|
|
6
|
+
* underlying tracker logic lives in `PendingTracker<GlobalEvalRoundResult>`;
|
|
7
|
+
* this subclass wires the round-specific logger prefix and disconnect error.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the dynamic-eval and init trackers, this round runs BEFORE any run row
|
|
10
|
+
* exists: its whole purpose is to decide which global workflows produce a run at
|
|
11
|
+
* all, so creating one up-front would defeat it.
|
|
12
|
+
*/
|
|
13
|
+
import { type GlobalEvalRoundResult } from '@kici-dev/engine';
|
|
14
|
+
import { PendingTracker } from './pending-tracker.js';
|
|
15
|
+
/**
|
|
16
|
+
* Validate the round result carried on a `job.status` message before it reaches
|
|
17
|
+
* the tracker.
|
|
18
|
+
*
|
|
19
|
+
* **This is the one place the round's wire payload is validated.** The status
|
|
20
|
+
* message's `data` field is `z.record(z.string(), z.unknown())`, so nothing
|
|
21
|
+
* upstream checks `globalEvalResult` against its schema — a cast at the message
|
|
22
|
+
* handler would be a type assertion over an arbitrary agent-supplied value, and
|
|
23
|
+
* any agent that owns the round job can send one. Parsing here keeps the
|
|
24
|
+
* "typed but unvalidated" boundary at a single named function instead of spread
|
|
25
|
+
* across every consumer.
|
|
26
|
+
*
|
|
27
|
+
* The parsed value is widened back to {@link GlobalEvalRoundResult}: the schema
|
|
28
|
+
* is deliberately loose about `jobs` (the lock-job shape has no Zod mirror), and
|
|
29
|
+
* the dispatch path re-checks every generated job before it builds anything from
|
|
30
|
+
* one.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseGlobalEvalResult(raw: unknown): {
|
|
33
|
+
ok: true;
|
|
34
|
+
value: GlobalEvalRoundResult;
|
|
35
|
+
} | {
|
|
36
|
+
ok: false;
|
|
37
|
+
error: string;
|
|
38
|
+
};
|
|
39
|
+
export declare class PendingGlobalEvalTracker extends PendingTracker<GlobalEvalRoundResult> {
|
|
40
|
+
constructor();
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=pending-global-evals.d.ts.map
|
|
@@ -18,6 +18,16 @@ export interface InitResult {
|
|
|
18
18
|
* function. The dispatch path re-materializes these into N execution jobs.
|
|
19
19
|
*/
|
|
20
20
|
matrixValues?: Array<Record<string, string | undefined>>;
|
|
21
|
+
/**
|
|
22
|
+
* Verdict of the workflow-level `filter`, reported only when the init job was
|
|
23
|
+
* asked to evaluate one. `false` means the workflow does not apply to this
|
|
24
|
+
* event and its job must not be dispatched.
|
|
25
|
+
*
|
|
26
|
+
* Optional on purpose: an agent that predates the filter never sends it, so
|
|
27
|
+
* absence means "no verdict was reported" and dispatch proceeds — only an
|
|
28
|
+
* explicit `false` suppresses.
|
|
29
|
+
*/
|
|
30
|
+
filterPassed?: boolean;
|
|
21
31
|
}
|
|
22
32
|
export declare class PendingInitTracker extends PendingTracker<InitResult> {
|
|
23
33
|
constructor();
|
|
@@ -25,14 +25,52 @@ interface StringKnobSpec {
|
|
|
25
25
|
expects: string;
|
|
26
26
|
validate: (value: string) => boolean;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* One boolean knob. A third spec type for the same reason {@link StringKnobSpec}
|
|
30
|
+
* is separate from {@link KnobSpec}: the value parser, the PATCH body type, and
|
|
31
|
+
* the `show` formatter all differ. A boolean also has no bounds to validate —
|
|
32
|
+
* only two accepted spellings.
|
|
33
|
+
*/
|
|
34
|
+
interface BooleanKnobSpec {
|
|
35
|
+
field: string;
|
|
36
|
+
flag: string;
|
|
37
|
+
label: string;
|
|
38
|
+
}
|
|
28
39
|
/** camelCase → value (null clears the override). */
|
|
29
|
-
type PatchBody = Record<string, number | string | null>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
40
|
+
type PatchBody = Record<string, number | string | boolean | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Parse an integer CLI flag against its knob's bounds, exiting on failure.
|
|
43
|
+
*
|
|
44
|
+
* `max` is optional because most knobs have no meaningful ceiling; the ones
|
|
45
|
+
* that do carry a hard boot-safety bound (see {@link KnobSpec.max}).
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseKnobValue(flag: string, value: string, min: number, max?: number): number;
|
|
32
48
|
/** Validate a text CLI flag against its knob's shape, exiting on failure. */
|
|
33
49
|
export declare function parseStringKnobValue(knob: StringKnobSpec, value: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Parse a boolean CLI flag, exiting on failure.
|
|
52
|
+
*
|
|
53
|
+
* Accepts only the exact strings `true` and `false`. A permissive parser (any
|
|
54
|
+
* non-empty string is true) would read `--global-workflows-enabled no` as an
|
|
55
|
+
* enable, which for this knob turns a typo into a security posture change.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseBooleanKnobValue(knob: BooleanKnobSpec, value: string): boolean;
|
|
34
58
|
/** Assemble the PATCH body from provided `set` flags (exits if none given). */
|
|
35
59
|
export declare function buildClusterPatch(opts: Record<string, string | undefined>): PatchBody;
|
|
60
|
+
/**
|
|
61
|
+
* Warn when only one of the two ordered global-eval timeouts is being set.
|
|
62
|
+
*
|
|
63
|
+
* The orchestrator's wait ceiling must exceed the agent's round budget — the
|
|
64
|
+
* agent's budget starts only once the round job is RUNNING, so a lower ceiling
|
|
65
|
+
* fires on every round that merely waited for a free agent, failing every round
|
|
66
|
+
* silently. The server rejects the pair outright when BOTH effective values are
|
|
67
|
+
* stored, but it cannot compare against a column left NULL: NULL means "the
|
|
68
|
+
* orchestrator's configured default applies", and the route does not know that
|
|
69
|
+
* number. Setting one alone is exactly that blind spot, so warn here.
|
|
70
|
+
*
|
|
71
|
+
* Returns the lines rather than printing them so the check is unit-testable.
|
|
72
|
+
*/
|
|
73
|
+
export declare function unpairedEvalTimeoutWarnings(patch: PatchBody): string[];
|
|
36
74
|
/** Build the reset PATCH body: all knobs → null, or just the flagged ones. */
|
|
37
75
|
export declare function buildClusterReset(opts: Record<string, boolean | undefined>): PatchBody;
|
|
38
76
|
/**
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* runs list List execution runs (table or JSON)
|
|
10
10
|
* runs show <runId> Show run detail with jobs and steps
|
|
11
11
|
* runs jobs <runId> List jobs for a run (optional steps)
|
|
12
|
+
* runs logs <runId> --job <jobId> Print a page of one step's log lines
|
|
12
13
|
* runs ephemeral-key <runId> Show scrub status of the run's key
|
|
13
14
|
* runs secret-outputs <runId> List secret outputs (masked / reveal)
|
|
14
15
|
*
|