@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.
- package/dist/agent/token-store.d.ts +4 -0
- package/dist/app.d.ts +27 -0
- package/dist/audit/access-log.d.ts +4 -0
- package/dist/cache/pending-inits.d.ts +3 -2
- package/dist/cancel/cancel-run.d.ts +2 -0
- package/dist/cli/api-client.d.ts +10 -0
- package/dist/cli/commands/attestations-list.d.ts +49 -0
- package/dist/cli/commands/attestations-retry.d.ts +28 -0
- package/dist/cli/commands/attestations-reverify.d.ts +18 -0
- package/dist/cli/commands/attestations.d.ts +4 -0
- package/dist/cli/commands/cluster.d.ts +3 -0
- package/dist/cli/commands/shared/versioned-upgrade.d.ts +52 -0
- package/dist/cli/local-github-ingress-url.d.ts +8 -0
- package/dist/cli.js +3308 -1011
- package/dist/cluster/cluster-identity.d.ts +11 -0
- package/dist/cluster/reconcile-identity.d.ts +50 -0
- package/dist/cold-store/load-access-log-range.d.ts +4 -0
- package/dist/config.d.ts +4 -0
- package/dist/dashboard/attestation-filters.d.ts +131 -0
- package/dist/dashboard/handler.d.ts +56 -4
- package/dist/db/migrations/054_local_working_tree.d.ts +15 -0
- package/dist/db/migrations/055_agent_token_mandatory_labels.d.ts +18 -0
- package/dist/db/migrations/056_execution_jobs_environments.d.ts +17 -0
- package/dist/db/migrations/057_step_concurrency.d.ts +4 -0
- package/dist/db/migrations/058_access_log_agent_label.d.ts +4 -0
- package/dist/db/migrations/059_attestation_verdict.d.ts +4 -0
- package/dist/db/migrations/060_run_trigger_actor.d.ts +4 -0
- package/dist/db/migrations/061_execution_runs_environment_id.d.ts +4 -0
- package/dist/db/migrations/062_execution_runs_agent_label.d.ts +4 -0
- package/dist/db/migrations/063_access_log_agent_label_index.d.ts +10 -0
- package/dist/db/migrations/064_execution_jobs_skipped_environments.d.ts +17 -0
- package/dist/db/migrations/065_pending_attestations.d.ts +22 -0
- package/dist/db/migrations/066_pending_attestations_rejected.d.ts +13 -0
- package/dist/db/types.d.ts +113 -0
- package/dist/environments/protection/aggregate.d.ts +43 -0
- package/dist/environments/protection/satisfiability.d.ts +69 -0
- package/dist/events/circuit-breaker.d.ts +8 -5
- package/dist/events/event-router.d.ts +12 -4
- package/dist/events/event-store.d.ts +21 -3
- package/dist/events/trust-store.d.ts +6 -1
- package/dist/events/types.d.ts +6 -1
- package/dist/helpers/rate-limiter.d.ts +17 -2
- package/dist/index.js +11 -1
- package/dist/metrics/prometheus.d.ts +6 -0
- package/dist/orchestrator-core.d.ts +8 -0
- package/dist/pipeline/dispatch-matched-workflow.d.ts +8 -0
- package/dist/pipeline/flatten-lock-steps.d.ts +11 -0
- package/dist/pipeline/inline-eval.d.ts +2 -1
- package/dist/pipeline/job-environments.d.ts +71 -0
- package/dist/pipeline/manual-schedule.d.ts +23 -1
- package/dist/pipeline/process-webhook.d.ts +14 -1
- package/dist/pipeline/rerun.d.ts +1 -1
- package/dist/pipeline/test-pipeline.d.ts +15 -0
- package/dist/provenance/attestation-retrier.d.ts +92 -0
- package/dist/provenance/backfill-run.d.ts +41 -0
- package/dist/provenance/pending-attestations-repo.d.ts +64 -0
- package/dist/provenance/trust-root.d.ts +24 -0
- package/dist/provenance/verify-at-ingest.d.ts +31 -0
- package/dist/reporting/agent-failure-category.d.ts +27 -0
- package/dist/reporting/agent-run-result-mapper.d.ts +13 -0
- package/dist/reporting/execution-tracker.d.ts +38 -6
- package/dist/reporting/run-aggregator.d.ts +165 -0
- package/dist/reporting/step-log-reader.d.ts +39 -0
- package/dist/routes/admin-access-log.d.ts +2 -1
- package/dist/routes/admin-registrations.d.ts +8 -0
- package/dist/routes/admin-runs.d.ts +7 -0
- package/dist/routes/admin.d.ts +14 -0
- package/dist/routes/github-webhook.d.ts +37 -0
- package/dist/routes/health.d.ts +9 -0
- package/dist/routes/webhooks.d.ts +2 -4
- package/dist/secrets/index.d.ts +1 -1
- package/dist/server.js +5326 -2100
- package/dist/sources/source-store.d.ts +6 -0
- package/dist/stale-detector/workflow-deadline-detector.d.ts +25 -0
- package/dist/standalone.js +19787 -17304
- package/dist/storage/loopback-guard.d.ts +49 -0
- package/dist/webhook/dedup.d.ts +14 -0
- package/dist/ws/agent-handler.d.ts +38 -1
- package/dist/ws/oidc-token-relay.d.ts +31 -21
- package/dist/ws/orch-rpc.d.ts +9 -0
- package/dist/ws/platform-client.d.ts +53 -1
- package/dist/ws/test-relay-handlers.d.ts +2 -0
- package/installer-image-digests.json +3 -3
- package/package.json +6 -8
- package/sbom.spdx.json +62 -57
- package/dist/secrets/crypto.d.ts +0 -49
package/dist/db/types.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
22
|
-
*
|
|
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(
|
|
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
|
|
162
|
+
* Catch-up: process every unprocessed event missed during downtime.
|
|
163
163
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* of
|
|
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
|
|
62
|
-
*
|
|
63
|
-
*
|
|
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;
|
package/dist/events/types.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|