@kici-dev/orchestrator 0.1.22 → 0.1.24

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 (62) hide show
  1. package/dist/agent/host-roster.d.ts +39 -3
  2. package/dist/agent/token-store.d.ts +33 -0
  3. package/dist/app.d.ts +3 -0
  4. package/dist/cache/pending-inits.d.ts +3 -2
  5. package/dist/cli/api-client.d.ts +8 -0
  6. package/dist/cli/commands/attestations-reverify.d.ts +18 -0
  7. package/dist/cli/commands/attestations.d.ts +3 -0
  8. package/dist/cli/commands/shared/versioned-upgrade.d.ts +52 -0
  9. package/dist/cli.js +2284 -442
  10. package/dist/config.d.ts +2 -0
  11. package/dist/dashboard/attestation-filters.d.ts +70 -0
  12. package/dist/dashboard/handler.d.ts +33 -1
  13. package/dist/db/migrations/051_binding_host_pattern.d.ts +18 -0
  14. package/dist/db/migrations/052_host_reach_metadata.d.ts +19 -0
  15. package/dist/db/migrations/053_agent_token_single_use.d.ts +17 -0
  16. package/dist/db/migrations/054_local_working_tree.d.ts +15 -0
  17. package/dist/db/migrations/055_agent_token_mandatory_labels.d.ts +18 -0
  18. package/dist/db/migrations/056_execution_jobs_environments.d.ts +17 -0
  19. package/dist/db/migrations/057_step_concurrency.d.ts +4 -0
  20. package/dist/db/migrations/058_access_log_agent_label.d.ts +4 -0
  21. package/dist/db/migrations/059_attestation_verdict.d.ts +4 -0
  22. package/dist/db/migrations/060_run_trigger_actor.d.ts +4 -0
  23. package/dist/db/types.d.ts +78 -0
  24. package/dist/environments/binding-store.d.ts +12 -3
  25. package/dist/environments/held-runs.d.ts +24 -0
  26. package/dist/environments/protection/aggregate.d.ts +43 -0
  27. package/dist/environments/protection/satisfiability.d.ts +64 -0
  28. package/dist/index.js +9 -0
  29. package/dist/metrics/prometheus.d.ts +2 -0
  30. package/dist/orchestrator-core.d.ts +8 -0
  31. package/dist/pipeline/dispatch-matched-workflow.d.ts +60 -3
  32. package/dist/pipeline/flatten-lock-steps.d.ts +11 -0
  33. package/dist/pipeline/inline-eval.d.ts +2 -1
  34. package/dist/pipeline/job-environments.d.ts +71 -0
  35. package/dist/pipeline/manual-schedule.d.ts +22 -0
  36. package/dist/pipeline/test-pipeline.d.ts +15 -0
  37. package/dist/provenance/trust-root.d.ts +24 -0
  38. package/dist/provenance/verify-at-ingest.d.ts +24 -0
  39. package/dist/reporting/agent-failure-category.d.ts +27 -0
  40. package/dist/reporting/agent-run-result-mapper.d.ts +13 -0
  41. package/dist/reporting/execution-tracker.d.ts +22 -2
  42. package/dist/reporting/log-writer.d.ts +19 -0
  43. package/dist/reporting/run-aggregator.d.ts +161 -0
  44. package/dist/reporting/step-log-reader.d.ts +39 -0
  45. package/dist/routes/admin-registrations.d.ts +8 -0
  46. package/dist/routes/admin-runs.d.ts +7 -0
  47. package/dist/scaler/manager.d.ts +17 -0
  48. package/dist/secrets/index.d.ts +1 -1
  49. package/dist/secrets/secret-resolver.d.ts +19 -6
  50. package/dist/server.js +21535 -19131
  51. package/dist/standalone.js +26544 -24580
  52. package/dist/storage/loopback-guard.d.ts +49 -0
  53. package/dist/ws/agent-handler.d.ts +15 -1
  54. package/dist/ws/bringup-api.d.ts +76 -0
  55. package/dist/ws/dashboard-fleet-handler.d.ts +11 -1
  56. package/dist/ws/fleet-runs-on-all.d.ts +3 -0
  57. package/dist/ws/platform-client.d.ts +39 -1
  58. package/dist/ws/test-relay-handlers.d.ts +9 -0
  59. package/installer-image-digests.json +3 -3
  60. package/package.json +6 -8
  61. package/sbom.spdx.json +57 -57
  62. package/dist/secrets/crypto.d.ts +0 -49
package/dist/config.d.ts CHANGED
@@ -27,6 +27,7 @@ declare const configSchema: z.ZodObject<{
27
27
  platformUrl: z.ZodOptional<z.ZodString>;
28
28
  platformToken: z.ZodOptional<z.ZodString>;
29
29
  dashboardUrl: z.ZodOptional<z.ZodString>;
30
+ provenanceIssuer: z.ZodOptional<z.ZodString>;
30
31
  webhookPublicUrl: z.ZodOptional<z.ZodString>;
31
32
  databaseUrl: z.ZodDefault<z.ZodString>;
32
33
  lockfileCacheMax: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -248,6 +249,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
248
249
  platformUrl?: string | undefined;
249
250
  platformToken?: string | undefined;
250
251
  dashboardUrl?: string | undefined;
252
+ provenanceIssuer?: string | undefined;
251
253
  webhookPublicUrl?: string | undefined;
252
254
  cacheStorageType?: "s3" | "filesystem" | undefined;
253
255
  cacheStoragePath?: string | undefined;
@@ -0,0 +1,70 @@
1
+ import { type Kysely } from 'kysely';
2
+ import type { AttestationListFilters } from '@kici-dev/engine';
3
+ import type { Database } from '../db/types.js';
4
+ /**
5
+ * The org-wide attestations base query: joins `attestations` to `execution_jobs`
6
+ * (for the job name) and `execution_runs` (for repository / workflow context).
7
+ *
8
+ * `attestations.run_id` / `job_id` are TEXT (P1.5 schema) while the
9
+ * `execution_*` keys are `uuid`; Postgres won't compare `uuid = text` implicitly,
10
+ * so the join casts the uuid side to text — mirroring `resolveAttestationsForRun`.
11
+ * The `execution_runs` join is a LEFT join so a row with no matching run still
12
+ * lists (repository / workflow come back null).
13
+ */
14
+ export declare function baseAttestationsQuery(db: Kysely<Database>): import("kysely").SelectQueryBuilder<{
15
+ environments: import("../db/types.js").EnvironmentsTable;
16
+ host_roster: import("../db/types.js").HostRosterTable;
17
+ dispatch_queue: import("../db/types.js").DispatchQueueTable;
18
+ dedup_cache: import("../db/types.js").DedupCacheTable;
19
+ ip_allocations: import("../db/types.js").IpAllocationTable;
20
+ execution_runs: import("kysely").Nullable<import("../db/types.js").ExecutionRunTable>;
21
+ execution_jobs: import("../db/types.js").ExecutionJobTable;
22
+ execution_steps: import("../db/types.js").ExecutionStepTable;
23
+ raft_state: import("../db/types.js").RaftStateTable;
24
+ secret_audit_log: import("../db/types.js").SecretAuditLogTable;
25
+ scoped_secrets: import("../db/types.js").ScopedSecretsTable;
26
+ environment_bindings: import("../db/types.js").EnvironmentBindingsTable;
27
+ environment_variables: import("../db/types.js").EnvironmentVariablesTable;
28
+ environment_source_overrides: import("../db/types.js").EnvironmentSourceOverridesTable;
29
+ held_runs: import("../db/types.js").HeldRunsTable;
30
+ held_run_approvals: import("../db/types.js").HeldRunApprovalsTable;
31
+ admin_tokens: import("../db/types.js").AdminTokenTable;
32
+ agent_tokens: import("../db/types.js").AgentTokenTable;
33
+ config_versions: import("../db/types.js").ConfigVersionTable;
34
+ kici_events: import("../db/types.js").KiciEventTable;
35
+ generic_webhook_sources: import("../db/types.js").GenericWebhookSourceTable;
36
+ cross_repo_trust: import("../db/types.js").CrossRepoTrustTable;
37
+ test_uploads: import("../db/types.js").TestUploadsTable;
38
+ workflow_registrations: import("../db/types.js").WorkflowRegistrationsTable;
39
+ registry_versions: import("../db/types.js").RegistryVersionsTable;
40
+ cron_last_fired: import("../db/types.js").CronLastFiredTable;
41
+ run_ephemeral_keys: import("../db/types.js").RunEphemeralKeysTable;
42
+ run_secret_outputs: import("../db/types.js").RunSecretOutputsTable;
43
+ concurrency_groups: import("../db/types.js").ConcurrencyGroupsTable;
44
+ sources: import("../db/types.js").SourcesTable;
45
+ cluster_meta: import("../db/types.js").ClusterMetaTable;
46
+ join_tokens: import("../db/types.js").JoinTokenTable;
47
+ org_settings: import("../db/types.js").OrgSettingsTable;
48
+ execution_job_needs: import("../db/types.js").ExecutionJobNeedsTable;
49
+ pending_job_contexts: import("../db/types.js").PendingJobContextsTable;
50
+ pending_workflow_contexts: import("../db/types.js").PendingWorkflowContextsTable;
51
+ event_log: import("../db/types.js").EventLogTable;
52
+ access_log: import("../db/types.js").AccessLogTable;
53
+ cold_store_chunk_counts: import("../db/types.js").ColdStoreChunkCountsTable;
54
+ cold_store_chunks: import("../db/types.js").ColdStoreChunksTable;
55
+ check_run_tracking: import("../db/types.js").CheckRunTrackingTable;
56
+ scaler_spawning_agents: import("../db/types.js").ScalerSpawningAgentsTable;
57
+ scaler_agent_jobs: import("../db/types.js").ScalerAgentJobsTable;
58
+ scaler_reservations: import("../db/types.js").ScalerReservationsTable;
59
+ attestations: import("../db/types.js").AttestationsTable;
60
+ remote_sources: import("../db/types.js").RemoteSourcesTable;
61
+ }, "execution_runs" | "execution_jobs" | "attestations", {}>;
62
+ export type AttestationsBaseQuery = ReturnType<typeof baseAttestationsQuery>;
63
+ /**
64
+ * Apply org-wide attestation filters to the base query. Digest is exact-match;
65
+ * name is an ILIKE substring; status / repository / workflow / job are equality;
66
+ * created_at gets `>=` / `<=` bounds for the date range. Absent filters are
67
+ * skipped.
68
+ */
69
+ export declare function applyAttestationFilters(qb: AttestationsBaseQuery, filters: AttestationListFilters): AttestationsBaseQuery;
70
+ //# sourceMappingURL=attestation-filters.d.ts.map
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { type Kysely } from 'kysely';
15
15
  import { type ColdStore } from '@kici-dev/shared';
16
- import type { DashboardRunDetailRequest, DashboardRunsListRequest, DashboardRunsListResponse, DashboardRunsFiltersRequest, DashboardRunsFiltersResponse, DashboardSourcesListRequest, DashboardSourcesListResponse, DashboardStepLogsRequest, DashboardAttestationsListRequest, DashboardPayloadRequest, DashboardOrchLogsRequest, DashboardEventLogListRequest, DashboardEventLogDetailRequest, DashboardEventLogPayloadStreamRequest, DashboardAccessLogListRequest, DashboardEventDlqListRequest, DashboardEventDlqCountRequest, DashboardEventDlqRetryRequest, DashboardEventDlqDiscardRequest, RunRerunRequest, RunCancelRequest, ManualScheduleRequest } from '@kici-dev/engine';
16
+ import type { DashboardRunDetailRequest, DashboardRunsListRequest, DashboardRunsListResponse, DashboardRunsFiltersRequest, DashboardRunsFiltersResponse, DashboardSourcesListRequest, DashboardSourcesListResponse, DashboardStepLogsRequest, DashboardAttestationsListRequest, DashboardAttestationsListAllRequest, DashboardAttestationGetRequest, DashboardPayloadRequest, DashboardOrchLogsRequest, DashboardEventLogListRequest, DashboardEventLogDetailRequest, DashboardEventLogPayloadStreamRequest, DashboardAccessLogListRequest, DashboardEventDlqListRequest, DashboardEventDlqCountRequest, DashboardEventDlqRetryRequest, DashboardEventDlqDiscardRequest, RunRerunRequest, RunCancelRequest, ManualScheduleRequest, DashboardRunStructuredRequest } from '@kici-dev/engine';
17
17
  import type { Database } from '../db/types.js';
18
18
  import type { LogStorage } from '../reporting/log-storage.js';
19
19
  import type { CacheStorage } from '../storage/types.js';
@@ -166,6 +166,14 @@ export declare class DashboardHandler {
166
166
  * builds a nested job/step tree, and sends the response.
167
167
  */
168
168
  handleRunDetail(msg: DashboardRunDetailRequest): Promise<void>;
169
+ /**
170
+ * Handle a dashboard.run.structured request — the user-plane equivalent of
171
+ * the orchestrator-admin `/runs/:id/structured` endpoint. Reuses the Phase-1
172
+ * aggregator + provenance mapper so the result is byte-identical to the admin
173
+ * surface; emits an `access_log` `run.structured.read` row (carrying the
174
+ * agent label when the actor came through an agent PAT).
175
+ */
176
+ handleRunStructured(msg: DashboardRunStructuredRequest): Promise<void>;
169
177
  /**
170
178
  * Resolve every routing key owned by an org by unioning both source
171
179
  * tables. The orchestrator is single-org but multi-routing-key: one org
@@ -275,6 +283,30 @@ export declare class DashboardHandler {
275
283
  * skipped (best-effort) rather than failing the whole list.
276
284
  */
277
285
  handleAttestationsList(msg: DashboardAttestationsListRequest): Promise<void>;
286
+ /**
287
+ * Fetch + parse one attestation bundle from object storage. Returns the full
288
+ * detail item, or null when the bundle is missing or not valid JSON (logged).
289
+ * Shared by `handleAttestationsList` (per-run) and `handleAttestationGet`.
290
+ */
291
+ private inlineBundle;
292
+ /**
293
+ * Org-wide attestations query: one page of metadata-only summaries (no bundle
294
+ * fetch) plus the total matching count. Filters/pagination/sort applied via
295
+ * the shared filter builder.
296
+ */
297
+ private resolveAttestationsListAll;
298
+ /**
299
+ * Handle a dashboard.attestations.list.all request: org-wide, paginated,
300
+ * filtered list of attestation summaries (metadata only). Access-logged
301
+ * against the `attestation` target.
302
+ */
303
+ handleAttestationsListAll(msg: DashboardAttestationsListAllRequest): Promise<void>;
304
+ /**
305
+ * Handle a dashboard.attestation.get request: a single attestation by id with
306
+ * its bundle inlined for the detail page. Resolves the run-owning org for the
307
+ * access-log row, then logs against the `attestation` target.
308
+ */
309
+ handleAttestationGet(msg: DashboardAttestationGetRequest): Promise<void>;
278
310
  /**
279
311
  * Handle a dashboard.payload request.
280
312
  * Reads the webhook payload from log storage for the given runId.
@@ -0,0 +1,18 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add a per-host dimension to environment secret bindings:
4
+ *
5
+ * - `environment_bindings.host_pattern text NOT NULL DEFAULT '**'` — the host
6
+ * selector a binding applies to (exact / glob / regex, matched against a
7
+ * fan-out child's agentId / hostname / labels). `'**'` matches every host, so
8
+ * existing rows are backfilled to `'**'` and keep their fleet-wide behaviour.
9
+ * - The binding unique key widens from `(environment_id, scope_pattern)` to
10
+ * `(environment_id, scope_pattern, host_pattern)` so the same scope can carry
11
+ * distinct per-host selectors.
12
+ *
13
+ * Idempotent: re-running on a DB that already has the column / index is a no-op.
14
+ * Additive — staging data is preserved.
15
+ */
16
+ export declare function up(db: Kysely<unknown>): Promise<void>;
17
+ export declare function down(db: Kysely<unknown>): Promise<void>;
18
+ //# sourceMappingURL=051_binding_host_pattern.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add pre-agent reach metadata to the host roster so a declared host (no agent
4
+ * yet) can be reached over SSH for bootstrap bring-up:
5
+ *
6
+ * - `host_roster.address text NULL` — IP / hostname to SSH to.
7
+ * - `host_roster.ssh_user text NULL` — SSH login user (defaults to `root` at use site).
8
+ * - `host_roster.ssh_port int NULL` — SSH port (defaults to 22 at use site).
9
+ * - `host_roster.ssh_key_secret text NULL` — scoped-secret ref (`scope/key`)
10
+ * holding the bring-up private key. The orchestrator resolves it server-side;
11
+ * the key never lives in the roster.
12
+ *
13
+ * All nullable: a host with no reach metadata simply cannot be bootstrapped and
14
+ * behaves exactly as before. Idempotent (`ADD COLUMN IF NOT EXISTS`); additive,
15
+ * so staging data is preserved.
16
+ */
17
+ export declare function up(db: Kysely<unknown>): Promise<void>;
18
+ export declare function down(db: Kysely<unknown>): Promise<void>;
19
+ //# sourceMappingURL=052_host_reach_metadata.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add a single-use marker to agent tokens for the bootstrap (init-runner)
4
+ * bring-up flow:
5
+ *
6
+ * - `agent_tokens.consumed_at timestamptz NULL` — set the first time a
7
+ * single-use bootstrap token is consumed (at `agent.register`). A second
8
+ * register with the same token is rejected. NULL = never consumed (the
9
+ * default for every existing static / ephemeral token, which stay reusable
10
+ * until expiry).
11
+ *
12
+ * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
13
+ * preserved.
14
+ */
15
+ export declare function up(db: Kysely<unknown>): Promise<void>;
16
+ export declare function down(db: Kysely<unknown>): Promise<void>;
17
+ //# sourceMappingURL=053_agent_token_single_use.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Mark runs that executed an uploaded local working tree (`kici run remote`):
4
+ *
5
+ * - `execution_runs.local_working_tree boolean NOT NULL DEFAULT false` — true
6
+ * for runs that ran a developer's local working tree from the CLI (inline
7
+ * lock file). The dashboard renders a "Local machine" badge for these runs
8
+ * and avoids building an external repository link from the repo identifier.
9
+ *
10
+ * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive with a default, so staging
11
+ * data is preserved.
12
+ */
13
+ export declare function up(db: Kysely<unknown>): Promise<void>;
14
+ export declare function down(db: Kysely<unknown>): Promise<void>;
15
+ //# sourceMappingURL=054_local_working_tree.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add a token-bound mandatory-label taint to agent tokens:
4
+ *
5
+ * - `agent_tokens.mandatory_labels text NULL` — a JSON-encoded `string[]` of
6
+ * Kubernetes-taint-style gate labels the token authorizes. When a static
7
+ * agent registers with this token, the set becomes the agent's registry-entry
8
+ * `mandatoryLabels`: the agent only accepts a job when every label here
9
+ * appears in the job's required labels. NULL = no taint (the default; the
10
+ * agent accepts any job its advertised labels match), so every existing token
11
+ * stays unconfined until re-minted.
12
+ *
13
+ * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
14
+ * preserved.
15
+ */
16
+ export declare function up(db: Kysely<unknown>): Promise<void>;
17
+ export declare function down(db: Kysely<unknown>): Promise<void>;
18
+ //# sourceMappingURL=055_agent_token_mandatory_labels.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add a per-job bound deployment-environment list to `execution_jobs`:
4
+ *
5
+ * - `execution_jobs.environments text NULL` — a JSON-encoded `string[]` of the
6
+ * ordered environment names a job binds (`environments: [...]`), in merge
7
+ * order. Written at dispatch with the statically-resolved names (impure
8
+ * dynamic elements as a `(dynamic)` placeholder), then overwritten with the
9
+ * fully-resolved list when a deferred-init agent eval resolves dynamic
10
+ * elements. NULL = the job binds no environment.
11
+ *
12
+ * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
13
+ * preserved.
14
+ */
15
+ export declare function up(db: Kysely<unknown>): Promise<void>;
16
+ export declare function down(db: Kysely<unknown>): Promise<void>;
17
+ //# sourceMappingURL=056_execution_jobs_environments.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=057_step_concurrency.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=058_access_log_agent_label.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=059_attestation_verdict.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=060_run_trigger_actor.d.ts.map
@@ -271,6 +271,8 @@ export interface ExecutionRunTable {
271
271
  provider_context: Generated<string>;
272
272
  /** Whether this is a CLI-initiated test run */
273
273
  is_test_run: Generated<boolean>;
274
+ /** True when the run executed an uploaded local working tree (`kici run remote`). */
275
+ local_working_tree: Generated<boolean>;
274
276
  /** Fixture ID for test runs (null for real webhook runs) */
275
277
  fixture_id: string | null;
276
278
  /** Parent run ID for re-run lineage (null for original runs). */
@@ -289,6 +291,22 @@ export interface ExecutionRunTable {
289
291
  lock_file_source: string | null;
290
292
  /** Username of the contributor (null for non-PR events) */
291
293
  contributor_username: string | null;
294
+ /**
295
+ * Origin provider of the triggering actor (`github` today). Provider-generic
296
+ * so GitLab/Bitbucket extend later. Null when no actor was captured.
297
+ */
298
+ trigger_actor_provider: string | null;
299
+ /**
300
+ * Provider login of the person who triggered the run (pusher / PR author).
301
+ * Captured for ALL event types, unlike the PR-only `contributor_username`.
302
+ */
303
+ trigger_actor_username: string | null;
304
+ /**
305
+ * Immutable provider user id of the triggering actor (mirrors
306
+ * `identity_links.provider_user_id`). Preferred over the mutable username
307
+ * when resolving the actor to a KiCI user.
308
+ */
309
+ trigger_actor_user_id: string | null;
292
310
  /** Human-readable reason why the run failed (null for non-failed runs). */
293
311
  failure_reason: string | null;
294
312
  /**
@@ -371,6 +389,12 @@ export interface ExecutionJobTable {
371
389
  dispatched_contexts: Generated<string>;
372
390
  /** Aggregated step outputs JSONB (step-keyed map of outputs). Populated on job success. */
373
391
  outputs: string | null;
392
+ /**
393
+ * Ordered bound deployment-environment names for this job, JSON-encoded
394
+ * `string[]` (null when the job binds none). Written at dispatch and
395
+ * overwritten with the agent-resolved list for dynamic environments.
396
+ */
397
+ environments: string | null;
374
398
  /** Whether all upstream needs edges are satisfied (dispatch gate). */
375
399
  needs_satisfied: Generated<boolean>;
376
400
  /** Timestamp when needs_satisfied first flipped to true. */
@@ -453,6 +477,13 @@ export interface ExecutionStepTable {
453
477
  drift_summary: string | null;
454
478
  /** Structured drift value returned by `check()` (JSONB). NULL when no drift. */
455
479
  drift: ColumnType<unknown | null, unknown, unknown>;
480
+ /**
481
+ * Parallel step-group concurrency role (`sequential` | `parallel-child` |
482
+ * `parallel-group`). NULL for an ordinary sequential step.
483
+ */
484
+ concurrency_kind: string | null;
485
+ /** Parallel-group correlation id shared by a group's children. NULL for sequential steps. */
486
+ group_id: string | null;
456
487
  /** When this record was created */
457
488
  created_at: Generated<Date>;
458
489
  /**
@@ -605,6 +636,12 @@ export interface EnvironmentBindingsTable {
605
636
  environment_id: string;
606
637
  /** Scope pattern for matching (e.g. workflow name glob, repo pattern) */
607
638
  scope_pattern: string;
639
+ /**
640
+ * Host selector this binding applies to (exact / glob / regex over a fan-out
641
+ * child's agentId / hostname / labels). `'**'` (the default) matches every
642
+ * host.
643
+ */
644
+ host_pattern: Generated<string>;
608
645
  /** When this binding was created */
609
646
  created_at: Generated<Date>;
610
647
  }
@@ -798,6 +835,12 @@ export interface AccessLogTable {
798
835
  source: string;
799
836
  outcome: string;
800
837
  error_message: string | null;
838
+ /**
839
+ * Human-set agent name, when the actor authenticated with an agent-kind PAT.
840
+ * NULL for ordinary human / API-key / system actors. Queryable so the access
841
+ * log can be filtered by agent.
842
+ */
843
+ agent_label: string | null;
801
844
  created_at: Generated<Date>;
802
845
  /**
803
846
  * Set inside the archive transaction before the row is DELETEd.
@@ -851,6 +894,14 @@ export interface AgentTokenTable {
851
894
  token_prefix: string;
852
895
  /** JSON-encoded string[] of agent labels (null = any) */
853
896
  labels: string | null;
897
+ /**
898
+ * JSON-encoded string[] of mandatory labels (a Kubernetes-taint-style gate):
899
+ * a static agent registering with this token only accepts a job when every
900
+ * label here appears in the job's required labels. null = no taint (the
901
+ * default; the agent accepts any job its advertised labels match).
902
+ * Authorized at mint time alongside `labels`.
903
+ */
904
+ mandatory_labels: string | null;
854
905
  /** Token type: 'ephemeral' (scaler-issued) or 'static' (CLI-created) */
855
906
  agent_type: string;
856
907
  /** When this token was created */
@@ -863,6 +914,13 @@ export interface AgentTokenTable {
863
914
  revoked_at: Date | null;
864
915
  /** When this token expires (null = never, static tokens) */
865
916
  expires_at: Date | null;
917
+ /**
918
+ * Single-use marker for bootstrap (init-runner) tokens. Set the first time
919
+ * the token is consumed at `agent.register`; a second register is rejected.
920
+ * NULL = never consumed (every static / ephemeral token stays reusable until
921
+ * expiry).
922
+ */
923
+ consumed_at: ColumnType<Date | null, Date | string | null | undefined, Date | string | null>;
866
924
  }
867
925
  export type AgentTokenRow = Selectable<AgentTokenTable>;
868
926
  export type NewAgentTokenRow = Insertable<AgentTokenTable>;
@@ -1572,6 +1630,16 @@ export interface AttestationsTable {
1572
1630
  media_type: string;
1573
1631
  /** When this row was inserted. */
1574
1632
  created_at: Generated<Date>;
1633
+ /**
1634
+ * Server-side verification verdict computed at ingest. One of
1635
+ * `verified` / `failed` / `unverifiable` / `pending`
1636
+ * (`attestationVerifyStatusSchema.enum.*`). DB default is `pending`.
1637
+ */
1638
+ verify_status: Generated<string>;
1639
+ /** First failure code from `verifyKiciBundle`, or NULL when verified/pending. */
1640
+ verify_reason: string | null;
1641
+ /** When the verdict was recorded (explicitly set, not DB-generated). */
1642
+ verified_at: Date | null;
1575
1643
  }
1576
1644
  export type AttestationRow = Selectable<AttestationsTable>;
1577
1645
  export type NewAttestationRow = Insertable<AttestationsTable>;
@@ -1639,6 +1707,16 @@ export interface HostRosterTable {
1639
1707
  * reconnects (down-then-up). NULL = no reboot pending.
1640
1708
  */
1641
1709
  reboot_pending_until: ColumnType<Date | null, Date | string | null, Date | string | null>;
1710
+ /**
1711
+ * Pre-agent reach metadata: how to SSH to a declared host before it has a
1712
+ * KiCI agent, for bootstrap bring-up. All nullable — a host with no reach
1713
+ * metadata cannot be bootstrapped and behaves exactly as before.
1714
+ */
1715
+ address: ColumnType<string | null, string | null | undefined, string | null>;
1716
+ ssh_user: ColumnType<string | null, string | null | undefined, string | null>;
1717
+ ssh_port: ColumnType<number | null, number | null | undefined, number | null>;
1718
+ /** Scoped-secret ref (`scope/key`) holding the bring-up private key. */
1719
+ ssh_key_secret: ColumnType<string | null, string | null | undefined, string | null>;
1642
1720
  created_at: Generated<Date>;
1643
1721
  updated_at: ColumnType<Date, Date | string | undefined, Date | string>;
1644
1722
  }
@@ -6,6 +6,14 @@
6
6
  */
7
7
  import type { Kysely } from 'kysely';
8
8
  import type { Database, EnvironmentBinding } from '../db/types.js';
9
+ /**
10
+ * A scope→environment binding with its host selector. `hostPattern` defaults to
11
+ * `'**'` (all hosts) when omitted.
12
+ */
13
+ export interface BindingInput {
14
+ scopePattern: string;
15
+ hostPattern?: string;
16
+ }
9
17
  /**
10
18
  * Data access layer for environment bindings.
11
19
  */
@@ -17,10 +25,11 @@ export declare class BindingStore {
17
25
  /**
18
26
  * Replace all bindings for an environment in a transaction.
19
27
  *
20
- * Deletes existing bindings and inserts the new set atomically.
21
- * Pass an empty array to clear all bindings.
28
+ * Deletes existing bindings and inserts the new set atomically. Each binding
29
+ * carries a `scopePattern` and an optional `hostPattern` (defaulting to
30
+ * `'**'`). Pass an empty array to clear all bindings.
22
31
  */
23
- set(orgId: string, environmentId: string, scopePatterns: string[]): Promise<void>;
32
+ set(orgId: string, environmentId: string, bindings: BindingInput[]): Promise<void>;
24
33
  /** Find bindings for an environment (alias for list, used by secret resolver). */
25
34
  findBindingsForEnvironment(orgId: string, environmentId: string): Promise<EnvironmentBinding[]>;
26
35
  }
@@ -104,8 +104,32 @@ export declare class HeldRunStore {
104
104
  * the created row.
105
105
  */
106
106
  createHold(orgId: string, data: CreateHoldData): Promise<HeldRun>;
107
+ /** INSERT one decision row using the given executor (root or transaction). */
108
+ private insertDecisionRow;
109
+ /** Flip a pending hold to 'approved' using the given executor. Undefined if not pending. */
110
+ private flipToApproved;
111
+ /** Flip a pending hold to 'rejected' using the given executor. Undefined if not pending. */
112
+ private flipToRejected;
113
+ /** Map a released held_runs row to the resume ReleaseSignal. */
114
+ private toReleaseSignal;
107
115
  /** Record one approve/reject decision against a hold. */
108
116
  recordDecision(heldRunId: string, data: RecordDecisionData): Promise<HeldRunApproval>;
117
+ /**
118
+ * Atomically record an approve decision and release the (now-satisfied) hold.
119
+ * The INSERT into `held_run_approvals` and the `held_runs` → approved UPDATE
120
+ * run in a single transaction, so a crash between them cannot strand the hold
121
+ * `pending` with a recorded approve. Throws if the hold is not found or no
122
+ * longer pending (the whole transaction rolls back).
123
+ */
124
+ recordAndRelease(orgId: string, heldRunId: string, data: RecordDecisionData): Promise<ReleaseSignal>;
125
+ /**
126
+ * Atomically record a reject decision and reject the hold. The INSERT and the
127
+ * `held_runs` → rejected UPDATE run in a single transaction, so a crash
128
+ * between them cannot strand the hold `pending` with a recorded reject (which
129
+ * would poison `evaluate()` forever). Throws if the hold is not found or no
130
+ * longer pending (the whole transaction rolls back).
131
+ */
132
+ recordAndReject(orgId: string, heldRunId: string, data: RecordDecisionData, reason?: string): Promise<HeldRun>;
109
133
  /** List the recorded decisions for a hold, oldest first. */
110
134
  listDecisions(heldRunId: string): Promise<HeldRunApproval[]>;
111
135
  /** Get a single held run by id (org-scoped). Returns null if absent. */
@@ -0,0 +1,43 @@
1
+ import { EnvGateRejectReason, type Environment } from '@kici-dev/engine';
2
+ import type { JobDispatchContext } from './pipeline.js';
3
+ /** A single environment's rejection under all-must-pass aggregation. */
4
+ export interface EnvGateRejection {
5
+ environment: string;
6
+ reason: EnvGateRejectReason;
7
+ detail: string;
8
+ }
9
+ /** Effective protection parameters after most-restrictive aggregation. */
10
+ export interface EffectiveProtection {
11
+ minimumTrust?: 'known' | 'trusted';
12
+ requiredReviewers: string[];
13
+ waitTimerSeconds: number | null;
14
+ holdExpirySeconds: number;
15
+ concurrencyLimit: number | null;
16
+ concurrencyStrategy: 'queue' | 'cancel-pending';
17
+ }
18
+ /**
19
+ * Evaluate each environment's hard reject gates against the run context. A name
20
+ * with no `Environment` record yields an `env_not_found` rejection. Returns all
21
+ * rejections (empty = every environment passed the reject gates).
22
+ */
23
+ export declare function evaluateMultiEnvGates(envs: ReadonlyArray<{
24
+ name: string;
25
+ env: Environment | undefined;
26
+ }>, ctx: JobDispatchContext): EnvGateRejection[];
27
+ /**
28
+ * Aggregate hold/wait/queue parameters across all bound environments, most
29
+ * restrictive wins: trust = max tier, reviewers = sorted dedup union, wait timer
30
+ * = max, hold expiry = min, concurrency limit = min (tightest). The concurrency
31
+ * strategy follows the primary (first) environment.
32
+ */
33
+ export declare function aggregateProtectionParams(envs: ReadonlyArray<Environment>): EffectiveProtection;
34
+ /**
35
+ * Build a synthetic `Environment` carrying the aggregated protection parameters,
36
+ * so the existing per-rule gate functions (trust/concurrency/reviewer/wait) can
37
+ * evaluate the all-must-pass holds in one pass. Branch/trigger/repo/enabled are
38
+ * already handled by `evaluateMultiEnvGates`, so they are neutralized here.
39
+ */
40
+ export declare function buildEffectiveEnvironment(primary: Environment, eff: EffectiveProtection): Environment;
41
+ /** Format a human-readable rejection reason naming the env(s) and rule(s). */
42
+ export declare function formatMultiEnvRejection(rejections: ReadonlyArray<EnvGateRejection>): string;
43
+ //# sourceMappingURL=aggregate.d.ts.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Registration-time satisfiability check for multi-environment job bindings.
3
+ *
4
+ * When a job binds several environments (`environments: [...]`), every
5
+ * environment's protection gates must pass for the job to ever dispatch
6
+ * (all-must-pass aggregation — see `aggregate.ts`). Some of those gates are
7
+ * decidable statically: if two bound environments restrict to disjoint fixed
8
+ * branch sets, no run can ever satisfy both, so the binding is provably
9
+ * unsatisfiable and should be rejected at registration rather than failing
10
+ * silently at every future dispatch.
11
+ *
12
+ * This module intersects the statically-decidable set rules (branch, trigger
13
+ * type, repository — only when every pattern is a literal, never a glob) plus
14
+ * existence and enabled across the bound environments, and reports the first
15
+ * provably-empty intersection. Any glob in a rule makes that rule undecidable,
16
+ * so it is skipped here and left to the dispatch-time catch-all
17
+ * (`evaluateMultiEnvGates`).
18
+ */
19
+ import { z } from 'zod';
20
+ import type { Environment } from '@kici-dev/engine';
21
+ /** Which decidable rule made a binding unsatisfiable. */
22
+ export declare const UnsatisfiableRule: z.ZodEnum<{
23
+ enabled: "enabled";
24
+ repo: "repo";
25
+ branch: "branch";
26
+ existence: "existence";
27
+ trigger: "trigger";
28
+ }>;
29
+ export type UnsatisfiableRule = z.infer<typeof UnsatisfiableRule>;
30
+ /** A provably-unsatisfiable multi-environment binding, naming the rule + reason. */
31
+ export interface UnsatisfiableBinding {
32
+ jobName: string;
33
+ environments: string[];
34
+ rule: UnsatisfiableRule;
35
+ message: string;
36
+ }
37
+ /**
38
+ * Returns a precise problem when the bound environments can NEVER be jointly
39
+ * satisfied (a provably-empty intersection on a decidable rule, a missing
40
+ * environment, or a disabled one), else `null`. Glob / undecidable cases return
41
+ * `null` and are caught at dispatch by `evaluateMultiEnvGates`.
42
+ *
43
+ * `envs[i]` is the resolved `Environment` for `envNames[i]` (undefined when the
44
+ * name has no environment record). Only the statically-known (non-dynamic) bound
45
+ * names should be passed — dynamic elements are unknown at registration and the
46
+ * all-must-pass semantics make the static subset's exclusivity still sound.
47
+ */
48
+ export declare function checkBindingSatisfiable(jobName: string, envs: ReadonlyArray<Environment | undefined>, envNames: readonly string[]): UnsatisfiableBinding | null;
49
+ /** Minimal lock-workflow shape needed to walk its jobs for satisfiability. */
50
+ interface SatisfiabilityLockWorkflow {
51
+ jobs?: readonly unknown[];
52
+ }
53
+ /**
54
+ * Walk every workflow's static jobs and reject the registration when a bound
55
+ * environment list is provably unsatisfiable (missing/disabled environment, or
56
+ * mutually-exclusive fixed branch/trigger/repo restrictions). Dynamic elements
57
+ * are skipped (unresolvable at registration); the all-must-pass semantics keep
58
+ * the static subset's exclusivity sound. Throws the first
59
+ * `UnsatisfiableBinding.message` so the registration route / direct helper
60
+ * surfaces it to the caller.
61
+ */
62
+ export declare function assertWorkflowsSatisfiable(workflows: ReadonlyArray<SatisfiabilityLockWorkflow>, resolveEnv: (name: string) => Promise<Environment | null>): Promise<void>;
63
+ export {};
64
+ //# sourceMappingURL=satisfiability.d.ts.map
package/dist/index.js CHANGED
@@ -1779,6 +1779,15 @@ var AdminApiClient = class {
1779
1779
  return this.request("GET", `/api/v1/admin/runs/${encodeURIComponent(runId)}/jobs${qs}`);
1780
1780
  }
1781
1781
  /**
1782
+ * Fetch the machine-first, provenance-tagged structured run result: typed
1783
+ * job DAG, per-step exit codes / durations / statuses, derived failure
1784
+ * category. Untrusted fields are envelope-tagged; secret values are never
1785
+ * returned (only secret-output key names).
1786
+ */
1787
+ async getRunStructured(runId) {
1788
+ return this.request("GET", `/api/v1/admin/runs/${encodeURIComponent(runId)}/structured`);
1789
+ }
1790
+ /**
1782
1791
  * Fetch the scrub status of the run's ephemeral key. Never returns the
1783
1792
  * key material itself — only `{ exists, createdAt }`.
1784
1793
  */
@@ -8,6 +8,8 @@ export declare function setDeclaredHostsUnreachable(value: number): void;
8
8
  export declare function setStaleRunsCurrent(value: number): void;
9
9
  interface ScalerUsageRow {
10
10
  scaler: string;
11
+ /** Backend type for this scaler (rollup dimension). `__global__` for the orchestrator-wide row. */
12
+ scalerType?: string;
11
13
  machinePool?: string;
12
14
  cpus: number;
13
15
  memBytes: number;