@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
|
@@ -87,6 +87,15 @@ interface SetPendingOptions {
|
|
|
87
87
|
repo: string;
|
|
88
88
|
sha: string;
|
|
89
89
|
workflowName: string;
|
|
90
|
+
/**
|
|
91
|
+
* The repository that DEFINES the workflow, when that is not the repository
|
|
92
|
+
* the run acted on — an organization-wide workflow dispatched against
|
|
93
|
+
* another repository. Qualifies the check-run name so the run cannot share a
|
|
94
|
+
* check run with a same-named workflow of the acted-on repository; see
|
|
95
|
+
* `workflowLabel`. Passing the acted-on repository here is a no-op, so a
|
|
96
|
+
* caller cannot change a per-repository run's name by accident.
|
|
97
|
+
*/
|
|
98
|
+
workflowRepoIdentifier?: string;
|
|
90
99
|
jobNames: string[];
|
|
91
100
|
installationId?: number;
|
|
92
101
|
/** Routing key for per-app credential lookup (e.g., "github:12345"). */
|
|
@@ -105,6 +114,8 @@ interface SetBuildPendingOptions {
|
|
|
105
114
|
repo: string;
|
|
106
115
|
sha: string;
|
|
107
116
|
workflowName: string;
|
|
117
|
+
/** See {@link SetPendingOptions.workflowRepoIdentifier}. */
|
|
118
|
+
workflowRepoIdentifier?: string;
|
|
108
119
|
installationId?: number;
|
|
109
120
|
/** Routing key for per-app credential lookup (e.g., "github:12345"). */
|
|
110
121
|
routingKey?: string;
|
|
@@ -122,6 +133,8 @@ interface SetBuildCompleteOptions {
|
|
|
122
133
|
repo: string;
|
|
123
134
|
sha: string;
|
|
124
135
|
workflowName: string;
|
|
136
|
+
/** See {@link SetPendingOptions.workflowRepoIdentifier}. */
|
|
137
|
+
workflowRepoIdentifier?: string;
|
|
125
138
|
status: TerminalJobStatus;
|
|
126
139
|
installationId?: number;
|
|
127
140
|
/** Routing key for per-app credential lookup (e.g., "github:12345"). */
|
|
@@ -141,6 +154,8 @@ interface UpdateJobStatusOptions {
|
|
|
141
154
|
repo: string;
|
|
142
155
|
sha: string;
|
|
143
156
|
workflowName: string;
|
|
157
|
+
/** See {@link SetPendingOptions.workflowRepoIdentifier}. */
|
|
158
|
+
workflowRepoIdentifier?: string;
|
|
144
159
|
jobName: string;
|
|
145
160
|
state: TerminalJobStatus;
|
|
146
161
|
installationId?: number;
|
|
@@ -167,6 +182,8 @@ interface UpdateWorkflowStatusOptions {
|
|
|
167
182
|
repo: string;
|
|
168
183
|
sha: string;
|
|
169
184
|
workflowName: string;
|
|
185
|
+
/** See {@link SetPendingOptions.workflowRepoIdentifier}. */
|
|
186
|
+
workflowRepoIdentifier?: string;
|
|
170
187
|
overallStatus: TerminalJobStatus;
|
|
171
188
|
installationId?: number;
|
|
172
189
|
/** Routing key for per-app credential lookup (e.g., "github:12345"). */
|
|
@@ -186,6 +203,8 @@ interface UpdateStepProgressOptions {
|
|
|
186
203
|
repo: string;
|
|
187
204
|
sha: string;
|
|
188
205
|
workflowName: string;
|
|
206
|
+
/** See {@link SetPendingOptions.workflowRepoIdentifier}. */
|
|
207
|
+
workflowRepoIdentifier?: string;
|
|
189
208
|
jobName: string;
|
|
190
209
|
stepIndex: number;
|
|
191
210
|
stepName: string;
|
|
@@ -321,6 +340,8 @@ export declare class CheckRunReporter {
|
|
|
321
340
|
repo: string;
|
|
322
341
|
sha: string;
|
|
323
342
|
workflowName: string;
|
|
343
|
+
/** See {@link SetPendingOptions.workflowRepoIdentifier}. */
|
|
344
|
+
workflowRepoIdentifier?: string;
|
|
324
345
|
jobNames: string[];
|
|
325
346
|
}): void;
|
|
326
347
|
/**
|
|
@@ -337,7 +358,8 @@ export declare class CheckRunReporter {
|
|
|
337
358
|
* Separate from execution check runs so users see build progress independently.
|
|
338
359
|
* Fire-and-forget: errors are logged but don't block the pipeline.
|
|
339
360
|
*
|
|
340
|
-
* Check run name format: kici/{workflowName}/setup
|
|
361
|
+
* Check run name format: kici/{workflowName}/setup — prefixed with the
|
|
362
|
+
* defining repository for a cross-repository global run, see `workflowLabel`.
|
|
341
363
|
*/
|
|
342
364
|
setBuildPending(opts: SetBuildPendingOptions): void;
|
|
343
365
|
/**
|
|
@@ -390,6 +412,13 @@ export declare class CheckRunReporter {
|
|
|
390
412
|
* Read-through helper: look up a check-run ID. Checks L1 first, falls
|
|
391
413
|
* through to the store on miss, caches the result on hit. Returns
|
|
392
414
|
* undefined when neither layer has the ID — the caller logs + skips.
|
|
415
|
+
*
|
|
416
|
+
* The same row also rehydrates the `terminalSent` latch. Both L1 entries are
|
|
417
|
+
* dropped together by `cleanupRun`, and only one of them used to come back:
|
|
418
|
+
* the id reloaded from here while the latch did not, which is exactly the
|
|
419
|
+
* pair that lets a late step-progress update resolve a check run and PATCH
|
|
420
|
+
* `status: in_progress` over its completion. Reading `terminal_sent_at` off
|
|
421
|
+
* the row this query already selects costs nothing and closes that gap.
|
|
393
422
|
*/
|
|
394
423
|
private resolveCheckRunId;
|
|
395
424
|
/**
|
|
@@ -425,6 +454,28 @@ export declare class CheckRunReporter {
|
|
|
425
454
|
*/
|
|
426
455
|
private appendTraceIds;
|
|
427
456
|
private checkRunKey;
|
|
457
|
+
/**
|
|
458
|
+
* The workflow label every check-run name and title is built from.
|
|
459
|
+
*
|
|
460
|
+
* A check run's identity is `(owner, repo, sha, check name)` — on the
|
|
461
|
+
* provider, in `check_run_tracking`'s primary key, and in this class's L1
|
|
462
|
+
* keys. There is no run id anywhere in it. Two per-repository runs cannot
|
|
463
|
+
* collide on that identity, because one lock file cannot define a workflow
|
|
464
|
+
* name twice. An organization-wide workflow can: it is defined in ANOTHER
|
|
465
|
+
* repository, so its name is free to equal a workflow name of the repository
|
|
466
|
+
* it was dispatched against, and on the same commit the two runs then resolve
|
|
467
|
+
* to one check run. The global run's conclusion would complete the acted-on
|
|
468
|
+
* repository's check — the signal branch protection reads — and point its
|
|
469
|
+
* `details_url` at the wrong run, while `cleanupRun` would evict the other
|
|
470
|
+
* run's check-run id and terminal latch on prune.
|
|
471
|
+
*
|
|
472
|
+
* Qualifying the label with the defining repository keeps them apart. The
|
|
473
|
+
* "differs from the acted-on repository" narrowing lives here rather than at
|
|
474
|
+
* the call sites, so a per-repository run's name — which is customer-visible
|
|
475
|
+
* and may sit in a branch-protection required-check list — cannot move
|
|
476
|
+
* because a caller passed the field where it did not apply.
|
|
477
|
+
*/
|
|
478
|
+
private workflowLabel;
|
|
428
479
|
/**
|
|
429
480
|
* Build the `details_url` for a check run pointing at the dashboard's
|
|
430
481
|
* public-alias resolver (`/r/orgs/<oal_xxx>/runs/<runId>`). Returns
|
|
@@ -28,6 +28,14 @@ export interface ExecutionContext {
|
|
|
28
28
|
installationId?: number;
|
|
29
29
|
requestId?: string;
|
|
30
30
|
routingKey?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The repository that DEFINES the workflow, when that is not
|
|
33
|
+
* `repoIdentifier` — a global workflow authored in one repository and
|
|
34
|
+
* dispatched against another. Set only when the two differ, so "present"
|
|
35
|
+
* marks a cross-repository global run; forwarded to the Platform on
|
|
36
|
+
* `execution.status`.
|
|
37
|
+
*/
|
|
38
|
+
workflowRepoIdentifier?: string;
|
|
31
39
|
/** Git branch or tag (e.g. "main", "feature/foo"). */
|
|
32
40
|
ref?: string;
|
|
33
41
|
/** Trigger event type (e.g. "push", "pr:open"). */
|
|
@@ -302,7 +310,14 @@ export declare class ExecutionTracker {
|
|
|
302
310
|
/** Agent provenance label when triggered through an agent credential. */
|
|
303
311
|
triggeredByAgentLabel?: string | null,
|
|
304
312
|
/** Pull-request number for PR-triggered runs; null/omitted for non-PR runs. */
|
|
305
|
-
prNumber?: number | null
|
|
313
|
+
prNumber?: number | null,
|
|
314
|
+
/**
|
|
315
|
+
* The repository that DEFINES the workflow, when that is not
|
|
316
|
+
* `repoIdentifier` — an organization-wide workflow authored in one
|
|
317
|
+
* repository and dispatched against another. Omitted/null for every
|
|
318
|
+
* per-repository run, where the two are the same repository.
|
|
319
|
+
*/
|
|
320
|
+
workflowRepoIdentifier?: string | null): Promise<void>;
|
|
306
321
|
/**
|
|
307
322
|
* Upsert one execution_jobs row per dispatched job (idempotent on
|
|
308
323
|
* (run_id, job_id) to tolerate a race with an early `onJobStatus`). The
|
|
@@ -515,6 +530,58 @@ export declare class ExecutionTracker {
|
|
|
515
530
|
initFailure: InitFailure;
|
|
516
531
|
triggerEvent?: string;
|
|
517
532
|
commitMessage?: string;
|
|
533
|
+
/**
|
|
534
|
+
* The repository that DEFINES the workflow this run was about to execute.
|
|
535
|
+
* REQUIRED: every run has one, and a caller that does not state it must not
|
|
536
|
+
* compile. Left optional, a global dispatch path added later would record a
|
|
537
|
+
* null marker — and a null marker does not mean "unknown", it means "the
|
|
538
|
+
* workflow lives in this run's own repository", which the whole
|
|
539
|
+
* defining-repository predicate then reads as fact.
|
|
540
|
+
*
|
|
541
|
+
* Narrowed to the differing case here, exactly as `onExecutionStarted`
|
|
542
|
+
* does, so passing `repoIdentifier` (the per-repository case) is correct
|
|
543
|
+
* and records nothing.
|
|
544
|
+
*/
|
|
545
|
+
workflowRepoIdentifier: string;
|
|
546
|
+
}): Promise<void>;
|
|
547
|
+
/**
|
|
548
|
+
* Write one `failed` execution_runs row for a global eval round that never
|
|
549
|
+
* produced verdicts.
|
|
550
|
+
*
|
|
551
|
+
* The round decides which organization-wide workflows apply to an event, so a
|
|
552
|
+
* round that fails suppresses every workflow it was deciding on. Without a row
|
|
553
|
+
* that outcome is invisible: no run was ever created for those workflows, so
|
|
554
|
+
* there is nothing on the dashboard to explain why they did not appear.
|
|
555
|
+
*
|
|
556
|
+
* **One row for the whole round, not one per workflow.** The round exists to
|
|
557
|
+
* collapse N candidate workflows into a single pre-run job; fanning its
|
|
558
|
+
* failure back out into N rows would undo exactly that. The reason names every
|
|
559
|
+
* affected workflow instead.
|
|
560
|
+
*
|
|
561
|
+
* Its `runId` is the round job's own — the last attempt's — so the row, the
|
|
562
|
+
* `dispatch_queue` row, and the attempt's logs all carry one id. No conflict
|
|
563
|
+
* guard is needed beyond `doNothing`: the id was minted for this round and
|
|
564
|
+
* belongs to no other run.
|
|
565
|
+
*/
|
|
566
|
+
recordGlobalEvalRoundFailureRun(args: {
|
|
567
|
+
runId: string;
|
|
568
|
+
workflowName: string;
|
|
569
|
+
provider: string;
|
|
570
|
+
repoIdentifier: string;
|
|
571
|
+
ref: string;
|
|
572
|
+
sha: string;
|
|
573
|
+
deliveryId: string | null;
|
|
574
|
+
providerContext: Record<string, unknown>;
|
|
575
|
+
routingKey: string;
|
|
576
|
+
failureReason: string;
|
|
577
|
+
triggerEvent?: string;
|
|
578
|
+
/**
|
|
579
|
+
* The repository whose global workflows the round was deciding. A round is
|
|
580
|
+
* definitionally the cross-repository case, so this is normally a different
|
|
581
|
+
* repository from `repoIdentifier` — recorded, like everywhere else, only
|
|
582
|
+
* when the two actually differ.
|
|
583
|
+
*/
|
|
584
|
+
workflowRepoIdentifier: string;
|
|
518
585
|
}): Promise<void>;
|
|
519
586
|
/**
|
|
520
587
|
* Record a run paused at the workflow install gate (a `registries:` /
|
|
@@ -549,6 +616,13 @@ export declare class ExecutionTracker {
|
|
|
549
616
|
* security hold fail-closed unreachable by the comment path.
|
|
550
617
|
*/
|
|
551
618
|
prNumber?: number | null;
|
|
619
|
+
/**
|
|
620
|
+
* The repository that DEFINES the held workflow. REQUIRED for the same
|
|
621
|
+
* reason as on {@link ExecutionTracker.recordInitFailureRun}: a hold is a
|
|
622
|
+
* live, resumable run, so a null marker here would misattribute it for the
|
|
623
|
+
* whole time it sits in the queue and for the run it resumes into.
|
|
624
|
+
*/
|
|
625
|
+
workflowRepoIdentifier: string;
|
|
552
626
|
}): Promise<void>;
|
|
553
627
|
/**
|
|
554
628
|
* Flip a `held` run back to `pending` so the resumed dispatch can proceed
|
|
@@ -593,11 +667,13 @@ export declare class ExecutionTracker {
|
|
|
593
667
|
* registered. Returns true when a token was taken, false for an unknown or
|
|
594
668
|
* already-completed run (nothing to hold).
|
|
595
669
|
*
|
|
596
|
-
*
|
|
670
|
+
* Four registration windows need this. A run whose source-pack `__build__`
|
|
597
671
|
* job is dispatched first is registered with that job ALONE, and its real
|
|
598
|
-
* jobs are only dispatched once the build finishes
|
|
599
|
-
*
|
|
600
|
-
*
|
|
672
|
+
* jobs are only dispatched once the build finishes; a run without a build is
|
|
673
|
+
* registered with NO jobs before its dispatch loop, so its jobs land once
|
|
674
|
+
* every dispatch has returned. A deferred init job and a deferred dynamic
|
|
675
|
+
* entry each register their jobs from a fire-and-forget task that outlives
|
|
676
|
+
* the dispatch call. In every case, without a token the already
|
|
601
677
|
* -registered jobs reaching a terminal state satisfies {@link isRunComplete},
|
|
602
678
|
* so the run is finalized early: a terminal run status is written, the
|
|
603
679
|
* provider check is posted, and the status is forwarded to the Platform — all
|
|
@@ -612,9 +688,13 @@ export declare class ExecutionTracker {
|
|
|
612
688
|
* and an off-by-one job count for the length of the window. A counter holds
|
|
613
689
|
* the completion check open without being visible to any of them, and it
|
|
614
690
|
* writes no `execution_jobs` row, so it never surfaces as a phantom job.
|
|
615
|
-
* A token whose holder never settles keeps the run
|
|
691
|
+
* A token whose holder never settles keeps the run open until the stale
|
|
616
692
|
* detector reaps it — the same backstop that covers a job that never
|
|
617
|
-
* reports.
|
|
693
|
+
* reports. That backstop only reaches a run that has at least one job: every
|
|
694
|
+
* sub-scan of the stale detector starts from `execution_jobs` or
|
|
695
|
+
* `dispatch_queue`. So a holder that registers NO jobs must terminalize the
|
|
696
|
+
* run itself rather than rely on the sweep — which is what
|
|
697
|
+
* `dispatchMatchedWorkflow` does when its dispatch loop throws.
|
|
618
698
|
*
|
|
619
699
|
* Each token must be paired with exactly one {@link releasePendingJobsHold}.
|
|
620
700
|
*/
|
|
@@ -645,6 +725,35 @@ export declare class ExecutionTracker {
|
|
|
645
725
|
* Used by LogWriter to construct log paths.
|
|
646
726
|
*/
|
|
647
727
|
getJobName(runId: string, jobId: string): string | undefined;
|
|
728
|
+
/**
|
|
729
|
+
* Resolve the job name that names a step-log storage path, with a durable
|
|
730
|
+
* fallback. The step-log path is `executions/{runId}/job-{jobName}/…` and it
|
|
731
|
+
* is written from two sites — the chunk writer (`log-chunk-sink`) and the
|
|
732
|
+
* `execution_steps.log_path` upsert in `onStepStatus`. A purely in-memory
|
|
733
|
+
* lookup (`getJobName`) returns `undefined` during the brief window between
|
|
734
|
+
* dispatch and `addJobsToRun` populating `run.jobs`, so an early chunk would
|
|
735
|
+
* land under `job-{jobId}` while the reader later keys on `job-{realName}` —
|
|
736
|
+
* silently losing those lines. Both write sites go through this resolver so
|
|
737
|
+
* they cannot disagree: in-memory first, then the authoritative
|
|
738
|
+
* `dispatch_queue.job_name` (the same source `recoverJobFromDispatchQueue`
|
|
739
|
+
* trusts), and only `jobId` when the job is genuinely unknown.
|
|
740
|
+
*/
|
|
741
|
+
resolveJobName(runId: string, jobId: string): Promise<string>;
|
|
742
|
+
/**
|
|
743
|
+
* Seed the in-memory job name for a job the coordinator did not itself
|
|
744
|
+
* dispatch — a worker-rerouted job. Such a job is tracked in the cluster
|
|
745
|
+
* coordinator's own map, never enters this tracker's `run.jobs` via
|
|
746
|
+
* `addJobsToRun`, and has no coordinator-owned `dispatch_queue` row (the
|
|
747
|
+
* worker owns it under a fresh id), so `resolveJobName` would fall back to
|
|
748
|
+
* the bare `jobId` for its early log chunks while the reader keys on the real
|
|
749
|
+
* name — the same split-brain that loses lines. Seeding the name here, at
|
|
750
|
+
* reroute time, makes `resolveJobName`'s fast path correct from the first
|
|
751
|
+
* relayed chunk, independent of when the worker's `dispatch_queue` row becomes
|
|
752
|
+
* visible. In-memory only: the `execution_jobs` row is still created lazily by
|
|
753
|
+
* the worker's first status update. Guarded against clobbering an existing
|
|
754
|
+
* entry so a later terminal status is never overwritten.
|
|
755
|
+
*/
|
|
756
|
+
registerJobName(runId: string, jobId: string, jobName: string): void;
|
|
648
757
|
/**
|
|
649
758
|
* Find the runId of the in-memory run that owns a given dispatched jobId.
|
|
650
759
|
*
|
|
@@ -697,6 +806,7 @@ export declare class ExecutionTracker {
|
|
|
697
806
|
status: ExecutionRunStatus;
|
|
698
807
|
routingKey?: string;
|
|
699
808
|
repoIdentifier?: string;
|
|
809
|
+
workflowRepoIdentifier?: string;
|
|
700
810
|
sha?: string;
|
|
701
811
|
ref?: string;
|
|
702
812
|
triggerEvent?: string;
|
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
* behave identically — a worker-dispatched job's logs are persisted, counted
|
|
8
8
|
* and forwarded exactly like a locally-dispatched one's.
|
|
9
9
|
*
|
|
10
|
-
* The storage key is derived from `executionTracker.
|
|
11
|
-
* `ExecutionTracker.onStepStatus` uses to fill `execution_steps.log_path`,
|
|
12
|
-
* the reader and the writer cannot disagree about the naming rule
|
|
10
|
+
* The storage key is derived from `executionTracker.resolveJobName`, the same
|
|
11
|
+
* call `ExecutionTracker.onStepStatus` uses to fill `execution_steps.log_path`,
|
|
12
|
+
* so the reader and the writer cannot disagree about the naming rule — the
|
|
13
|
+
* resolver falls back to `dispatch_queue.job_name` during the dispatch window
|
|
14
|
+
* before in-memory job state is populated, which is what stops an early chunk
|
|
15
|
+
* from being persisted under an unreadable `job-{jobId}` path.
|
|
13
16
|
*/
|
|
14
17
|
import type { LogStream } from '@kici-dev/engine';
|
|
15
18
|
import type { LogWriter } from './log-writer.js';
|
|
@@ -32,9 +35,9 @@ export interface LogChunkSinkDeps {
|
|
|
32
35
|
stepLogBuffer?: StepLogBuffer;
|
|
33
36
|
/** Durable step-log persistence. Absent when the orchestrator has no database. */
|
|
34
37
|
logWriter?: LogWriter;
|
|
35
|
-
/** Resolves the job name that names the storage path. */
|
|
38
|
+
/** Resolves the job name that names the storage path (durable fallback). */
|
|
36
39
|
executionTracker?: {
|
|
37
|
-
|
|
40
|
+
resolveJobName(runId: string, jobId: string): Promise<string>;
|
|
38
41
|
};
|
|
39
42
|
/**
|
|
40
43
|
* Forward to the Platform for browser fan-out. Absent in independent mode.
|
|
@@ -18,6 +18,11 @@ import type { AccessLogWriter } from '../audit/access-log.js';
|
|
|
18
18
|
interface OrgSettingsRouteDeps {
|
|
19
19
|
db: Kysely<Database>;
|
|
20
20
|
rbac: RbacEnforcer;
|
|
21
|
+
/**
|
|
22
|
+
* Applies when `cluster_settings.global_workflows_enabled` is NULL —
|
|
23
|
+
* `config.globalWorkflowsEnabled`.
|
|
24
|
+
*/
|
|
25
|
+
globalWorkflowsEnabledDefault: boolean;
|
|
21
26
|
/**
|
|
22
27
|
* Optional — when wired, each `dashboard_write_policy` flip emits one
|
|
23
28
|
* `access_log` row (`org_settings.dashboard_write_policy.update`)
|
package/dist/routes/admin.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ export interface AdminRouteDeps {
|
|
|
40
40
|
rbac: RbacEnforcer;
|
|
41
41
|
secretStore: PgSecretStore;
|
|
42
42
|
auditLogger: AuditLogger;
|
|
43
|
+
/**
|
|
44
|
+
* Fleet-wide default for the global-workflows master switch, applied when the
|
|
45
|
+
* cluster column is NULL. Optional so WS-only / test admins can omit it;
|
|
46
|
+
* omitting it means the secure default (disabled).
|
|
47
|
+
*/
|
|
48
|
+
globalWorkflowsEnabledDefault?: boolean;
|
|
43
49
|
/**
|
|
44
50
|
* Optional -- the coordinator drain controller. When provided, the
|
|
45
51
|
* `POST`/`GET /api/v1/admin/orchestrator/drain` routes are mounted (backing
|
package/dist/scaler/manager.d.ts
CHANGED
|
@@ -422,16 +422,18 @@ export declare class ScalerManager {
|
|
|
422
422
|
*/
|
|
423
423
|
onAgentDisconnected(agentId: string): void;
|
|
424
424
|
/**
|
|
425
|
-
* Look up the scaler backend
|
|
425
|
+
* Look up the scaler backend TYPE (`container`, `firecracker`,
|
|
426
426
|
* `bare-metal`) for a registered agent. Returns null if the agent is
|
|
427
|
-
* not scaler-managed (
|
|
427
|
+
* not scaler-managed (a static / stateful agent).
|
|
428
428
|
*
|
|
429
|
-
* Used by AgentMetricsAggregator to
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
|
|
434
|
-
|
|
429
|
+
* Used by AgentMetricsAggregator to stamp the `scaler` label on each
|
|
430
|
+
* kici_agent_* series. The label MUST be the backend type, not the
|
|
431
|
+
* operator-chosen scaler name: the Platform catalog filter constrains
|
|
432
|
+
* it to AGENT_SCALER_VALUES (the four types), so a free-form name is
|
|
433
|
+
* dropped as bad_label_value. `managedAgentIndex` stores the scaler
|
|
434
|
+
* name, so resolve it through `backends` to the type.
|
|
435
|
+
*/
|
|
436
|
+
getBackendType(agentId: string): string | null;
|
|
435
437
|
/**
|
|
436
438
|
* Called from agent-handler.ts when an agent sends config.ack.
|
|
437
439
|
* For Firecracker agents, clears MMDS data (belt-and-suspenders with in-VM iptables).
|
|
@@ -60,12 +60,30 @@ export declare class PgSecretStore implements SecretStore {
|
|
|
60
60
|
* Get all secrets for a scope as decrypted key-value pairs.
|
|
61
61
|
*/
|
|
62
62
|
getSecrets(orgId: string, scope: string): Promise<Record<string, string>>;
|
|
63
|
+
/** Check if a scope is internal/operational (always allowed regardless of toggle). */
|
|
64
|
+
private isInternalScope;
|
|
63
65
|
/**
|
|
64
66
|
* Set (create or update) a secret in a scope.
|
|
65
67
|
* Encrypts the value with AAD = "orgId:scope:key".
|
|
68
|
+
*
|
|
69
|
+
* Rejects a key outside `[A-Za-z0-9._-]` before doing anything else. The AAD
|
|
70
|
+
* is a plain concatenation, so a `:` in the key would let two distinct
|
|
71
|
+
* locations render one AAD (scope 'b' + key 'c:d' equals scope 'b:c' + key
|
|
72
|
+
* 'd') and a ciphertext written at one would authenticate at the other. The
|
|
73
|
+
* check sits ahead of the customerSecretsEnabled gate so internal scopes get
|
|
74
|
+
* no exemption — the binding has to hold for every writer.
|
|
75
|
+
*
|
|
76
|
+
* Callers pass a bare scope path, so the AAD's middle field is colon-free
|
|
77
|
+
* too: the admin route and the dashboard handler both run the scope
|
|
78
|
+
* validator immediately before calling in, and `source-store` builds its
|
|
79
|
+
* scope from a uuid. That precondition is what makes the whole triple
|
|
80
|
+
* recoverable, and it is the caller's to keep — this method does not
|
|
81
|
+
* re-check it.
|
|
82
|
+
*
|
|
83
|
+
* Write-path only: getSecrets, listKeys, deleteSecret, deleteScope,
|
|
84
|
+
* renameScope, getAllSecrets and createScope stay unvalidated, which is what
|
|
85
|
+
* keeps a key stored before this rule readable and deletable.
|
|
66
86
|
*/
|
|
67
|
-
/** Check if a scope is internal/operational (always allowed regardless of toggle). */
|
|
68
|
-
private isInternalScope;
|
|
69
87
|
setSecret(orgId: string, scope: string, key: string, value: string): Promise<void>;
|
|
70
88
|
/**
|
|
71
89
|
* Delete a secret from a scope.
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import type { Kysely } from 'kysely';
|
|
2
2
|
import type { Database } from '../db/types.js';
|
|
3
|
+
import type { ClusterSettingsReader } from '../cluster/cluster-settings-reader.js';
|
|
3
4
|
interface GlobalWorkflowPermission {
|
|
4
5
|
allowed: boolean;
|
|
5
6
|
reason?: string;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
+
* Permission enforcement for global workflows.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
11
|
+
* A fleet-wide master switch gates everything first:
|
|
12
|
+
* `cluster_settings.global_workflows_enabled`, held by the orchestrator
|
|
13
|
+
* operator through `kici-admin cluster-settings` (NULL ⇒ the configured
|
|
14
|
+
* `KICI_GLOBAL_WORKFLOWS_ENABLED` default, off by default). When the switch is
|
|
15
|
+
* off — or the cluster row is unreadable, which fails closed — no repo may
|
|
16
|
+
* register or dispatch a global workflow, whatever the per-org lists say.
|
|
17
|
+
*
|
|
18
|
+
* With the switch on, three independent per-org axes apply (each stored as a
|
|
19
|
+
* jsonb array of `{routingKey?, pattern}` entries on `org_settings`). A missing
|
|
20
|
+
* `org_settings` row means "no per-org restrictions" for the repo and source
|
|
21
|
+
* axes, not a denial; elevated access still requires an explicit list, so a
|
|
22
|
+
* missing row grants none.
|
|
12
23
|
*
|
|
13
24
|
* - Workflow-repo allow-list (`global_workflow_allowed_repos`): which repos
|
|
14
25
|
* may author global workflows. null/empty = any repo. Checked at
|
|
@@ -32,15 +43,32 @@ interface GlobalWorkflowPermission {
|
|
|
32
43
|
*/
|
|
33
44
|
export declare class GlobalWorkflowPolicy {
|
|
34
45
|
private readonly db;
|
|
35
|
-
|
|
46
|
+
private readonly clusterSettings;
|
|
47
|
+
/** Applies when the cluster column is NULL — `config.globalWorkflowsEnabled`. */
|
|
48
|
+
private readonly defaultEnabled;
|
|
49
|
+
constructor(db: Kysely<Database>, clusterSettings: ClusterSettingsReader,
|
|
50
|
+
/** Applies when the cluster column is NULL — `config.globalWorkflowsEnabled`. */
|
|
51
|
+
defaultEnabled: boolean);
|
|
52
|
+
/**
|
|
53
|
+
* The fleet-wide master gate, consulted before any per-org list.
|
|
54
|
+
*
|
|
55
|
+
* Returns `undefined` when the gate passes, or the denial to return
|
|
56
|
+
* verbatim when it does not — so all three axes share one decision and one
|
|
57
|
+
* pair of reasons.
|
|
58
|
+
*
|
|
59
|
+
* Fails closed on `{ ok: false }`. That case is a database fault, not an
|
|
60
|
+
* operator choice, and answering it from `defaultEnabled` would open the
|
|
61
|
+
* feature during an outage the moment that default is ever flipped.
|
|
62
|
+
*/
|
|
63
|
+
private clusterGate;
|
|
36
64
|
/**
|
|
37
65
|
* Check whether a workflow-authoring repository is allowed to register or
|
|
38
66
|
* dispatch global workflows. The workflow's own routing key is matched
|
|
39
67
|
* against each entry's optional `routingKey` qualifier.
|
|
40
68
|
*
|
|
41
69
|
* Decision flow:
|
|
42
|
-
* 1.
|
|
43
|
-
* 2. allowed list null/empty → any repo allowed.
|
|
70
|
+
* 1. Cluster master switch off or unreadable → not allowed (fails closed).
|
|
71
|
+
* 2. allowed list null/empty (or no org_settings row) → any repo allowed.
|
|
44
72
|
* 3. Entry matches when its `routingKey` is absent OR equals
|
|
45
73
|
* `workflowRoutingKey`, AND its pattern matches the workflow repo.
|
|
46
74
|
* 4. Otherwise → not allowed.
|
|
@@ -52,18 +80,30 @@ export declare class GlobalWorkflowPolicy {
|
|
|
52
80
|
* (so a source-qualified deny only applies to that specific source).
|
|
53
81
|
*
|
|
54
82
|
* Decision flow:
|
|
55
|
-
* 1.
|
|
56
|
-
* 2. denied list null/empty → allowed.
|
|
83
|
+
* 1. Cluster master switch off or unreadable → not allowed (fails closed).
|
|
84
|
+
* 2. denied list null/empty (or no org_settings row) → allowed.
|
|
57
85
|
* 3. Any deny entry whose routing key (if set) equals the event's routing
|
|
58
86
|
* key AND whose pattern matches the source repo → not allowed.
|
|
59
87
|
*/
|
|
60
88
|
isSourceRepoAllowed(eventRoutingKey: string, sourceRepoIdentifier: string, customerId: string): Promise<GlobalWorkflowPermission>;
|
|
61
89
|
/**
|
|
62
|
-
* Check whether a workflow-authoring repository
|
|
63
|
-
*
|
|
64
|
-
*
|
|
90
|
+
* Check whether a workflow-authoring repository is on the elevated-access
|
|
91
|
+
* list. The workflow's routing key is matched against each entry's optional
|
|
92
|
+
* `routingKey` qualifier. Returns false if no org_settings row or the
|
|
93
|
+
* elevated list is null/empty.
|
|
94
|
+
*
|
|
95
|
+
* @deprecated Not enforced, and not enforceable in this shape. It was meant
|
|
96
|
+
* to gate a global workflow's job reading the *source* repository's secrets,
|
|
97
|
+
* but the organization-wide dispatch path resolves no secrets at all — it
|
|
98
|
+
* binds no secret contexts, and writes no secret material into a job config
|
|
99
|
+
* (asserted by `pipeline/process-webhook-globals-secrets.test.ts`). So there
|
|
100
|
+
* is no injection for a grant to widen, and this method has no caller.
|
|
65
101
|
*
|
|
66
|
-
*
|
|
102
|
+
* Granting it would not be a matter of calling this from the dispatch path:
|
|
103
|
+
* secrets are stored `(org_id, scope, key)` with no repository dimension, so
|
|
104
|
+
* "the source repository's secrets" is not a set the orchestrator can name
|
|
105
|
+
* today. The list, its admin route, its CLI mutators and its wire field are
|
|
106
|
+
* deprecated pending removal at v1.0.0 (`docs/user/deprecations.md`).
|
|
67
107
|
*/
|
|
68
108
|
isElevatedAccessAllowed(workflowRoutingKey: string, repoIdentifier: string, customerId: string): Promise<boolean>;
|
|
69
109
|
private getSettings;
|