@kici-dev/orchestrator 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/__test-helpers__/test-db.d.ts +14 -1
  2. package/dist/agent/dispatcher.d.ts +137 -41
  3. package/dist/agent/registry.d.ts +79 -2
  4. package/dist/app.d.ts +2 -0
  5. package/dist/cancel/cancel-held-run.d.ts +68 -0
  6. package/dist/cancel/cancel-route-answer.d.ts +21 -0
  7. package/dist/cancel/cancel-run.d.ts +32 -0
  8. package/dist/cli/api-client.d.ts +3 -0
  9. package/dist/cli/commands/context.d.ts +3 -1
  10. package/dist/cli/commands/runs.d.ts +7 -0
  11. package/dist/cli/commands/shared/unbound-context-warning.d.ts +43 -0
  12. package/dist/cli/commands/signing-key.d.ts +22 -1
  13. package/dist/cli.js +1306 -739
  14. package/dist/cluster/cluster-settings-reader.d.ts +1 -1
  15. package/dist/cluster/coordinator.d.ts +2 -0
  16. package/dist/cluster/live-coordinator-peer.d.ts +13 -0
  17. package/dist/config.d.ts +9 -0
  18. package/dist/contexts/context-store.d.ts +3 -2
  19. package/dist/contexts/protection/satisfiability.d.ts +6 -3
  20. package/dist/db/migrations/145_execution_runs_workflow_provenance.d.ts +4 -0
  21. package/dist/db/migrations/146_pending_job_contexts_context_resolution.d.ts +4 -0
  22. package/dist/db/migrations/147_sealed_job_secrets.d.ts +19 -0
  23. package/dist/db/migrations/148_cluster_settings_sealed_secrets_retry_backoff_ms.d.ts +21 -0
  24. package/dist/db/migrations/149_workflow_registrations_dep_cache_key.d.ts +4 -0
  25. package/dist/db/migrations/150_execution_runs_status_epoch.d.ts +18 -0
  26. package/dist/db/migrations/151_workflow_registrations_dep_cache_key_sha.d.ts +4 -0
  27. package/dist/db/repos/signing-keys-repo.d.ts +48 -3
  28. package/dist/db/types.d.ts +81 -1
  29. package/dist/git/clone-token-auth.d.ts +7 -0
  30. package/dist/git/dispatch-git-auth.d.ts +63 -0
  31. package/dist/lockfile-validate.d.ts +4 -2
  32. package/dist/metrics/prometheus.d.ts +1 -1
  33. package/dist/oidc/id-token-claims.d.ts +10 -0
  34. package/dist/oidc/oidc-mint-registration.d.ts +2 -0
  35. package/dist/oidc/orchestrator-mint.d.ts +25 -4
  36. package/dist/oidc/reconcile-signing-key.d.ts +31 -6
  37. package/dist/oidc/resolve-signer.d.ts +12 -6
  38. package/dist/pipeline/decorating-secret-resolver.d.ts +5 -3
  39. package/dist/pipeline/dispatch-matched-workflow.d.ts +105 -13
  40. package/dist/pipeline/dispatch-matched-workflow.test-helpers.d.ts +121 -0
  41. package/dist/pipeline/global-dispatch-identity.d.ts +76 -0
  42. package/dist/pipeline/global-dispatch.d.ts +91 -0
  43. package/dist/pipeline/global-eval-round.d.ts +56 -4
  44. package/dist/pipeline/global-round-hold.d.ts +41 -0
  45. package/dist/pipeline/held-context-data.d.ts +143 -0
  46. package/dist/pipeline/held-round-registrations.d.ts +40 -0
  47. package/dist/pipeline/install-gate.d.ts +93 -0
  48. package/dist/pipeline/install-secrets-resolver.d.ts +28 -13
  49. package/dist/pipeline/job-contexts.d.ts +21 -6
  50. package/dist/pipeline/pending-workflow-context.d.ts +15 -3
  51. package/dist/pipeline/process-webhook.d.ts +38 -1
  52. package/dist/pipeline/processor.d.ts +20 -0
  53. package/dist/pipeline/rerun-global.d.ts +28 -0
  54. package/dist/pipeline/rerun-inputs.d.ts +88 -0
  55. package/dist/pipeline/rerun.d.ts +25 -2
  56. package/dist/pipeline/resume-workflow.d.ts +36 -2
  57. package/dist/pipeline/unbound-job-context.d.ts +34 -0
  58. package/dist/pipeline/workflow-lock-at-sha.d.ts +67 -0
  59. package/dist/provenance/backfill-run.d.ts +5 -0
  60. package/dist/providers/github/check-status-poster.d.ts +4 -16
  61. package/dist/queue/job-queue.d.ts +127 -10
  62. package/dist/queue/terminalize-unroutable.d.ts +37 -4
  63. package/dist/registration/registration-index.d.ts +13 -1
  64. package/dist/registration/registration-store.d.ts +10 -1
  65. package/dist/reporting/execution-tracker.d.ts +149 -8
  66. package/dist/reporting/log-chunk-sink.d.ts +1 -1
  67. package/dist/reporting/log-writer.d.ts +14 -0
  68. package/dist/reporting/step-log-path.d.ts +8 -0
  69. package/dist/reporting/step-log-reader.d.ts +21 -3
  70. package/dist/routes/admin-signing-keys.d.ts +33 -0
  71. package/dist/scaler/agent-fit.d.ts +159 -0
  72. package/dist/scaler/container-routing.d.ts +16 -16
  73. package/dist/scaler/manager.d.ts +23 -17
  74. package/dist/secrets/context-secret-resolver.d.ts +1 -1
  75. package/dist/secrets/index.d.ts +1 -1
  76. package/dist/secrets/job-secret-gate.d.ts +25 -3
  77. package/dist/secrets/job-secret-seal.d.ts +36 -0
  78. package/dist/secrets/master-key-rotation.d.ts +7 -3
  79. package/dist/secrets/secret-resolver.d.ts +42 -23
  80. package/dist/server.js +27732 -23477
  81. package/dist/standalone.js +58270 -53199
  82. package/dist/worker/in-memory-job-queue.d.ts +12 -3
  83. package/dist/worker/reroute-dispatch.d.ts +35 -0
  84. package/dist/ws/agent-handler.d.ts +23 -10
  85. package/dist/ws/git-credential-relay.d.ts +16 -3
  86. package/installer-image-digests.json +3 -3
  87. package/package.json +4 -4
  88. package/sbom.spdx.json +53 -53
@@ -3,10 +3,13 @@
3
3
  * one `active` key exists and returns a live `Signer` bound to it.
4
4
  *
5
5
  * - `db` custody: the private key is a random keypair generated + persisted
6
- * (master-key-wrapped) in the DB. Generation is LEADER-GATED so an HA cluster
7
- * never races two active keys; every node then loads the one active row. A
8
- * non-leader that finds no active row yet returns null this tick and is
9
- * retried on the next leadership/boot cycle.
6
+ * (master-key-wrapped) in the DB. Any node with signing enabled may create it
7
+ * when no key of any custody is active; the leader gets the first chance (see
8
+ * `createKeyCreationGate`). The write is an atomic compare-and-activate, so
9
+ * nodes that race converge on the one key that won and every node signs with
10
+ * that key. An active key of another custody kind is never replaced here:
11
+ * the node refuses to sign, and `kici-admin signing-key rotate` is the
12
+ * explicit move to db custody.
10
13
  * - `aws-kms` / `command` custody: the key lives outside KiCI, so every node
11
14
  * resolves the SAME kid and `upsertActive` is idempotent across the fleet.
12
15
  */
@@ -14,10 +17,32 @@ import type { JWK } from 'jose';
14
17
  import { type OrchestratorSignerConfig } from './orchestrator-signer-factory.js';
15
18
  import type { Signer } from './signer.js';
16
19
  import type { OrchestratorSigningKeyRepo } from '../db/repos/signing-keys-repo.js';
20
+ /**
21
+ * How long a non-leader leaves key creation to the Raft leader before it
22
+ * creates the key itself. Kept well under the mint's signer wait so the first
23
+ * mint on a non-leader of a cluster whose leader has signing disabled still
24
+ * gets a token instead of a deferral.
25
+ */
26
+ export declare const NON_LEADER_KEY_CREATE_GRACE_MS = 2000;
27
+ /**
28
+ * Decide whether this node may create the db-custody key right now. The leader
29
+ * always may. A non-leader may once `graceMs` has passed since it first asked,
30
+ * so a cluster whose leader never creates the key (signing disabled on it, or
31
+ * no leader elected) still gets one.
32
+ */
33
+ export declare function createKeyCreationGate(opts: {
34
+ isLeader: () => boolean;
35
+ graceMs: number;
36
+ now?: () => number;
37
+ }): () => boolean;
17
38
  export interface ReconcileSigningKeyDeps {
18
- repo: Pick<OrchestratorSigningKeyRepo, 'getActiveRow' | 'upsertActive'>;
39
+ repo: Pick<OrchestratorSigningKeyRepo, 'getActiveRow' | 'upsertActive' | 'activateIfCurrent'>;
19
40
  config: OrchestratorSignerConfig;
20
- isLeader: () => boolean;
41
+ /**
42
+ * Whether this node may create the db-custody key now that none is usable.
43
+ * Consulted only by `db` custody; see `createKeyCreationGate`.
44
+ */
45
+ mayCreateKey: () => boolean;
21
46
  /** The orchestrator master key (`KICI_SECRET_KEY`), for `db` custody wrapping. */
22
47
  secretKey: string | undefined;
23
48
  /**
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Bounded wait for the orchestrator's provenance signer.
3
3
  *
4
- * A mint that arrives before the leader has generated the key must NOT fall
4
+ * A mint that arrives before any node has generated the key must NOT fall
5
5
  * back to a Platform-signed bundle (it would fail verification against the
6
6
  * orchestrator trust root), so the resolver waits for the key instead of
7
7
  * deferring on the first miss. But the wait exists for ONE condition — a
8
- * reconcile that returns `null` because a non-leader has not yet seen the row
9
- * the leader is about to write. A reconcile that THROWS is a different answer:
8
+ * reconcile that returns `null` because a non-leader is still leaving key
9
+ * creation to the leader. A reconcile that THROWS is a different answer:
10
10
  * a signing key sealed under a master key this process does not hold, or `db`
11
11
  * custody with no master key at all. Neither changes across the wait, so
12
12
  * retrying it for the full window only turns every mint into a silent
@@ -31,10 +31,16 @@ export interface ResolveSignerOptions {
31
31
  /** Injectable for tests. */
32
32
  sleep?: (ms: number) => Promise<void>;
33
33
  }
34
+ /** Per-call override of the resolver's wait. */
35
+ export interface ResolveSignerCallBudget {
36
+ /** Attempts for this call only, in place of the configured `maxAttempts`. */
37
+ maxAttempts: number;
38
+ }
34
39
  /**
35
40
  * Build a memoizing resolver: the first successful reconcile is cached for the
36
- * life of the process, a null reconcile is retried up to `maxAttempts` times,
37
- * and a thrown reconcile ends the call at once with `null`.
41
+ * life of the process, a null reconcile is retried up to `maxAttempts` times
42
+ * (or the call's own budget), and a thrown reconcile ends the call at once with
43
+ * `null`.
38
44
  */
39
- export declare function createBoundedSignerResolver(opts: ResolveSignerOptions): () => Promise<Signer | null>;
45
+ export declare function createBoundedSignerResolver(opts: ResolveSignerOptions): (budget?: ResolveSignerCallBudget) => Promise<Signer | null>;
40
46
  //# sourceMappingURL=resolve-signer.d.ts.map
@@ -12,7 +12,7 @@
12
12
  * wins.
13
13
  */
14
14
  import type { HostFacts } from '@kici-dev/engine';
15
- import type { SecretResolverApi, ResolvedSecretMeta } from '../secrets/secret-resolver.js';
15
+ import type { MatchedContextRef, ResolvedSecretMeta, SecretResolutionAttribution, SecretResolverApi } from '../secrets/secret-resolver.js';
16
16
  /** Decrypted CLI-uploaded local secrets: flat keys + per-context namespaces. */
17
17
  export interface CliSecrets {
18
18
  flat: Record<string, string>;
@@ -21,13 +21,15 @@ export interface CliSecrets {
21
21
  export declare class DecoratingSecretResolver implements SecretResolverApi {
22
22
  private readonly base;
23
23
  private readonly cli;
24
+ /** The wrapped resolver's binding count, when it offers one. */
25
+ readonly countContextBindings?: (contextId: string) => Promise<number>;
24
26
  constructor(base: SecretResolverApi, cli: CliSecrets);
25
- resolveForJob(orgId: string, contextName: string, hostCtx?: HostFacts): Promise<Record<string, string>>;
27
+ resolveForContext(orgId: string, context: MatchedContextRef, hostCtx?: HostFacts, attribution?: SecretResolutionAttribution): Promise<Record<string, string>>;
26
28
  resolveNamedInternal(orgId: string, scope: string, key: string, opts?: {
27
29
  store?: string;
28
30
  runId?: string;
29
31
  jobId?: string;
30
32
  }): Promise<string | null>;
31
- resolveForJobWithMeta(orgId: string, contextName: string, hostCtx?: HostFacts): Promise<Record<string, ResolvedSecretMeta>>;
33
+ resolveForContextWithMeta(orgId: string, context: MatchedContextRef, hostCtx?: HostFacts): Promise<Record<string, ResolvedSecretMeta>>;
32
34
  }
33
35
  //# sourceMappingURL=decorating-secret-resolver.d.ts.map
@@ -16,16 +16,20 @@
16
16
  import { ExecutionJobStatus, InitFailureCategory, CacheRefScope, HoldScope, TriggerSource } from '@kici-dev/engine';
17
17
  import type { LabelMatcher, LockWorkflow, LockJob, ApprovalRequirement, HostTargetSelector, SimulatedEvent, WorkflowDecision, MaterializedJob, ResolvedHostAgent, UpstreamSnapshot, HostFacts } from '@kici-dev/engine';
18
18
  import { type HostRosterStore } from '../agent/host-roster.js';
19
+ import type { RerunLineage } from '../reporting/execution-tracker.js';
19
20
  import { NEEDS_PENDING_JOB_ID_PREFIX } from '../db/synthetic-job-ids.js';
20
21
  import { gatherInvokeResults } from '../orchestrator-core.js';
21
22
  import type { WebhookInfo } from '../webhook/handler.js';
22
23
  import type { ProviderBundle } from '../provider-registry.js';
24
+ import { type GlobalDispatchIdentity } from './global-dispatch-identity.js';
23
25
  import type { Dispatcher } from '../agent/dispatcher.js';
24
26
  import type { QueuedJobInput } from '../queue/job-queue.js';
25
27
  import type { TrustResolution } from '../security/trust-resolver.js';
26
28
  import type { TrustPolicyOutcome } from '../security/trust-policy-gate.js';
27
29
  import type { CreateHeldRunData } from '../contexts/held-runs.js';
30
+ import { type DeferredContextResolution } from './held-context-data.js';
28
31
  import { type NpmRegistrySpec } from './install-secrets-resolver.js';
32
+ import type { InstallGateRecord } from './install-gate.js';
29
33
  import { type ProcessingDeps } from './processor.js';
30
34
  /**
31
35
  * Trusted refs (write+ contributor, default-branch) get the org-shared cache
@@ -60,23 +64,21 @@ export interface WorkflowDispatchContext {
60
64
  payload: unknown;
61
65
  repoIdentifier: string;
62
66
  /**
63
- * The repository that DEFINES the workflow being dispatched. `repoIdentifier`
64
- * is the repository the run acts on; for an organization-wide workflow the
65
- * two are different repositories, and every run row this dispatch writes has
66
- * to say which one defined it.
67
+ * The repository that defines the workflow. Equals `repoIdentifier` unless
68
+ * `global` is set.
67
69
  *
68
70
  * REQUIRED, for the same reason `securityDecision` is: a dispatch path that
69
71
  * does not state it must not compile. Left optional, a new caller omits it
70
72
  * silently and every row it records claims the workflow lives in the
71
73
  * repository the run acted on — a null marker is read as that fact, not as
72
74
  * "unknown" (`registration/registration-run-match.ts`).
73
- *
74
- * Every caller today states `repoIdentifier` or a value equal to it, because
75
- * no cross-repository global dispatch enters this function — the global path
76
- * builds its job inputs directly and dispatches them itself. The recording
77
- * sites narrow, so stating the acted-on repository records nothing.
78
75
  */
79
76
  workflowRepoIdentifier: string;
77
+ /**
78
+ * Set when the workflow is defined in another repository; `repoIdentifier`
79
+ * is then the event's source repository.
80
+ */
81
+ global?: GlobalDispatchIdentity;
80
82
  credentials: Record<string, unknown>;
81
83
  event: SimulatedEvent;
82
84
  eventWithFiles: SimulatedEvent;
@@ -131,6 +133,19 @@ export interface WorkflowDispatchContext {
131
133
  triggeredBy?: string | null;
132
134
  /** Agent provenance label when the run was initiated through an agent credential. */
133
135
  triggeredByAgentLabel?: string | null;
136
+ /**
137
+ * Re-run lineage for `execution_runs.parent_run_id` / `original_run_id`: the
138
+ * run this one re-runs and the root of its re-run chain. Set by the re-run of
139
+ * an organization-wide run; absent for every first run.
140
+ */
141
+ rerunLineage?: RerunLineage;
142
+ /**
143
+ * The trigger event the run's OIDC subject derives from, when it is not the
144
+ * run's own `triggerEvent`. A re-run records `rerun` as its trigger event,
145
+ * which carries no pull-request dimension, so it states the original run's
146
+ * event here to keep the subject a re-run of a fork pull request presents.
147
+ */
148
+ subjectTriggerEvent?: string;
134
149
  /** True only when invoked from the cross-source dispatch shell. */
135
150
  crossSource: boolean;
136
151
  /**
@@ -305,6 +320,14 @@ export interface WorkflowDispatchContext {
305
320
  * exposes them as `ctx.dispatchInputs`. Undefined for webhook runs.
306
321
  */
307
322
  dispatchInputs?: Record<string, unknown>;
323
+ /**
324
+ * What the workflow install gate recorded when it held this dispatch: the
325
+ * contexts that held, which the hold's approval covers, and the ones it
326
+ * admitted. Stored with the held dispatch and read by its release, which
327
+ * gates every context the approval did not cover. Unset on any other
328
+ * dispatch.
329
+ */
330
+ installGateRecord?: InstallGateRecord;
308
331
  }
309
332
  /**
310
333
  * The minimal slice of a dispatch context the needs catch-up + ready-recompute
@@ -443,6 +466,13 @@ interface JobEnvData {
443
466
  serveraddress: string;
444
467
  };
445
468
  held?: boolean;
469
+ /**
470
+ * Set when the job's bound contexts admitted its reject rules: what a later
471
+ * dispatch resolves the context data from when the job is stored as a
472
+ * pending dispatch context (held, waiting on upstream jobs, or outside its
473
+ * wave window) instead of dispatching now. Carries no secret value.
474
+ */
475
+ contextResolution?: DeferredContextResolution;
446
476
  /**
447
477
  * Pending approval hold for this job, set when a context policy or
448
478
  * explicit lock `approval` requires human sign-off. The dispatch loop turns
@@ -795,6 +825,20 @@ export declare function resolveWorkflowDockerfileBuilds(workflow: LockWorkflow,
795
825
  * on the hold rows before giving up and leaving the check unclosable.
796
826
  */
797
827
  export declare const PENDING_CHECK_MARK_ATTEMPTS = 3;
828
+ export declare function postPendingHoldCheck(args: {
829
+ poster: NonNullable<NonNullable<WorkflowDispatchContext['bundle']>['checkStatusPoster']>;
830
+ store: NonNullable<ProcessingDeps['heldRunStore']>;
831
+ orgId: string;
832
+ /** Every hold that gates the one check run this post writes. */
833
+ heldRunIds: readonly string[];
834
+ repoIdentifier: string;
835
+ sha: string;
836
+ summary: string;
837
+ credentials: unknown;
838
+ /** Log fields naming the hold, and the message a failed POST is reported under. */
839
+ logContext: Record<string, unknown>;
840
+ postFailureMessage: string;
841
+ }): Promise<void>;
798
842
  /**
799
843
  * Open the needs gate for any job whose upstreams already reached terminal
800
844
  * before this run's edges existed.
@@ -901,6 +945,12 @@ export interface GeneratedJobConfig {
901
945
  * site below, which is what the credential relay authorizes a request against.
902
946
  */
903
947
  gitCredentials?: Readonly<Record<string, Readonly<Record<string, string>>>>;
948
+ /**
949
+ * What a later dispatch resolves the job's context data from, when the job
950
+ * binds a context. A generated job stored as a pending dispatch context
951
+ * carries no context data at rest.
952
+ */
953
+ contextResolution?: DeferredContextResolution;
904
954
  }
905
955
  /**
906
956
  * Split generated configs into pinned (host-pin dispatch) and unpinned (normal
@@ -912,12 +962,11 @@ export declare function partitionGeneratedConfigsByPin(configs: readonly Generat
912
962
  pinnedConfigs: GeneratedJobConfig[];
913
963
  unpinnedConfigs: GeneratedJobConfig[];
914
964
  };
915
- export declare function resolveGeneratedJobConfigs(args: {
965
+ /** Inputs {@link resolveGeneratedJobConfigs} builds every generated job's config from. */
966
+ interface ResolveGeneratedJobConfigsArgs {
916
967
  ctx: WorkflowDispatchContext;
917
968
  workflow: LockWorkflow;
918
969
  fullLockFile: WorkflowDispatchContext['fullLockFile'];
919
- resolvedSecrets: Record<string, string> | undefined;
920
- resolvedNamespacedSecrets: Record<string, Record<string, string>> | undefined;
921
970
  runPublicKeyBase64: string | undefined;
922
971
  npmRegistries: NpmRegistrySpec[] | undefined;
923
972
  installEnvSecrets: Record<string, string> | undefined;
@@ -925,7 +974,31 @@ export declare function resolveGeneratedJobConfigs(args: {
925
974
  dynamicEntry: BuildPrepResult['dynamicEntries'][number];
926
975
  /** Frozen upstream snapshot threaded into each generated job's dynamicSource for re-eval. */
927
976
  upstreamSnapshot?: UpstreamSnapshot;
928
- }): Promise<GeneratedJobConfig[]>;
977
+ }
978
+ /** A generated job its contexts' protection gates held, with the hold they decided. */
979
+ export interface HeldGeneratedJob {
980
+ config: GeneratedJobConfig;
981
+ envData: JobEnvData;
982
+ }
983
+ /**
984
+ * The generated jobs split by their context gate verdict. A job the gates
985
+ * rejected, or whose resolution threw, appears in neither list: it is recorded
986
+ * on the run as failed.
987
+ */
988
+ export interface GeneratedJobResolution {
989
+ /** Jobs cleared to dispatch, carrying their resolved context data. */
990
+ dispatchable: GeneratedJobConfig[];
991
+ /** Jobs to hold; their context secrets are not resolved. */
992
+ held: HeldGeneratedJob[];
993
+ }
994
+ /**
995
+ * Gate each generated job on its bound contexts and build the job configs.
996
+ *
997
+ * A job the context rules reject is recorded on the run as failed and returned
998
+ * in neither list. A job whose resolution throws is recorded on the run as
999
+ * failed too, and dropped from both lists.
1000
+ */
1001
+ export declare function resolveGeneratedJobConfigs(args: ResolveGeneratedJobConfigsArgs): Promise<GeneratedJobResolution>;
929
1002
  /**
930
1003
  * Recompute the needs gate for `jobNames` and act on whatever became ready:
931
1004
  * open the gate for a satisfied job, terminalize a job an upstream's status
@@ -938,6 +1011,25 @@ export declare function resolveGeneratedJobConfigs(args: {
938
1011
  */
939
1012
  export declare function recomputeAndApplyReady(ctx: NeedsSchedulingContext, jobNames: readonly string[]): Promise<void>;
940
1013
  export { gatherInvokeResults };
1014
+ /**
1015
+ * The `held_runs` row the org trust policy's PR-wide hold writes for `runId`.
1016
+ *
1017
+ * Shared by the per-workflow hold below and the held pre-run evaluation round,
1018
+ * so both land in the same queue and release through the same route.
1019
+ */
1020
+ export declare function buildSecurityHoldData(runId: string, decision: Extract<TrustPolicyOutcome, {
1021
+ action: 'hold';
1022
+ }>): {
1023
+ runId: string;
1024
+ jobId: "__fork_pr__";
1025
+ contextId: null;
1026
+ holdType: "security";
1027
+ queueType: 'security';
1028
+ reason: "fork_pr";
1029
+ expiresAt: Date;
1030
+ scope: "workflow";
1031
+ triggerSource: "context";
1032
+ };
941
1033
  /**
942
1034
  * Dispatch a single matched workflow.
943
1035
  *
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Shared fixtures for the `dispatchMatchedWorkflow` test files.
3
+ *
4
+ * Kept out of any `*.test.ts` file so importing it does not re-register another
5
+ * file's suites in the importer.
6
+ */
7
+ import { type Mock } from 'vitest';
8
+ import type { ProviderType } from '@kici-dev/engine';
9
+ import type { QueuedJobInput } from '../queue/job-queue.js';
10
+ import type { WorkflowDispatchContext } from './dispatch-matched-workflow.js';
11
+ /** Provider of the inbound event every {@link makeSingleJobContext} fixture carries. */
12
+ export declare const TEST_INBOUND_PROVIDER: ProviderType;
13
+ /** Overrides {@link makeSingleJobContext} accepts. */
14
+ export interface SingleJobContextOptions {
15
+ bundle: WorkflowDispatchContext['bundle'];
16
+ fullRepo?: boolean;
17
+ testRun?: {
18
+ fixtureId: string;
19
+ };
20
+ db?: unknown;
21
+ executionTracker?: unknown;
22
+ withBuildInfra?: boolean;
23
+ withBuildMiss?: boolean;
24
+ localWorkingTree?: boolean;
25
+ runWideFlatSecrets?: Record<string, string>;
26
+ jobContext?: string;
27
+ /** Workflow-level context names (`LockWorkflow.contexts`) bound by every job. */
28
+ workflowContexts?: string[];
29
+ secretResolver?: unknown;
30
+ checkMode?: string;
31
+ jobContainer?: unknown;
32
+ jobSandbox?: {
33
+ capabilities?: string[];
34
+ network?: string;
35
+ };
36
+ sandboxAllowListReader?: unknown;
37
+ jobMatrix?: unknown;
38
+ contextConcurrencyLimit?: number | null;
39
+ /** Set wait_timer_seconds on the jobContext row, so its gate returns `wait`. */
40
+ contextWaitTimerSeconds?: number | null;
41
+ heldRunStore?: unknown;
42
+ /** Append a dynamic job fn, so dispatch spawns a deferred dynamic entry. */
43
+ withDynamicEntry?: boolean;
44
+ /** Give the static job a dynamic matrix, so its init is deferred. */
45
+ withDeferredInit?: boolean;
46
+ /** Declare a workflow-level filter, so every job defers to the init round. */
47
+ withFilter?: boolean;
48
+ pendingDynamics?: unknown;
49
+ pendingInits?: unknown;
50
+ /** Give the static job an explicit SDK requireApproval. */
51
+ jobApproval?: unknown;
52
+ /** Give the job a dynamic env, so its init is deferred without a matrix. */
53
+ withDynamicEnv?: boolean;
54
+ /** Bind the job to a DYNAMIC context, resolved only by the init round. */
55
+ withDynamicContext?: boolean;
56
+ /** A contextStore stub used verbatim (overrides the jobContext-derived one). */
57
+ contextStore?: unknown;
58
+ /**
59
+ * A checkRunReporter stub, so a case can observe the queued check runs the
60
+ * dispatch setup phase creates against the commit.
61
+ */
62
+ checkRunReporter?: unknown;
63
+ }
64
+ /**
65
+ * A context row named `name` with no rules, always enabled; `rowOver` replaces
66
+ * any column.
67
+ */
68
+ export declare function makeJobContextRow(name: string, over: SingleJobContextOptions, rowOver?: Record<string, unknown>): {
69
+ id: string;
70
+ org_id: string;
71
+ name: string;
72
+ type: string;
73
+ glob_pattern: null;
74
+ branch_restrictions: null;
75
+ trigger_type_filters: null;
76
+ repo_patterns: null;
77
+ concurrency_limit: number | null;
78
+ concurrency_strategy: null;
79
+ concurrency_timeout_ms: null;
80
+ required_reviewers: null;
81
+ wait_timer_seconds: number | null;
82
+ hold_expiry_seconds: null;
83
+ minimum_trust: null;
84
+ allow_local_execution: boolean;
85
+ enabled: boolean;
86
+ created_at: Date;
87
+ updated_at: Date;
88
+ created_by: null;
89
+ };
90
+ /**
91
+ * Assemble a minimal real `WorkflowDispatchContext` for a single static job,
92
+ * with a capturing dispatcher and all optional deps absent unless overridden.
93
+ * This is the test-mode shape: `bundle` may be undefined and `trustResolution`
94
+ * is undefined (single-orch, no holds, no trust).
95
+ */
96
+ export declare function makeSingleJobContext(over: SingleJobContextOptions): {
97
+ ctx: WorkflowDispatchContext;
98
+ dispatched: QueuedJobInput[];
99
+ };
100
+ /** An execution-tracker stub covering the calls a gated dispatch makes. */
101
+ export interface GateTracker {
102
+ addJobsToRun: Mock;
103
+ onExecutionStarted: Mock;
104
+ onJobStatus: Mock;
105
+ holdRunForPendingJobs: Mock;
106
+ releasePendingJobsHold: Mock;
107
+ recordInitFailureRun: Mock;
108
+ }
109
+ /**
110
+ * Resolve once every background task a dispatch started has finished. Each task
111
+ * (a dynamic entry, a deferred init job) takes a pending-jobs token before
112
+ * `dispatchMatchedWorkflow` returns and gives it back when it settles, so the
113
+ * two counts match only when none is still running. A negative assertion made
114
+ * after this cannot pass by running before the task reached its decision.
115
+ */
116
+ export declare function awaitDispatchTasksSettled(tracker: GateTracker): Promise<void>;
117
+ /** A fresh {@link GateTracker}. */
118
+ export declare function makeGateTracker(): GateTracker;
119
+ /** Enough Kysely surface for the hold path: counts, upserts, and one transaction. */
120
+ export declare function makeHoldDb(): Record<string, unknown>;
121
+ //# sourceMappingURL=dispatch-matched-workflow.test-helpers.d.ts.map
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The workflow-repository identity of a dispatch whose workflow is defined in
3
+ * another repository (a global workflow), and the seams that decide which
4
+ * repository each dispatch concern reads.
5
+ *
6
+ * Policy — contexts, install secrets, the user-cache namespace — follows the
7
+ * repository that defines the workflow. Checkout and the event stay with the
8
+ * repository the event came from. For a same-repository dispatch every helper
9
+ * collapses to the event's own values.
10
+ */
11
+ import type { ProviderBundle } from '../provider-registry.js';
12
+ import type { WorkflowRepoProvenance } from '../reporting/execution-tracker.js';
13
+ /** The workflow repository of a run whose workflow is defined in another repository. */
14
+ export interface GlobalDispatchIdentity {
15
+ /** `owner/repo` that defines the workflow. */
16
+ readonly workflowRepoIdentifier: string;
17
+ /** Commit of the registration the run executes. `null` when the registration predates commit tracking. */
18
+ readonly workflowSha: string | null;
19
+ /** Branch the registration was taken from (`workflow_registrations.default_branch`). */
20
+ readonly workflowBranch: string | null;
21
+ /** Routing key of the source that registered the workflow repository. */
22
+ readonly workflowRoutingKey: string;
23
+ /** Provider context (e.g. the installation) the workflow repository's credentials are minted with. */
24
+ readonly workflowProviderContext: Record<string, unknown>;
25
+ /** The workflow repository's provider bundle (may differ from the inbound one). */
26
+ readonly workflowBundle: ProviderBundle | undefined;
27
+ /** Credentials minted for the workflow repository. */
28
+ readonly workflowCredentials: Record<string, unknown>;
29
+ }
30
+ interface IdentityCtx {
31
+ repoIdentifier: string;
32
+ event: {
33
+ targetBranch: string;
34
+ };
35
+ global?: GlobalDispatchIdentity;
36
+ }
37
+ /** Repository whose contexts, install secrets and lock the run uses. */
38
+ export declare function policyRepo(ctx: IdentityCtx): string;
39
+ /** Branch context branch rules check. A global run presents its registration's branch; `''` matches no rule. */
40
+ export declare function policyBranch(ctx: IdentityCtx): string;
41
+ /** User-cache namespace. A global run gets its own (workflow repo, source repo) namespace. */
42
+ export declare function cacheRepoIdFor(ctx: IdentityCtx): string;
43
+ /**
44
+ * Routing key whose per-source variable overrides layer over a context's
45
+ * variables. A global run reads the workflow repository's source, so the
46
+ * event's source cannot override the variables of a context it does not own.
47
+ */
48
+ export declare function policyRoutingKey(ctx: {
49
+ info: {
50
+ routingKey: string;
51
+ };
52
+ global?: GlobalDispatchIdentity;
53
+ }): string;
54
+ /** Provenance recorded on the run row; `undefined` for a same-repo run. */
55
+ export declare function workflowRepoProvenance(ctx: IdentityCtx): WorkflowRepoProvenance | undefined;
56
+ /**
57
+ * The dual-checkout job-config fields of a dispatch, with the workflow
58
+ * repository's clone URL built by its own provider bundle. Empty for a
59
+ * same-repository dispatch. Throws when the workflow bundle cannot build a
60
+ * clone URL: a global job without a workflow-repository URL cannot run.
61
+ */
62
+ export declare function globalJobConfigFor(ctx: IdentityCtx): Record<string, unknown>;
63
+ /** Job-config fields the agent uses for the dual checkout. */
64
+ export declare function globalJobConfigFields(ctx: IdentityCtx, workflowRepoUrl: string): Record<string, unknown>;
65
+ /**
66
+ * The part of a {@link GlobalDispatchIdentity} a held run stores: everything
67
+ * but the live provider bundle and the minted credentials, both of which a
68
+ * resume re-derives from `workflowRoutingKey`. The commit, branch and provider
69
+ * context are the ones captured when the run was held, so a resumed run
70
+ * executes the workflow commit it was held at, not the live registration's.
71
+ */
72
+ export type SerializableGlobalDispatchIdentity = Omit<GlobalDispatchIdentity, 'workflowBundle' | 'workflowCredentials'>;
73
+ /** The stored form of a global identity: its fields named one by one, so no live object or token rides along. */
74
+ export declare function toSerializableGlobalIdentity(g: GlobalDispatchIdentity): SerializableGlobalDispatchIdentity;
75
+ export {};
76
+ //# sourceMappingURL=global-dispatch-identity.d.ts.map
@@ -0,0 +1,91 @@
1
+ import type { LockJobOrFactory, LockWorkflow, SimulatedEvent } from '@kici-dev/engine';
2
+ import type { WebhookInfo } from '../webhook/handler.js';
3
+ import type { ProviderBundle } from '../provider-registry.js';
4
+ import type { RegisteredWorkflow } from '../registration/registration-index.js';
5
+ import type { TrustResolution } from '../security/trust-resolver.js';
6
+ import type { TrustPolicyOutcome } from '../security/trust-policy-gate.js';
7
+ import { type WorkflowDispatchContext } from './dispatch-matched-workflow.js';
8
+ import type { GlobalEvalCandidate } from './global-eval-round.js';
9
+ import type { ProcessingDeps } from './processor.js';
10
+ /**
11
+ * A global candidate handed to the pipeline, with the exact job set to dispatch.
12
+ *
13
+ * A round-cleared candidate carries its static jobs, the jobs the round
14
+ * generated, and its result-aware `DynamicJobFn` entries, which the pipeline
15
+ * evaluates on its deferred path once their upstreams complete
16
+ * (`RoundClearedCandidate`). A candidate the event's trust verdict settles
17
+ * before any round carries its lock-file job list as declared, `DynamicJobFn`
18
+ * entries included: the pipeline's trust gate rejects or ignores it before any
19
+ * job is expanded.
20
+ */
21
+ export interface ResolvedGlobalCandidate {
22
+ candidate: GlobalEvalCandidate;
23
+ jobs: readonly LockJobOrFactory[];
24
+ }
25
+ /**
26
+ * A candidate the evaluation round cleared, or one that never needed the round.
27
+ * Its only `DynamicJobFn` entries are result-aware ones: every needs-free
28
+ * generator was already expanded by the round.
29
+ */
30
+ export type RoundClearedCandidate = ResolvedGlobalCandidate;
31
+ /** Inputs of one global candidate's dispatch. */
32
+ export interface GlobalCandidateDispatchArgs {
33
+ info: WebhookInfo;
34
+ deps: ProcessingDeps;
35
+ payload: unknown;
36
+ /**
37
+ * The bundle that checks out the source repository and posts its checks.
38
+ * The inbound event's bundle, or another source's when a cross-provider
39
+ * lock-file fallback resolved one.
40
+ */
41
+ sourceBundle: ProviderBundle;
42
+ /** Routing key `sourceBundle` belongs to, when it is not `info.routingKey`. */
43
+ sourceRoutingKey?: string;
44
+ sourceRepoIdentifier: string;
45
+ /** Credentials of `sourceBundle` for the source repository. */
46
+ sourceCredentials: Record<string, unknown>;
47
+ event: SimulatedEvent;
48
+ eventWithFiles: SimulatedEvent;
49
+ /** The event's commit SHA. */
50
+ ref: string;
51
+ resolvedOrgId: string;
52
+ /** The event's trust resolution, recorded on the run and read by `minimumTrust`. */
53
+ trustResolution: TrustResolution | undefined;
54
+ /** The event's trust-policy verdict; a `hold` holds the global run. */
55
+ securityDecision: TrustPolicyOutcome;
56
+ /** The candidate and its final job list (static and round-generated entries). */
57
+ resolved: ResolvedGlobalCandidate;
58
+ }
59
+ /**
60
+ * Credentials for the workflow repository, minted by its own provider bundle
61
+ * with the registration's provider context. Throws when the mint fails.
62
+ */
63
+ export declare function mintWorkflowRepoCredentials(workflowBundle: ProviderBundle, reg: Pick<RegisteredWorkflow, 'repoIdentifier' | 'providerContext'>): Promise<Record<string, unknown>>;
64
+ /**
65
+ * A global workflow's lock entry with `jobs` as the job list to dispatch.
66
+ *
67
+ * The workflow `filter` was already decided before the run existed — by the
68
+ * eval round, or by the run a re-run repeats — so it is dropped here. Otherwise
69
+ * every job would defer to an init job that evaluates it a second time.
70
+ */
71
+ export declare function dispatchableGlobalWorkflow(lockEntry: LockWorkflow, jobs: readonly LockJobOrFactory[]): LockWorkflow;
72
+ /**
73
+ * The lock file a global run hands the pipeline: the one workflow it runs, with
74
+ * the dependency-cache key of the lock file that entry came from.
75
+ *
76
+ * The key is what the pipeline probes the dependency cache with, and what the
77
+ * build job publishes a tarball under, so a global run shares cache entries
78
+ * with every other run of the same lock. `reg` must describe the lock file
79
+ * `workflow` came from: a caller that read the entry at another commit passes
80
+ * that commit's key. A registration that recorded no key installs its
81
+ * dependencies on the agent.
82
+ */
83
+ export declare function globalRunLockFile(workflow: LockWorkflow, reg: Pick<RegisteredWorkflow, 'sourceFile' | 'lockfileHash' | 'siblingsDigest'>): WorkflowDispatchContext['fullLockFile'];
84
+ /**
85
+ * Dispatches one global candidate through the shared pipeline. Returns the run
86
+ * id, or undefined when no run was created: the workflow repository has no
87
+ * provider bundle, its credentials cannot be minted, or the candidate has no
88
+ * job to dispatch.
89
+ */
90
+ export declare function dispatchGlobalCandidateViaPipeline(args: GlobalCandidateDispatchArgs): Promise<string | undefined>;
91
+ //# sourceMappingURL=global-dispatch.d.ts.map