@kici-dev/orchestrator 0.9.1 → 0.11.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 (101) 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 +23 -1
  9. package/dist/cli/commands/attestations.d.ts +10 -0
  10. package/dist/cli/commands/cold-store-impl.d.ts +3 -3
  11. package/dist/cli/commands/cold-store.d.ts +6 -3
  12. package/dist/cli/commands/context.d.ts +6 -2
  13. package/dist/cli/commands/dashboard-encryption-key.d.ts +18 -0
  14. package/dist/cli/commands/event-dlq.d.ts +2 -2
  15. package/dist/cli/commands/registration.d.ts +8 -4
  16. package/dist/cli/commands/runs.d.ts +7 -0
  17. package/dist/cli/commands/secret.d.ts +1 -0
  18. package/dist/cli/commands/shared/confirm.d.ts +19 -0
  19. package/dist/cli/commands/shared/unbound-context-warning.d.ts +43 -0
  20. package/dist/cli/commands/signing-key.d.ts +22 -1
  21. package/dist/cli.js +1522 -871
  22. package/dist/cluster/cluster-settings-reader.d.ts +1 -1
  23. package/dist/cluster/coordinator.d.ts +2 -0
  24. package/dist/cluster/live-coordinator-peer.d.ts +13 -0
  25. package/dist/config.d.ts +9 -0
  26. package/dist/contexts/context-store.d.ts +3 -2
  27. package/dist/contexts/protection/satisfiability.d.ts +6 -3
  28. package/dist/contexts/variable-store.d.ts +2 -0
  29. package/dist/db/migrations/145_execution_runs_workflow_provenance.d.ts +4 -0
  30. package/dist/db/migrations/146_pending_job_contexts_context_resolution.d.ts +4 -0
  31. package/dist/db/migrations/147_sealed_job_secrets.d.ts +19 -0
  32. package/dist/db/migrations/148_cluster_settings_sealed_secrets_retry_backoff_ms.d.ts +21 -0
  33. package/dist/db/migrations/149_workflow_registrations_dep_cache_key.d.ts +4 -0
  34. package/dist/db/migrations/150_execution_runs_status_epoch.d.ts +18 -0
  35. package/dist/db/migrations/151_workflow_registrations_dep_cache_key_sha.d.ts +4 -0
  36. package/dist/db/repos/signing-keys-repo.d.ts +48 -3
  37. package/dist/db/types.d.ts +81 -1
  38. package/dist/git/clone-token-auth.d.ts +7 -0
  39. package/dist/git/dispatch-git-auth.d.ts +63 -0
  40. package/dist/index.js +26 -0
  41. package/dist/lockfile-validate.d.ts +4 -2
  42. package/dist/metrics/prometheus.d.ts +1 -1
  43. package/dist/oidc/id-token-claims.d.ts +10 -0
  44. package/dist/oidc/oidc-mint-registration.d.ts +2 -0
  45. package/dist/oidc/orchestrator-mint.d.ts +25 -4
  46. package/dist/oidc/reconcile-signing-key.d.ts +31 -6
  47. package/dist/oidc/resolve-signer.d.ts +12 -6
  48. package/dist/pipeline/decorating-secret-resolver.d.ts +5 -3
  49. package/dist/pipeline/dispatch-matched-workflow.d.ts +105 -13
  50. package/dist/pipeline/dispatch-matched-workflow.test-helpers.d.ts +121 -0
  51. package/dist/pipeline/global-dispatch-identity.d.ts +76 -0
  52. package/dist/pipeline/global-dispatch.d.ts +91 -0
  53. package/dist/pipeline/global-eval-round.d.ts +56 -4
  54. package/dist/pipeline/global-round-hold.d.ts +41 -0
  55. package/dist/pipeline/held-context-data.d.ts +143 -0
  56. package/dist/pipeline/held-round-registrations.d.ts +40 -0
  57. package/dist/pipeline/install-gate.d.ts +93 -0
  58. package/dist/pipeline/install-secrets-resolver.d.ts +28 -13
  59. package/dist/pipeline/job-contexts.d.ts +21 -6
  60. package/dist/pipeline/pending-workflow-context.d.ts +15 -3
  61. package/dist/pipeline/process-webhook.d.ts +38 -1
  62. package/dist/pipeline/processor.d.ts +20 -0
  63. package/dist/pipeline/rerun-global.d.ts +28 -0
  64. package/dist/pipeline/rerun-inputs.d.ts +88 -0
  65. package/dist/pipeline/rerun.d.ts +25 -2
  66. package/dist/pipeline/resume-workflow.d.ts +36 -2
  67. package/dist/pipeline/unbound-job-context.d.ts +34 -0
  68. package/dist/pipeline/workflow-lock-at-sha.d.ts +67 -0
  69. package/dist/provenance/backfill-run.d.ts +5 -0
  70. package/dist/providers/github/check-status-poster.d.ts +4 -16
  71. package/dist/queue/job-queue.d.ts +127 -10
  72. package/dist/queue/terminalize-unroutable.d.ts +37 -4
  73. package/dist/registration/registration-index.d.ts +13 -1
  74. package/dist/registration/registration-store.d.ts +10 -1
  75. package/dist/reporting/execution-tracker.d.ts +149 -8
  76. package/dist/reporting/log-chunk-sink.d.ts +1 -1
  77. package/dist/reporting/log-writer.d.ts +14 -0
  78. package/dist/reporting/step-log-path.d.ts +8 -0
  79. package/dist/reporting/step-log-reader.d.ts +21 -3
  80. package/dist/routes/admin-contexts.d.ts +6 -0
  81. package/dist/routes/admin-registrations.d.ts +2 -2
  82. package/dist/routes/admin-signing-keys.d.ts +33 -0
  83. package/dist/scaler/agent-fit.d.ts +159 -0
  84. package/dist/scaler/container-routing.d.ts +16 -16
  85. package/dist/scaler/manager.d.ts +23 -17
  86. package/dist/secrets/audit-logger.d.ts +1 -1
  87. package/dist/secrets/context-secret-resolver.d.ts +1 -1
  88. package/dist/secrets/index.d.ts +1 -1
  89. package/dist/secrets/job-secret-gate.d.ts +25 -3
  90. package/dist/secrets/job-secret-seal.d.ts +36 -0
  91. package/dist/secrets/master-key-rotation.d.ts +7 -3
  92. package/dist/secrets/secret-resolver.d.ts +42 -23
  93. package/dist/server.js +27611 -23219
  94. package/dist/standalone.js +58327 -53119
  95. package/dist/worker/in-memory-job-queue.d.ts +12 -3
  96. package/dist/worker/reroute-dispatch.d.ts +35 -0
  97. package/dist/ws/agent-handler.d.ts +23 -10
  98. package/dist/ws/git-credential-relay.d.ts +16 -3
  99. package/installer-image-digests.json +3 -3
  100. package/package.json +4 -4
  101. package/sbom.spdx.json +53 -53
@@ -1,7 +1,7 @@
1
1
  import type { Kysely } from 'kysely';
2
2
  import type { Database } from '../db/types.js';
3
3
  /** Numeric columns on cluster_settings readable via {@link ClusterSettingsReader}. */
4
- export type ClusterNumberColumn = 'max_github_payload_bytes' | 'event_log_max_payload_bytes' | 'lock_file_max_bytes' | 'webhook_dedup_ttl_ms' | 'event_router_event_ttl_seconds' | 'event_router_max_dispatch_attempts' | 'queue_max_depth' | 'reroute_flap_grace_ms' | 'max_fanout_hosts' | 'event_router_rate_limit_per_workflow_per_minute' | 'cache_max_tarball_bytes' | 'cache_ttl_days' | 'lockfile_cache_max' | 'lockfile_cache_max_bytes' | 'lockfile_cache_ttl_ms' | 'content_cache_max' | 'content_cache_max_bytes' | 'content_cache_ttl_ms' | 'global_eval_round_timeout_ms' | 'global_eval_candidate_timeout_ms' | 'global_eval_cache_max' | 'global_eval_wait_timeout_ms' | 'check_run_tracking_ttl_days' | 'run_retention_days' | 'audit_retention_days' | 'provenance_retention_days' | 'held_run_retention_days' | 'concurrency_wait_timeout_ms' | 'agent_token_ttl_ms' | 'ownership_db_check_timeout_ms' | 'unroutable_grace_ms' | 'ingest_overflow_claim_timeout_ms' | 'scaler_reap_interval_ms' | 'scaler_reap_stranded_timeout_ms' | 'scaler_reap_reattempt_interval_ms' | 'scaler_claim_retention_ms' | 'scaler_provision_backoff_base_ms' | 'scaler_provision_backoff_max_ms' | 'scaler_provision_max_consecutive_failures';
4
+ export type ClusterNumberColumn = 'max_github_payload_bytes' | 'event_log_max_payload_bytes' | 'lock_file_max_bytes' | 'webhook_dedup_ttl_ms' | 'event_router_event_ttl_seconds' | 'event_router_max_dispatch_attempts' | 'queue_max_depth' | 'reroute_flap_grace_ms' | 'max_fanout_hosts' | 'event_router_rate_limit_per_workflow_per_minute' | 'cache_max_tarball_bytes' | 'cache_ttl_days' | 'lockfile_cache_max' | 'lockfile_cache_max_bytes' | 'lockfile_cache_ttl_ms' | 'content_cache_max' | 'content_cache_max_bytes' | 'content_cache_ttl_ms' | 'global_eval_round_timeout_ms' | 'global_eval_candidate_timeout_ms' | 'global_eval_cache_max' | 'global_eval_wait_timeout_ms' | 'check_run_tracking_ttl_days' | 'run_retention_days' | 'audit_retention_days' | 'provenance_retention_days' | 'held_run_retention_days' | 'concurrency_wait_timeout_ms' | 'agent_token_ttl_ms' | 'ownership_db_check_timeout_ms' | 'sealed_secrets_retry_backoff_ms' | 'unroutable_grace_ms' | 'ingest_overflow_claim_timeout_ms' | 'scaler_reap_interval_ms' | 'scaler_reap_stranded_timeout_ms' | 'scaler_reap_reattempt_interval_ms' | 'scaler_claim_retention_ms' | 'scaler_provision_backoff_base_ms' | 'scaler_provision_backoff_max_ms' | 'scaler_provision_max_consecutive_failures';
5
5
  /** Text columns on cluster_settings readable via {@link ClusterSettingsReader}. */
6
6
  export type ClusterStringColumn = 'dashboard_verified_issuer';
7
7
  /** Boolean columns on cluster_settings readable via {@link ClusterSettingsReader}. */
@@ -33,6 +33,8 @@ export interface RunContext {
33
33
  traceId?: string;
34
34
  /** Pre-resolved clone token for workers without provider credentials. */
35
35
  cloneToken?: string;
36
+ /** Pre-resolved clone token for the workflow repository of an organization-wide job. */
37
+ workflowCloneToken?: string;
36
38
  }
37
39
  export interface JobToRoute {
38
40
  jobName: string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Whether this coordinator has a live coordinator peer.
3
+ *
4
+ * `PeerRegistry` flips `connected` the instant a peer WS closes, with no grace
5
+ * of its own, so sampling it alone reads a link blip between two healthy
6
+ * coordinators as a dead peer. That is the same hazard `shouldDeferReroutedJob`
7
+ * and `adopterIsLive` guard against, and it is answered the same way: a
8
+ * coordinator peer counts while it is connected or was last heard from within
9
+ * the flap grace. Worker peers never count: they do not drain the shared queue.
10
+ */
11
+ import type { PeerRegistry } from './peer-registry.js';
12
+ export declare function hasLiveCoordinatorPeer(peers: Pick<PeerRegistry, 'getCoordinatorPeers'>, nowMs: number, flapGraceMs: number): boolean;
13
+ //# sourceMappingURL=live-coordinator-peer.d.ts.map
package/dist/config.d.ts CHANGED
@@ -15,6 +15,12 @@
15
15
  * and PgSecretStore, not through config. See SourceStore and SourceManager.
16
16
  */
17
17
  import { z } from 'zod';
18
+ /**
19
+ * Default for `sealed_secrets_retry_backoff_ms`: how long a coordinator stops
20
+ * offering a queued job it put back because it could not open its seal. The
21
+ * config default below and the job queue's own fallback both read this.
22
+ */
23
+ export declare const DEFAULT_SEALED_SECRETS_RETRY_BACKOFF_MS = 60000;
18
24
  /**
19
25
  * Validation scope for orchestrator config: a running orchestrator (`runtime`)
20
26
  * vs a build-artifact (agent packaging) operation (`packaging`). Packaging only
@@ -73,6 +79,7 @@ declare const configSchema: z.ZodObject<{
73
79
  concurrencyWaitTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
74
80
  dispatchAckTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
75
81
  ownershipDbCheckTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
82
+ sealedSecretsRetryBackoffMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
76
83
  ingestMaxConcurrency: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
77
84
  ingestMaxQueueDepth: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
78
85
  ingestCodelTargetMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -282,6 +289,7 @@ export declare const packagingConfigSchema: z.ZodObject<{
282
289
  concurrencyWaitTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
283
290
  dispatchAckTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
284
291
  ownershipDbCheckTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
292
+ sealedSecretsRetryBackoffMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
285
293
  ingestMaxConcurrency: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
286
294
  ingestMaxQueueDepth: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
287
295
  ingestCodelTargetMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -524,6 +532,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
524
532
  concurrencyWaitTimeoutMs: number;
525
533
  dispatchAckTimeoutMs: number;
526
534
  ownershipDbCheckTimeoutMs: number;
535
+ sealedSecretsRetryBackoffMs: number;
527
536
  ingestMaxConcurrency: number;
528
537
  ingestMaxQueueDepth: number;
529
538
  ingestCodelTargetMs: number;
@@ -1,4 +1,5 @@
1
1
  import { type Kysely } from 'kysely';
2
+ import { ContextType } from '@kici-dev/engine';
2
3
  import type { ConcurrencyStrategy, Context as EngineContext, MinimumTrust } from '@kici-dev/engine';
3
4
  import type { Database, Context } from '../db/types.js';
4
5
  /** Thrown by `delete` when pending held runs reference the context. */
@@ -15,7 +16,7 @@ export declare function toContext(row: Context): EngineContext;
15
16
  /** Fields accepted when creating a context. */
16
17
  export interface ContextCreateInput {
17
18
  name: string;
18
- type?: 'fixed' | 'glob';
19
+ type?: ContextType;
19
20
  globPattern?: string | null;
20
21
  branchRestrictions?: string[];
21
22
  triggerTypeFilters?: string[];
@@ -44,7 +45,7 @@ export interface ContextCreateInput {
44
45
  */
45
46
  export interface ContextUpdateInput {
46
47
  name?: string;
47
- type?: 'fixed' | 'glob';
48
+ type?: ContextType;
48
49
  globPattern?: string | null;
49
50
  branchRestrictions?: string[] | null;
50
51
  triggerTypeFilters?: string[];
@@ -52,13 +52,16 @@ export interface UnsatisfiableBinding {
52
52
  export declare function checkBindingSatisfiable(jobName: string, envs: ReadonlyArray<Context | undefined>, envNames: readonly string[]): UnsatisfiableBinding | null;
53
53
  /** Minimal lock-workflow shape needed to walk its jobs for satisfiability. */
54
54
  interface SatisfiabilityLockWorkflow {
55
+ /** Workflow-level context names, bound by every job before its own. */
56
+ contexts?: readonly unknown[];
55
57
  jobs?: readonly unknown[];
56
58
  }
57
59
  /**
58
60
  * Walk every workflow's static jobs and reject the registration when a bound
59
- * context list is provably unsatisfiable (a disabled context, or
60
- * mutually-exclusive fixed branch/trigger/repo restrictions among the resolved
61
- * contexts — missing names are lenient, never rejected). Dynamic elements
61
+ * context list — the workflow-level names plus the job's own — is provably
62
+ * unsatisfiable (a disabled context, or mutually-exclusive fixed
63
+ * branch/trigger/repo restrictions among the resolved contexts — missing names
64
+ * are lenient, never rejected). Dynamic elements
62
65
  * are skipped (unresolvable at registration); the all-must-pass semantics keep
63
66
  * the static subset's exclusivity sound. Throws the first
64
67
  * `UnsatisfiableBinding.message` so the registration route / direct helper
@@ -20,6 +20,8 @@ export declare class VariableStore {
20
20
  deleteVar(orgId: string, contextId: string, key: string): Promise<void>;
21
21
  /** List source overrides for a specific source (routing key). */
22
22
  listSourceOverrides(orgId: string, contextId: string, routingKey: string): Promise<ContextSourceOverride[]>;
23
+ /** List every source override in a context, across all routing keys. */
24
+ listAllSourceOverrides(orgId: string, contextId: string): Promise<ContextSourceOverride[]>;
23
25
  /** Upsert a source override. */
24
26
  setSourceOverride(orgId: string, contextId: string, routingKey: string, key: string, value: string): Promise<void>;
25
27
  /** Delete a source override. */
@@ -0,0 +1,4 @@
1
+ import { type Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ export declare function down(db: Kysely<unknown>): Promise<void>;
4
+ //# sourceMappingURL=145_execution_runs_workflow_provenance.d.ts.map
@@ -0,0 +1,4 @@
1
+ import { type Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ export declare function down(db: Kysely<unknown>): Promise<void>;
4
+ //# sourceMappingURL=146_pending_job_contexts_context_resolution.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Hold the secret fields of a stored job encrypted under the orchestrator
4
+ * master key, beside the plain JSON column that keeps every other field.
5
+ *
6
+ * Adds a nullable `sealed_secrets text` column to the three tables a job waits
7
+ * in: `dispatch_queue` (its `job_config`), `pending_job_contexts` (its
8
+ * `job_input`), and `pending_workflow_contexts` (its `context`).
9
+ *
10
+ * Nullable and unbackfilled: NULL on every row written before this column
11
+ * existed, and on every row an orchestrator without a master key writes. Such
12
+ * a row keeps its secret fields in the plain column and reads back unchanged.
13
+ *
14
+ * Idempotent: each column is added only when missing.
15
+ */
16
+ export declare const SEALED_SECRETS_TABLES: readonly ['dispatch_queue', 'pending_job_contexts', 'pending_workflow_contexts'];
17
+ export declare function up(db: Kysely<unknown>): Promise<void>;
18
+ export declare function down(db: Kysely<unknown>): Promise<void>;
19
+ //# sourceMappingURL=147_sealed_job_secrets.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add `cluster_settings.sealed_secrets_retry_backoff_ms` (nullable bigint).
4
+ *
5
+ * How long a coordinator leaves a queued job alone after it claimed the job and
6
+ * could not open its sealed secrets: the job was sealed with a master key this
7
+ * coordinator does not hold. During a rolling key rotation a coordinator that
8
+ * holds the key can take the job in that window; after it, this coordinator can
9
+ * claim the job again, and each claim spends another dispatch attempt, so a job
10
+ * no coordinator can open still fails.
11
+ *
12
+ * Cluster-global (every coordinator drains the same queue), so it lives on
13
+ * `cluster_settings` rather than `org_settings`. NULL ⇒ the orchestrator's own
14
+ * configured default applies.
15
+ *
16
+ * Idempotent: guarded on column existence; a re-run on a DB that already has the
17
+ * column is a no-op.
18
+ */
19
+ export declare function up(db: Kysely<unknown>): Promise<void>;
20
+ export declare function down(db: Kysely<unknown>): Promise<void>;
21
+ //# sourceMappingURL=148_cluster_settings_sealed_secrets_retry_backoff_ms.d.ts.map
@@ -0,0 +1,4 @@
1
+ import { type Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ export declare function down(db: Kysely<unknown>): Promise<void>;
4
+ //# sourceMappingURL=149_workflow_registrations_dep_cache_key.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add `execution_runs.status_epoch` (integer, NOT NULL, default 0).
4
+ *
5
+ * The run's status generation. It starts at 0, and the tracker raises it each
6
+ * time a run leaves a terminal status: a run failed before a job it had already
7
+ * dispatched reported in, and the run continues. Every `execution.status` frame
8
+ * carries it, so the Platform can keep a finished run finished against a
9
+ * non-terminal frame that arrives late, and still follow a real reopen.
10
+ *
11
+ * A constant default adds the column without rewriting the table, and every
12
+ * existing row reads 0: none of them was reopened under this counter.
13
+ *
14
+ * Idempotent: guarded on the column's existence, so re-running is a no-op.
15
+ */
16
+ export declare function up(db: Kysely<unknown>): Promise<void>;
17
+ export declare function down(db: Kysely<unknown>): Promise<void>;
18
+ //# sourceMappingURL=150_execution_runs_status_epoch.d.ts.map
@@ -0,0 +1,4 @@
1
+ import { type Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ export declare function down(db: Kysely<unknown>): Promise<void>;
4
+ //# sourceMappingURL=151_workflow_registrations_dep_cache_key_sha.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { Kysely } from 'kysely';
1
+ import { type Kysely } from 'kysely';
2
2
  import type { Database, OrchestratorSigningKeyRow } from '../types.js';
3
3
  export interface UpsertActiveInput {
4
4
  kid: string;
@@ -9,6 +9,31 @@ export interface UpsertActiveInput {
9
9
  signer_kind: string;
10
10
  key_ref: string | null;
11
11
  }
12
+ /**
13
+ * Advisory-lock name taken (`pg_advisory_xact_lock(hashtext(...))`) by every
14
+ * writer that changes which key is `active` or revokes a key: `activateIfCurrent`,
15
+ * `upsertActive` and `revoke`. Holding it for the whole transaction makes
16
+ * "read the active key, then write" atomic across orchestrator nodes sharing the
17
+ * database, and orders a revoke against a key switch.
18
+ */
19
+ export declare const ACTIVE_SIGNING_KEY_LOCK = "orchestrator-signing-key-active";
20
+ /**
21
+ * The columns a key listing may expose: identity, lifecycle and custody
22
+ * metadata. Never the public JWK or the wrapped private key.
23
+ */
24
+ export declare const SIGNING_KEY_METADATA_COLUMNS: readonly ['kid', 'status', 'alg', 'signer_kind', 'key_ref', 'activated_at', 'created_at'];
25
+ /** One row of {@link OrchestratorSigningKeyRepo.listTrustedMetadata}. */
26
+ export type SigningKeyMetadataRow = Pick<OrchestratorSigningKeyRow, (typeof SIGNING_KEY_METADATA_COLUMNS)[number]>;
27
+ /** Outcome of {@link OrchestratorSigningKeyRepo.activateIfCurrent}. */
28
+ export interface ActivateIfCurrentResult {
29
+ /** True when this call activated `input.kid`. */
30
+ activated: boolean;
31
+ /**
32
+ * The active row after the call: the new row when `activated`, otherwise the
33
+ * row that was active when the lock was taken (null when none was).
34
+ */
35
+ active: OrchestratorSigningKeyRow | null;
36
+ }
12
37
  /**
13
38
  * Repository for the orchestrator's provenance signing keys. Ports the Platform's
14
39
  * `signing-keys-repo.ts` status model (activate / retire / revoke) and adds
@@ -17,18 +42,38 @@ export interface UpsertActiveInput {
17
42
  export declare class OrchestratorSigningKeyRepo {
18
43
  private readonly db;
19
44
  constructor(db: Kysely<Database>);
45
+ private lockActiveKey;
46
+ private readActiveRow;
20
47
  /** Public keys served in the JWKS / trusted for verification (everything except revoked). */
21
48
  listTrusted(): Promise<OrchestratorSigningKeyRow[]>;
49
+ /**
50
+ * The same keys as {@link listTrusted}, in the same order, with only the
51
+ * {@link SIGNING_KEY_METADATA_COLUMNS}. Backs `kici-admin signing-key list`.
52
+ */
53
+ listTrustedMetadata(): Promise<SigningKeyMetadataRow[]>;
22
54
  /** The single currently-active key row, or null when none is active. */
23
55
  getActiveRow(): Promise<OrchestratorSigningKeyRow | null>;
56
+ /**
57
+ * Activate the freshly generated key `input` only if the active key is still
58
+ * the one the caller last read (`expectedActiveKid`; null = none was active).
59
+ * The read and the write run under {@link ACTIVE_SIGNING_KEY_LOCK}, so of
60
+ * several nodes that each read "no key" and each generated one, exactly one
61
+ * activates; every other call returns the winner's row untouched. A replaced
62
+ * key is demoted to `retiring`.
63
+ */
64
+ activateIfCurrent(expectedActiveKid: string | null, input: UpsertActiveInput): Promise<ActivateIfCurrentResult>;
24
65
  /**
25
66
  * Activate `input.kid`. If it is already active, no-op. Otherwise demote any
26
67
  * current active key to `retiring`, upsert this kid as `active`, in one
27
- * transaction. Returns true when a NEW kid was activated (caller audits).
28
- * Refuses to reactivate a `revoked` kid.
68
+ * transaction under {@link ACTIVE_SIGNING_KEY_LOCK}. Returns true when a NEW
69
+ * kid was activated (caller audits). Refuses to reactivate a `revoked` kid.
29
70
  */
30
71
  upsertActive(input: UpsertActiveInput): Promise<boolean>;
31
72
  retire(kid: string): Promise<void>;
73
+ /**
74
+ * Mark `kid` revoked, under {@link ACTIVE_SIGNING_KEY_LOCK} so a concurrent
75
+ * key switch either sees the revoke or finishes before it.
76
+ */
32
77
  revoke(kid: string, reason: string): Promise<void>;
33
78
  }
34
79
  //# sourceMappingURL=signing-keys-repo.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import type { ColumnType, Generated, Insertable, Selectable, Updateable } from 'kysely';
2
2
  import type { ApprovalRequirement, ApproverClause, InitFailure, StepApprovalPayload } from '@kici-dev/engine';
3
3
  import type { PrecursorResult } from '../cache/precursor-result.js';
4
+ import type { DeferredContextResolution } from '../pipeline/held-context-data.js';
4
5
  /**
5
6
  * Job kind stored in `execution_jobs.job_kind`. `Standard` runs steps on an
6
7
  * agent; `Gate` is an invoke gate that summons source-repo runs; `Proxy`
@@ -231,8 +232,15 @@ export interface DispatchQueueTable {
231
232
  /** JSONB array of runs-on labels for agent matching.
232
233
  * Insert: JSON.stringify(labels), Select: auto-parsed by pg driver. */
233
234
  runs_on_labels: string;
234
- /** JSON-encoded Record of the full job configuration */
235
+ /** JSON-encoded Record of the job configuration, without the fields in `sealed_secrets` */
235
236
  job_config: string;
237
+ /**
238
+ * The secret fields of the stored job, AES-256-GCM encrypted under the
239
+ * orchestrator master key (`secrets/job-secret-seal.ts`). NULL when there is
240
+ * nothing to seal, when no master key is configured, and on a row written
241
+ * before the column existed: those rows keep the fields in the plain column.
242
+ */
243
+ sealed_secrets: ColumnType<string | null, string | null | undefined, string | null>;
236
244
  /** Repository clone URL */
237
245
  repo_url: string;
238
246
  /** Git ref (branch/tag) */
@@ -506,6 +514,13 @@ export interface ExecutionRunTable {
506
514
  * cancelled and for one that entered `cancelling` before the column existed.
507
515
  */
508
516
  cancelling_at: ColumnType<Date | null, Date | null | undefined, Date | null>;
517
+ /**
518
+ * The run's status generation (migration 150). Starts at 0 and rises by one
519
+ * each time the run leaves a terminal status to continue. Forwarded on every
520
+ * `execution.status` frame as `statusEpoch`, so the Platform can tell a
521
+ * reopen from a frame that arrives late after the run finished.
522
+ */
523
+ status_epoch: Generated<number>;
509
524
  /** Total execution duration in milliseconds */
510
525
  duration_ms: number | null;
511
526
  /**
@@ -627,6 +642,18 @@ export interface ExecutionRunTable {
627
642
  * repository that defines it rather than from the one it ran against.
628
643
  */
629
644
  workflow_repo_identifier: string | null;
645
+ /**
646
+ * Commit of the workflow repository the run dispatched, when the workflow is
647
+ * defined in another repository (`workflow_repo_identifier` set). A re-run
648
+ * dispatches this exact commit. NULL on every per-repository run.
649
+ */
650
+ workflow_sha: string | null;
651
+ /**
652
+ * The workflow repository's registered branch, when the workflow is defined
653
+ * in another repository. The git-credential relay checks context branch rules
654
+ * against it. NULL on every per-repository run.
655
+ */
656
+ workflow_branch: string | null;
630
657
  /**
631
658
  * True when this row records a global evaluation round rather than a
632
659
  * workflow.
@@ -1638,6 +1665,31 @@ export interface WorkflowRegistrationsTable {
1638
1665
  default_branch: ColumnType<string | null, string | null | undefined, string | null>;
1639
1666
  /** Source file path for this workflow (e.g. ".kici/workflows/deploy.ts") */
1640
1667
  source_file: ColumnType<string | null, string | null | undefined, string | null>;
1668
+ /**
1669
+ * `lockfileHash` of the lock file that last registered this workflow: the
1670
+ * package-manager lockfile hash that keys the dependency cache. A run dispatched
1671
+ * from the registration probes the dependency cache with it.
1672
+ *
1673
+ * NULL when that lock file records none, and on rows written before migration
1674
+ * 149. NULL means the run installs its dependencies on the agent.
1675
+ */
1676
+ lockfile_hash: ColumnType<string | null, string | null | undefined, string | null>;
1677
+ /**
1678
+ * `siblingsDigest` of the lock file that last registered this workflow: the
1679
+ * in-repo `workspace:` sibling closure, part of the dependency-cache key next to
1680
+ * `lockfile_hash`. NULL when that lock file records none.
1681
+ */
1682
+ siblings_digest: ColumnType<string | null, string | null | undefined, string | null>;
1683
+ /**
1684
+ * The `commit_sha` written together with `lockfile_hash` and `siblings_digest`.
1685
+ * The key is used only while this equals `commit_sha`: a writer that does not
1686
+ * know the key columns moves `commit_sha` without them, and the key it leaves
1687
+ * behind belongs to an older lock file.
1688
+ *
1689
+ * NULL on rows written before migration 151 and on rows registered with no
1690
+ * commit; such a row uses no key.
1691
+ */
1692
+ dep_cache_key_sha: ColumnType<string | null, string | null | undefined, string | null>;
1641
1693
  /** Whether this is a global workflow (triggers across all repos under same routing key) */
1642
1694
  is_global: ColumnType<boolean, boolean | undefined, boolean>;
1643
1695
  /**
@@ -2211,6 +2263,12 @@ export interface ClusterSettingsTable {
2211
2263
  * violation. NULL ⇒ the orchestrator's configured default.
2212
2264
  */
2213
2265
  ownership_db_check_timeout_ms: ColumnType<string | null, number | null | undefined, number | null>;
2266
+ /**
2267
+ * How long a coordinator leaves a queued job alone after it could not open the
2268
+ * job's sealed secrets, before claiming it again. NULL ⇒ the orchestrator's
2269
+ * configured default.
2270
+ */
2271
+ sealed_secrets_retry_backoff_ms: ColumnType<string | null, number | null | undefined, number | null>;
2214
2272
  /**
2215
2273
  * Verified-tier origin the dashboard fetches the orchestrator's X25519
2216
2274
  * dashboard-encryption public key from (and always displays) under the
@@ -2248,6 +2306,21 @@ export interface PendingJobContextsTable {
2248
2306
  * instead of dispatching to an agent. NULL for every ordinary job.
2249
2307
  */
2250
2308
  invoke_config: ColumnType<string | null, string | null | undefined, string | null>;
2309
+ /**
2310
+ * For a job bound to a context, what the dispatch of the stored job resolves
2311
+ * the job's context variables and secrets from (context names, org, routing
2312
+ * key, host facts, registry-auth inputs) as JSONB. Never a secret value. NULL
2313
+ * for a job with no bound context, and for a row stored before the column
2314
+ * existed, whose `job_input` still carries its resolved context data.
2315
+ */
2316
+ context_resolution: ColumnType<DeferredContextResolution | null, string | null | undefined, string | null>;
2317
+ /**
2318
+ * The secret fields of the stored job, AES-256-GCM encrypted under the
2319
+ * orchestrator master key (`secrets/job-secret-seal.ts`). NULL when there is
2320
+ * nothing to seal, when no master key is configured, and on a row written
2321
+ * before the column existed: those rows keep the fields in the plain column.
2322
+ */
2323
+ sealed_secrets: ColumnType<string | null, string | null | undefined, string | null>;
2251
2324
  /** When this context was stored */
2252
2325
  created_at: Generated<Date>;
2253
2326
  }
@@ -2266,6 +2339,13 @@ export interface PendingWorkflowContextsTable {
2266
2339
  org_id: string;
2267
2340
  /** Serializable WorkflowDispatchContext inputs as JSONB */
2268
2341
  context: ColumnType<Record<string, unknown>, string, string>;
2342
+ /**
2343
+ * The secret fields of the stored job, AES-256-GCM encrypted under the
2344
+ * orchestrator master key (`secrets/job-secret-seal.ts`). NULL when there is
2345
+ * nothing to seal, when no master key is configured, and on a row written
2346
+ * before the column existed: those rows keep the fields in the plain column.
2347
+ */
2348
+ sealed_secrets: ColumnType<string | null, string | null | undefined, string | null>;
2269
2349
  /** When this context was stored */
2270
2350
  created_at: Generated<Date>;
2271
2351
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The structured clone auth a bare clone token stands for.
3
+ */
4
+ import type { ProviderGitAuth } from '@kici-dev/engine';
5
+ /** Basic-auth envelope for a provider clone token (`x-access-token` user, GitHub convention). */
6
+ export declare function cloneTokenGitAuth(token: string): ProviderGitAuth;
7
+ //# sourceMappingURL=clone-token-auth.d.ts.map
@@ -0,0 +1,63 @@
1
+ import type { ProviderGitAuth } from '@kici-dev/engine';
2
+ import type { ProviderBundle, ProviderRegistry } from '../provider-registry.js';
3
+ /** Auth the dispatch message carries; every field is optional on the wire. */
4
+ export interface DispatchCloneAuth {
5
+ /** Bare source-repository token, kept for agents that read only `token`. */
6
+ token?: string;
7
+ sourceAuth?: ProviderGitAuth;
8
+ workflowAuth?: ProviderGitAuth;
9
+ }
10
+ /**
11
+ * Mint structured auth (and a bare token when it is basic auth) for one
12
+ * repository through one bundle. Providers that implement `issueGitAuth()`
13
+ * return the auth kind directly; otherwise a clone token is wrapped in a
14
+ * basic-auth envelope.
15
+ */
16
+ export declare function mintRepoGitAuth(bundle: ProviderBundle, repoIdentifier: string, providerContext: unknown): Promise<{
17
+ token: string | null;
18
+ structuredAuth: ProviderGitAuth | null;
19
+ }>;
20
+ /** The job fields the auth resolution reads. */
21
+ export interface DispatchAuthJob {
22
+ id: string;
23
+ repoUrl: string;
24
+ routingKey: string;
25
+ providerContext: unknown;
26
+ jobConfig: Record<string, unknown>;
27
+ }
28
+ /**
29
+ * The git host of a clone URL: the URL host, or the host of an scp-style
30
+ * `[user@]host:path` remote. `undefined` when neither form parses.
31
+ */
32
+ export declare function gitHostOf(url: string): string | undefined;
33
+ /**
34
+ * Why a global job must not be dispatched with this auth, or `undefined` when
35
+ * it may.
36
+ *
37
+ * The agent falls back across the two clones: it clones the source repository
38
+ * with `sourceAuth ?? workflowAuth ?? token` and the workflow repository with
39
+ * `workflowAuth ?? sourceAuth ?? token`. When only one repository's credential is
40
+ * present, the other clone sends it to that repository's host. On the same host
41
+ * that is a credential the host issued; on another host it hands one provider's
42
+ * credential to another, so the job is refused instead. A job with no source URL
43
+ * clones nothing from a host, and neither does a `file:` clone, so pairing one
44
+ * credential with a local repository is never refused.
45
+ */
46
+ export declare function crossHostAuthRefusal(jobConfig: Record<string, unknown>, repoUrl: string, auth: DispatchCloneAuth): string | undefined;
47
+ /**
48
+ * Resolve the clone auth of one dispatch: the source repository's through the
49
+ * job's bundle, and for a global job the workflow repository's through its own
50
+ * bundle. Returns a refusal message when the job must not be dispatched.
51
+ */
52
+ export declare function resolveDispatchCloneAuth(args: {
53
+ providerRegistry: ProviderRegistry;
54
+ /** The bundle of the job's routing key; undefined for an overlay (local) run. */
55
+ bundle: ProviderBundle | undefined;
56
+ repoIdentifier: string;
57
+ job: DispatchAuthJob;
58
+ }): Promise<{
59
+ auth: DispatchCloneAuth;
60
+ } | {
61
+ refused: string;
62
+ }>;
63
+ //# sourceMappingURL=dispatch-git-auth.d.ts.map
package/dist/index.js CHANGED
@@ -2509,6 +2509,32 @@ var AdminApiClient = class {
2509
2509
  return this.request("DELETE", `/api/v1/admin/secrets/${encodeURIComponent(orgId)}/${encodeURIComponent(scope)}/${encodeURIComponent(key)}`);
2510
2510
  }
2511
2511
  /**
2512
+ * Create an empty secret scope. A `<backend>:` qualifier selects the
2513
+ * backend; an unqualified scope targets the PG backend. Creating a scope
2514
+ * that already exists succeeds and changes nothing.
2515
+ */
2516
+ async createScope(orgId, scope) {
2517
+ return this.request("POST", "/api/v1/admin/secrets/scopes", {
2518
+ orgId,
2519
+ scope
2520
+ });
2521
+ }
2522
+ /**
2523
+ * Rename a secret scope inside its backend. The route refuses a move between
2524
+ * backends and a rename onto a scope that already exists.
2525
+ */
2526
+ async renameScope(orgId, oldScope, newScope) {
2527
+ return this.request("PUT", "/api/v1/admin/secrets/scopes/rename", {
2528
+ orgId,
2529
+ oldScope,
2530
+ newScope
2531
+ });
2532
+ }
2533
+ /** Delete a secret scope and every secret in it. */
2534
+ async deleteScope(orgId, scope) {
2535
+ return this.request("DELETE", `/api/v1/admin/secrets/scopes/${encodeURIComponent(orgId)}/${encodeURIComponent(scope)}`);
2536
+ }
2537
+ /**
2512
2538
  * Create (or upsert) a deployment context. `allowLocalExecution` lets
2513
2539
  * CLI-initiated / local runs resolve secrets through this context.
2514
2540
  */
@@ -18,9 +18,11 @@ export declare function parseLockDocument(raw: string, repoIdentifier: string, r
18
18
  * `schemaVersion` is at or above this codebase's `BREAKING_FLOOR` and whose
19
19
  * required reader version is at or below this orchestrator's `SCHEMA_VERSION`.
20
20
  * Both out-of-window cases throw a `LockFileParseError` (the established
21
- * corrupt-lock signal) carrying operator-actionable guidance.
21
+ * corrupt-lock signal) carrying operator-actionable guidance. `readerVersion`
22
+ * is the schema version this orchestrator reads, overridable so a test can pose
23
+ * as an older orchestrator.
22
24
  */
23
- export declare function assertLockFileSchemaCompatible(lockFile: LockFile, repoIdentifier: string, ref: string): void;
25
+ export declare function assertLockFileSchemaCompatible(lockFile: LockFile, repoIdentifier: string, ref: string, readerVersion?: number): void;
24
26
  /**
25
27
  * Walk every static job's runsOn / excludeLabels / runsOnAll matchers and throw
26
28
  * if any element is not a valid LabelMatcher. Dynamic job generators carry no
@@ -642,7 +642,7 @@ export declare const installSecretsContributorStrippedTotal: import("@openteleme
642
642
  */
643
643
  export declare const containerRegistryAuthContributorStrippedTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
644
644
  /**
645
- * Per-environment latency of `secretResolver.resolveForJob` calls in the
645
+ * Per-environment latency of `secretResolver.resolveForContext` calls in the
646
646
  * install-secrets path. Buckets mirror `triggerMatchDurationSeconds`:
647
647
  * 1ms → 5s covers fast Postgres reads through pathological Vault timeouts.
648
648
  *
@@ -14,6 +14,11 @@ export interface RunClaimSource {
14
14
  run_id: string;
15
15
  org_id: string;
16
16
  repo_identifier: string | null;
17
+ /**
18
+ * `owner/repo` of the repository that defines the workflow, when that is not
19
+ * `repo_identifier` (a global workflow). NULL for a same-repository run.
20
+ */
21
+ workflow_repo_identifier: string | null;
17
22
  ref: string | null;
18
23
  sha: string | null;
19
24
  workflow_name: string | null;
@@ -77,6 +82,11 @@ export interface IdTokenClaims extends EventClaims {
77
82
  kici_run_id: string;
78
83
  kici_job_id: string;
79
84
  repository: string | null;
85
+ /**
86
+ * The repository that defines the workflow. Equals `repository` for a
87
+ * same-repository run; names the workflow repository for a global workflow.
88
+ */
89
+ workflow_repository: string | null;
80
90
  ref: string | null;
81
91
  sha: string | null;
82
92
  workflow_ref: string | null;
@@ -40,6 +40,8 @@ export interface OidcMintRegistrationInput {
40
40
  * first mint (leader-election-race-safe).
41
41
  */
42
42
  resolveOrchestratorSigner?: () => Promise<Signer | null>;
43
+ /** Whether this node is the Raft leader; named when a mint defers for want of a key. */
44
+ isLeader?: () => boolean;
43
45
  /** The orchestrator's own provenance issuer identity (config.provenanceSigningIssuer). */
44
46
  provenanceSigningIssuer?: string;
45
47
  dispatcher: LocalMintOwnershipResolver & {