@kici-dev/orchestrator 0.1.21 → 0.1.23

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 (53) hide show
  1. package/dist/agent/dispatcher.d.ts +44 -0
  2. package/dist/agent/host-roster-reaper.d.ts +2 -1
  3. package/dist/agent/host-roster.d.ts +57 -3
  4. package/dist/agent/token-store.d.ts +29 -0
  5. package/dist/approvals/step-approval-bridge.d.ts +5 -0
  6. package/dist/cli/commands/source-manifest.d.ts +8 -0
  7. package/dist/cli/service/compose.d.ts +13 -0
  8. package/dist/cli/service/deploy-env.d.ts +31 -0
  9. package/dist/cli.js +687 -219
  10. package/dist/config.d.ts +6 -0
  11. package/dist/dashboard/needs-edges.d.ts +4 -3
  12. package/dist/db/migrations/047_needs_run_on.d.ts +4 -0
  13. package/dist/db/migrations/048_host_reboot_pending.d.ts +19 -0
  14. package/dist/db/migrations/049_held_runs_payload.d.ts +14 -0
  15. package/dist/db/migrations/050_sources_slug.d.ts +17 -0
  16. package/dist/db/migrations/051_binding_host_pattern.d.ts +18 -0
  17. package/dist/db/migrations/052_host_reach_metadata.d.ts +19 -0
  18. package/dist/db/migrations/053_agent_token_single_use.d.ts +17 -0
  19. package/dist/db/types.d.ts +50 -4
  20. package/dist/deployment/deployment-identity.d.ts +9 -0
  21. package/dist/entry-helpers.d.ts +7 -0
  22. package/dist/environments/binding-store.d.ts +12 -3
  23. package/dist/environments/held-runs.d.ts +31 -1
  24. package/dist/github-app-name-refresher/github-app-name-refresher.d.ts +77 -0
  25. package/dist/index.js +21 -2
  26. package/dist/metrics/prometheus.d.ts +2 -0
  27. package/dist/orchestrator-core.d.ts +13 -1
  28. package/dist/pipeline/decorating-secret-resolver.d.ts +32 -0
  29. package/dist/pipeline/dispatch-matched-workflow.d.ts +132 -5
  30. package/dist/pipeline/install-secrets-resolver.d.ts +2 -2
  31. package/dist/pipeline/needs-scheduler.d.ts +22 -13
  32. package/dist/pipeline/processor.d.ts +2 -2
  33. package/dist/pipeline/test-pipeline.d.ts +37 -59
  34. package/dist/providers/github/manifest.d.ts +25 -0
  35. package/dist/reporting/log-writer.d.ts +19 -0
  36. package/dist/routes/admin-sources.d.ts +7 -0
  37. package/dist/scaler/manager.d.ts +17 -0
  38. package/dist/secrets/secret-resolver.d.ts +33 -5
  39. package/dist/server.js +21664 -19907
  40. package/dist/sources/source-store.d.ts +4 -0
  41. package/dist/sources/source-validator.d.ts +2 -0
  42. package/dist/stale-detector/reboot-deadline-sweep.d.ts +29 -0
  43. package/dist/standalone.js +25263 -24075
  44. package/dist/ws/agent-handler.d.ts +11 -6
  45. package/dist/ws/bringup-api.d.ts +76 -0
  46. package/dist/ws/dashboard-fleet-handler.d.ts +43 -0
  47. package/dist/ws/dashboard-fleet-write-handler.d.ts +60 -0
  48. package/dist/ws/fleet-runs-on-all.d.ts +19 -0
  49. package/dist/ws/platform-client.d.ts +16 -1
  50. package/dist/ws/test-relay-handlers.d.ts +13 -2
  51. package/installer-image-digests.json +3 -3
  52. package/package.json +4 -4
  53. package/sbom.spdx.json +50 -50
package/dist/config.d.ts CHANGED
@@ -68,6 +68,7 @@ declare const configSchema: z.ZodObject<{
68
68
  staleDetectorScanIntervalMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
69
69
  staleDetectorThresholdMultiplier: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
70
70
  jobHeartbeatIntervalMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
71
+ githubAppNameRefreshIntervalMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
71
72
  secretKey: z.ZodOptional<z.ZodString>;
72
73
  secretKeyFile: z.ZodOptional<z.ZodString>;
73
74
  secretKeyOld: z.ZodOptional<z.ZodString>;
@@ -84,6 +85,8 @@ declare const configSchema: z.ZodObject<{
84
85
  agentTokenTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
85
86
  rosterGraceMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
86
87
  rosterTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
88
+ hostRebootDeadlineMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
89
+ orchestratorHostAgentId: z.ZodOptional<z.ZodString>;
87
90
  maxFanoutHosts: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
88
91
  eventRouterMaxChainDepth: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
89
92
  eventRouterRateLimitPerWorkflowPerMinute: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -197,11 +200,13 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
197
200
  staleDetectorScanIntervalMs: number;
198
201
  staleDetectorThresholdMultiplier: number;
199
202
  jobHeartbeatIntervalMs: number;
203
+ githubAppNameRefreshIntervalMs: number;
200
204
  pgCustomerSecrets: boolean;
201
205
  agentAuth: "token" | "none";
202
206
  agentTokenTtlMs: number;
203
207
  rosterGraceMs: number;
204
208
  rosterTtlMs: number;
209
+ hostRebootDeadlineMs: number;
205
210
  maxFanoutHosts: number;
206
211
  eventRouterMaxChainDepth: number;
207
212
  eventRouterRateLimitPerWorkflowPerMinute: number;
@@ -265,6 +270,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
265
270
  secretKeyOld?: string | undefined;
266
271
  secretKeyFileOld?: string | undefined;
267
272
  bootstrapAdminToken?: string | undefined;
273
+ orchestratorHostAgentId?: string | undefined;
268
274
  testEventFailFirstN?: string | undefined;
269
275
  otelExporterOtlpEndpoint?: string | undefined;
270
276
  clusterName?: string | undefined;
@@ -1,13 +1,14 @@
1
- import { IfFailedPolicy } from '@kici-dev/engine';
1
+ import type { ExecutionJobStatus } from '@kici-dev/engine';
2
2
  /** A single resolved upstream dependency edge for a job. */
3
3
  export interface JobNeedEdge {
4
4
  upstreamName: string;
5
- ifFailed: IfFailedPolicy;
5
+ /** Upstream terminal statuses that satisfy this edge (the run-on set). */
6
+ runOn: ExecutionJobStatus[];
6
7
  }
7
8
  /** Group raw execution_job_needs rows by downstream job_name. */
8
9
  export declare function groupNeedsByJobName(rows: ReadonlyArray<{
9
10
  job_name: string;
10
11
  upstream_name: string;
11
- if_failed: string;
12
+ run_on: string;
12
13
  }>): Map<string, JobNeedEdge[]>;
13
14
  //# sourceMappingURL=needs-edges.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=047_needs_run_on.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add `host_roster.reboot_pending_until timestamptz NULL` — the persisted
4
+ * reboot-pending flag for workflow-level host restart.
5
+ *
6
+ * Set when an agent's `restartHost()` step calls `host.requestReboot` (so it
7
+ * survives an orchestrator restart during the host's reboot window). While the
8
+ * value is in the future it (1) makes the agent's imminent disconnect an
9
+ * expected reboot rather than a recovery-fail, (2) gates the pinned-drain off
10
+ * so the post-restart job is not dispatched into the about-to-reboot box, and
11
+ * (3) clears on the next reconnect (down-then-up), releasing the held job. NULL
12
+ * = no reboot pending.
13
+ *
14
+ * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
15
+ * preserved.
16
+ */
17
+ export declare function up(db: Kysely<unknown>): Promise<void>;
18
+ export declare function down(db: Kysely<unknown>): Promise<void>;
19
+ //# sourceMappingURL=048_host_reboot_pending.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add `held_runs.payload jsonb NULL` — the drift payload captured when a
4
+ * `when: 'drift'` step-approval gate fires. Holds `{ summaryMarkdown, drift }`:
5
+ * the author's `summarize(drift)` rendering plus the structured drift blob, so
6
+ * the dashboard approval queue and the CLI render the computed diff the
7
+ * operator approves. NULL for every non-drift hold.
8
+ *
9
+ * Idempotent (`ADD COLUMN IF NOT EXISTS`); additive, so staging data is
10
+ * preserved.
11
+ */
12
+ export declare function up(db: Kysely<unknown>): Promise<void>;
13
+ export declare function down(db: Kysely<unknown>): Promise<void>;
14
+ //# sourceMappingURL=049_held_runs_payload.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add `sources.slug TEXT NULL` — the GitHub App slug (the URL-safe identifier
4
+ * GitHub assigns, e.g. `my-kici-app`).
5
+ *
6
+ * For GitHub-App sources GitHub is the source of truth for both the display
7
+ * `name` and the `slug`: both are captured at creation and kept fresh by the
8
+ * daily refresher + `kici-admin source refresh`. NULL when the identity fetch
9
+ * hasn't populated it yet (manual `--app-id` flow whose initial fetch failed,
10
+ * or a row created before the rollout).
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=050_sources_slug.d.ts.map
@@ -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
@@ -1,5 +1,5 @@
1
1
  import type { ColumnType, Generated, Insertable, Selectable, Updateable } from 'kysely';
2
- import type { ApprovalRequirement, ApproverClause, InitFailure } from '@kici-dev/engine';
2
+ import type { ApprovalRequirement, ApproverClause, InitFailure, StepApprovalPayload } from '@kici-dev/engine';
3
3
  /**
4
4
  * PostgreSQL-only database types.
5
5
  * Column names use snake_case matching the actual database column names.
@@ -486,8 +486,13 @@ export interface ExecutionJobNeedsTable {
486
486
  job_name: string;
487
487
  /** Upstream job name (the job that must complete first) */
488
488
  upstream_name: string;
489
- /** Per-edge failure policy: 'skip' (default) or 'run' */
490
- if_failed: Generated<string>;
489
+ /**
490
+ * Per-edge run-on status-set: a JSON-encoded array of upstream terminal
491
+ * statuses (ExecutionJobStatus[]) that satisfy the edge. Default
492
+ * `'["success"]'`. The downstream dispatches when the upstream's terminal
493
+ * status is a member of this set.
494
+ */
495
+ run_on: Generated<string>;
491
496
  }
492
497
  export type ExecutionJobNeeds = Selectable<ExecutionJobNeedsTable>;
493
498
  export type NewExecutionJobNeeds = Insertable<ExecutionJobNeedsTable>;
@@ -600,6 +605,12 @@ export interface EnvironmentBindingsTable {
600
605
  environment_id: string;
601
606
  /** Scope pattern for matching (e.g. workflow name glob, repo pattern) */
602
607
  scope_pattern: string;
608
+ /**
609
+ * Host selector this binding applies to (exact / glob / regex over a fan-out
610
+ * child's agentId / hostname / labels). `'**'` (the default) matches every
611
+ * host.
612
+ */
613
+ host_pattern: Generated<string>;
603
614
  /** When this binding was created */
604
615
  created_at: Generated<Date>;
605
616
  }
@@ -695,7 +706,7 @@ export interface HeldRunsTable {
695
706
  step_index: number | null;
696
707
  /**
697
708
  * What created the hold: 'environment' (mandatory env policy) | 'explicit'
698
- * (SDK `requireApproval`). Engine `TriggerSource`.
709
+ * (SDK `approval`). Engine `TriggerSource`.
699
710
  */
700
711
  trigger_source: Generated<string>;
701
712
  /**
@@ -703,6 +714,11 @@ export interface HeldRunsTable {
703
714
  * must satisfy. Null for legacy rows that predate the approval model.
704
715
  */
705
716
  approval_requirement: ColumnType<ApprovalRequirement | null, ApprovalRequirement | string | null | undefined, ApprovalRequirement | string | null>;
717
+ /**
718
+ * Drift payload `{ summaryMarkdown, drift }` captured when a `when: 'drift'`
719
+ * step-approval gate fires. Null for every non-drift hold.
720
+ */
721
+ payload: ColumnType<StepApprovalPayload | null, StepApprovalPayload | string | null | undefined, StepApprovalPayload | string | null>;
706
722
  }
707
723
  export type HeldRun = Selectable<HeldRunsTable>;
708
724
  export type NewHeldRun = Insertable<HeldRunsTable>;
@@ -853,6 +869,13 @@ export interface AgentTokenTable {
853
869
  revoked_at: Date | null;
854
870
  /** When this token expires (null = never, static tokens) */
855
871
  expires_at: Date | null;
872
+ /**
873
+ * Single-use marker for bootstrap (init-runner) tokens. Set the first time
874
+ * the token is consumed at `agent.register`; a second register is rejected.
875
+ * NULL = never consumed (every static / ephemeral token stays reusable until
876
+ * expiry).
877
+ */
878
+ consumed_at: ColumnType<Date | null, Date | string | null | undefined, Date | string | null>;
856
879
  }
857
880
  export type AgentTokenRow = Selectable<AgentTokenTable>;
858
881
  export type NewAgentTokenRow = Insertable<AgentTokenTable>;
@@ -1215,6 +1238,12 @@ export interface SourcesTable {
1215
1238
  routing_key: string;
1216
1239
  /** JSONB config (non-sensitive, e.g. { appId: '12345' }) */
1217
1240
  config: string;
1241
+ /**
1242
+ * GitHub App slug (the URL-safe identifier GitHub assigns, e.g.
1243
+ * `my-kici-app`). NULL until the GitHub identity fetch populates it. GitHub
1244
+ * is the source of truth for both `name` and `slug` on GitHub-App sources.
1245
+ */
1246
+ slug: string | null;
1218
1247
  /** Customer/org identifier for secret and environment scoping */
1219
1248
  customer_id: Generated<string>;
1220
1249
  /** When this source was created */
@@ -1616,6 +1645,23 @@ export interface HostRosterTable {
1616
1645
  */
1617
1646
  host_properties: ColumnType<Record<string, string | number | boolean>, Record<string, string | number | boolean> | string | undefined, Record<string, string | number | boolean> | string>;
1618
1647
  last_seen: ColumnType<Date, Date | string | undefined, Date | string>;
1648
+ /**
1649
+ * Reboot-pending flag for workflow-level host restart. When set to a future
1650
+ * timestamp, the host's imminent disconnect is an expected reboot (not a
1651
+ * recovery-fail) and its pinned post-restart job is held until the host
1652
+ * reconnects (down-then-up). NULL = no reboot pending.
1653
+ */
1654
+ reboot_pending_until: ColumnType<Date | null, Date | string | null, Date | string | null>;
1655
+ /**
1656
+ * Pre-agent reach metadata: how to SSH to a declared host before it has a
1657
+ * KiCI agent, for bootstrap bring-up. All nullable — a host with no reach
1658
+ * metadata cannot be bootstrapped and behaves exactly as before.
1659
+ */
1660
+ address: ColumnType<string | null, string | null | undefined, string | null>;
1661
+ ssh_user: ColumnType<string | null, string | null | undefined, string | null>;
1662
+ ssh_port: ColumnType<number | null, number | null | undefined, number | null>;
1663
+ /** Scoped-secret ref (`scope/key`) holding the bring-up private key. */
1664
+ ssh_key_secret: ColumnType<string | null, string | null | undefined, string | null>;
1619
1665
  created_at: Generated<Date>;
1620
1666
  updated_at: ColumnType<Date, Date | string | undefined, Date | string>;
1621
1667
  }
@@ -0,0 +1,9 @@
1
+ import { type DeploymentIdentity } from '@kici-dev/engine';
2
+ /**
3
+ * Read the orchestrator's deployment shape from the env the installer injects
4
+ * (`KICI_DEPLOY_MODE` / `KICI_DEPLOY_CONTAINER` / `KICI_DEPLOY_CONTAINER_RUNTIME`).
5
+ * Hand-run / dev orchestrators carry no `KICI_DEPLOY_*` env and report `unknown`.
6
+ * Container fields are kept only for the `compose` mode.
7
+ */
8
+ export declare function readDeploymentIdentity(env?: NodeJS.ProcessEnv): DeploymentIdentity;
9
+ //# sourceMappingURL=deployment-identity.d.ts.map
@@ -19,6 +19,13 @@ export interface ProviderSource {
19
19
  routingKey: string;
20
20
  name: string;
21
21
  subtype: SourceSubtype;
22
+ /**
23
+ * GitHub App slug (URL-safe identifier GitHub assigns). Only set for
24
+ * GitHub-App sources, where it propagates orchestrator → Platform → dashboard
25
+ * alongside the display `name`. Undefined for generic / universal-git / local
26
+ * sources, and for a GitHub source whose identity fetch hasn't run yet.
27
+ */
28
+ slug?: string;
22
29
  }
23
30
  /**
24
31
  * Map a generic_webhook_sources `provider_type` (plus optional `git_config`
@@ -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
  }
@@ -4,7 +4,7 @@
4
4
  * Manages the lifecycle: pending -> approved/rejected/expired.
5
5
  */
6
6
  import { type Kysely } from 'kysely';
7
- import { type ApprovalRequirement, type ApproverClause, ApprovalDecision, HoldScope, TriggerSource } from '@kici-dev/engine';
7
+ import { type ApprovalRequirement, type ApproverClause, type StepApprovalPayload, ApprovalDecision, HoldScope, TriggerSource } from '@kici-dev/engine';
8
8
  import type { Database, HeldRun, HeldRunApproval } from '../db/types.js';
9
9
  /** Status values for held runs (held_runs table). */
10
10
  export declare enum HeldRunStatus {
@@ -51,6 +51,12 @@ export interface CreateHoldData {
51
51
  * the automated release sweeps can find their rows.
52
52
  */
53
53
  holdType?: string;
54
+ /**
55
+ * Drift payload `{ summaryMarkdown, drift }` captured for a `when: 'drift'`
56
+ * step gate; persisted to `held_runs.payload` and surfaced in the dashboard
57
+ * approval queue + the CLI. Omit for non-drift holds.
58
+ */
59
+ payload?: StepApprovalPayload;
54
60
  }
55
61
  /** A single decision to record against a hold. */
56
62
  export interface RecordDecisionData {
@@ -98,8 +104,32 @@ export declare class HeldRunStore {
98
104
  * the created row.
99
105
  */
100
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;
101
115
  /** Record one approve/reject decision against a hold. */
102
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>;
103
133
  /** List the recorded decisions for a hold, oldest first. */
104
134
  listDecisions(heldRunId: string): Promise<HeldRunApproval[]>;
105
135
  /** Get a single held run by id (org-scoped). Returns null if absent. */
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Daily refresh of every GitHub source's display name + slug from GitHub.
3
+ *
4
+ * GitHub is the source of truth for a GitHub-App source's display name and
5
+ * slug. They are captured at creation, but an operator can rename the App in
6
+ * the GitHub UI afterwards — this periodic task re-fetches `GET /app` for each
7
+ * GitHub source and, when the name or slug drifted, writes the new values to
8
+ * the `sources` row. The `sources_change` DB trigger then fans the change out
9
+ * (SourceManager reload → `platformClient.updateSources()` → re-register), so
10
+ * the Platform `webhook_sources` row and the dashboard Sources tab pick up the
11
+ * new name/slug without any extra plumbing here.
12
+ *
13
+ * Lifecycle mirrors `StaleRunDetector`: `start()` runs an immediate refresh
14
+ * (so a rename made while the orchestrator was down propagates on next boot)
15
+ * then a `setInterval`; `stop()` clears it. Per-source errors are logged and
16
+ * never abort the loop.
17
+ */
18
+ /** The slice of {@link SourceStore} this module needs. */
19
+ export interface RefreshableSourceStore {
20
+ listSources(): Promise<Array<{
21
+ routing_key: string;
22
+ provider: string;
23
+ name: string;
24
+ slug: string | null;
25
+ }>>;
26
+ getSourceWithSecrets(routingKey: string): Promise<{
27
+ provider: string;
28
+ config: string;
29
+ privateKey: string;
30
+ } | null>;
31
+ updateSource(routingKey: string, updates: {
32
+ name?: string;
33
+ slug?: string | null;
34
+ }): Promise<unknown>;
35
+ }
36
+ /** Fetch a GitHub App's authoritative identity. Matches `fetchGithubAppIdentity`. */
37
+ export type FetchGithubAppIdentity = (creds: {
38
+ appId: string;
39
+ privateKey: string;
40
+ }) => Promise<{
41
+ name: string;
42
+ slug: string;
43
+ }>;
44
+ /** Outcome of a single source refresh. */
45
+ export interface RefreshResult {
46
+ routingKey: string;
47
+ changed: boolean;
48
+ oldName: string;
49
+ newName: string;
50
+ oldSlug: string | null;
51
+ newSlug: string;
52
+ }
53
+ /**
54
+ * Re-fetch one GitHub source's identity from GitHub and persist it when the
55
+ * name or slug drifted. Shared by the daily task and `kici-admin source
56
+ * refresh`. Throws for a missing or non-GitHub routing key.
57
+ */
58
+ export declare function refreshGithubSourceIdentity(sourceStore: RefreshableSourceStore, routingKey: string, fetchIdentity: FetchGithubAppIdentity): Promise<RefreshResult>;
59
+ export interface GithubAppNameRefresherDeps {
60
+ sourceStore: RefreshableSourceStore;
61
+ fetchIdentity: FetchGithubAppIdentity;
62
+ /** Refresh cadence in ms. Default cluster value: 24h (`config.githubAppNameRefreshIntervalMs`). */
63
+ scanIntervalMs: number;
64
+ }
65
+ export declare class GithubAppNameRefresher {
66
+ private readonly sourceStore;
67
+ private readonly fetchIdentity;
68
+ private readonly scanIntervalMs;
69
+ private interval;
70
+ constructor(deps: GithubAppNameRefresherDeps);
71
+ /** Immediate refresh then periodic scans. */
72
+ start(): Promise<void>;
73
+ stop(): void;
74
+ /** Refresh every GitHub source once. Per-source failures are isolated. */
75
+ refresh(): Promise<void>;
76
+ }
77
+ //# sourceMappingURL=github-app-name-refresher.d.ts.map
package/dist/index.js CHANGED
@@ -1892,6 +1892,23 @@ function createInstallationOctokit(config, installationId) {
1892
1892
  * registration and returns it on the conversion response, so both GitHub and
1893
1893
  * the Platform end up sharing the same secret with zero operator effort.
1894
1894
  */
1895
+ /**
1896
+ * Validate a self-hosted webhook URL supplied via `source add github
1897
+ * --webhook-url`. Must be a well-formed absolute `https://` URL. Returns the URL
1898
+ * verbatim on success; throws a clear error otherwise. The validated URL is
1899
+ * baked into `manifest.hook_attributes.url` as-is — KiCI adds no ingress and
1900
+ * does not receive events at it; the operator owns delivery.
1901
+ */
1902
+ function validateWebhookUrl(value) {
1903
+ let url;
1904
+ try {
1905
+ url = new URL(value);
1906
+ } catch {
1907
+ throw new Error(`--webhook-url must be a valid absolute URL: ${value}`);
1908
+ }
1909
+ if (url.protocol !== "https:") throw new Error(`--webhook-url must be an https:// URL (got ${url.protocol}//…)`);
1910
+ return value;
1911
+ }
1895
1912
  function buildGithubAppManifest(input) {
1896
1913
  return {
1897
1914
  name: input.name,
@@ -1930,6 +1947,7 @@ async function convertManifestCode(code, deps = {}) {
1930
1947
  return {
1931
1948
  appId: String(d.id),
1932
1949
  slug: d.slug,
1950
+ name: d.name,
1933
1951
  privateKey: d.pem,
1934
1952
  webhookSecret: d.webhook_secret,
1935
1953
  clientId: d.client_id,
@@ -2178,7 +2196,8 @@ async function storeAndRegister(opts, client, deps, creds) {
2178
2196
  try {
2179
2197
  return await client.post("/api/v1/admin/sources", {
2180
2198
  provider: "github",
2181
- name: opts.name,
2199
+ name: creds.name,
2200
+ slug: creds.slug,
2182
2201
  appId: creds.appId,
2183
2202
  privateKey: creds.privateKey,
2184
2203
  webhookSecret: creds.webhookSecret
@@ -2195,7 +2214,7 @@ async function storeAndRegister(opts, client, deps, creds) {
2195
2214
  }
2196
2215
  }
2197
2216
  async function runGithubManifestSetup(opts, client, deps = realManifestSetupDeps) {
2198
- const webhookUrl = await resolveWebhookUrl(client);
2217
+ const webhookUrl = opts.webhookUrl ? validateWebhookUrl(opts.webhookUrl) : await resolveWebhookUrl(client);
2199
2218
  const state = randomBytes(16).toString("hex");
2200
2219
  const createUrl = manifestCreateUrl(opts.githubOrg);
2201
2220
  const loopback = await deps.startLoopback({
@@ -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;
@@ -23,7 +23,7 @@ import { DedupCache } from './webhook/dedup.js';
23
23
  import { ObserverRegistry } from './ws/observer-registry.js';
24
24
  import { AgentMetricsAggregator } from './metrics/agent-metrics-aggregator.js';
25
25
  import { SourceLocationStore } from './app.js';
26
- import { type LabelMatcher, type PeerHeartbeat, type PeerLogsCollectRequest, type PeerToPeerMessage } from '@kici-dev/engine';
26
+ import { ExecutionJobStatus, type LabelMatcher, type PeerHeartbeat, type PeerLogsCollectRequest, type PeerToPeerMessage } from '@kici-dev/engine';
27
27
  import { ScalerManager } from './scaler/index.js';
28
28
  import type { ScalerConfig } from './scaler/index.js';
29
29
  import type { CacheStorage } from './storage/index.js';
@@ -253,6 +253,17 @@ export declare function buildUpstreamOutputsByBase(baseNames: string[], rows: Ar
253
253
  variant_label?: string | null;
254
254
  status?: string | null;
255
255
  }>): Record<string, Record<string, unknown>> | undefined;
256
+ /**
257
+ * Build the downstream `upstreamJobStatuses` map keyed by each upstream job
258
+ * row's name. A single non-fanned upstream is keyed by its base name; a
259
+ * fanned-out upstream contributes one entry per expanded child name (`base
260
+ * (child)`). The agent uses this to stamp `ctx.needs.<job>.status` (single) and
261
+ * the per-child status of group / matrix / host-fanout entries.
262
+ */
263
+ export declare function buildUpstreamStatusesByBase(rows: Array<{
264
+ job_name: string;
265
+ status?: string | null;
266
+ }>): Record<string, ExecutionJobStatus> | undefined;
256
267
  /**
257
268
  * Fold a `runsOnAll` upstream's host children into the `byHost` envelope
258
269
  * `{ byHost: { '<host>': outputs }, summary: { succeededHosts, failedHosts, outputs } }`.
@@ -302,6 +313,7 @@ export declare function buildMatrixOutputsEnvelope(baseName: string, children: A
302
313
  export declare function mergeUpstreamOutputs(db: Kysely<Database>, runId: string, jobName: string, needs: unknown, dispatchSecrets: Record<string, string> | undefined, secretKey: string): Promise<{
303
314
  mergedSecrets: Record<string, string> | undefined;
304
315
  upstreamJobOutputs: Record<string, Record<string, unknown>> | undefined;
316
+ upstreamJobStatuses: Record<string, ExecutionJobStatus> | undefined;
305
317
  }>;
306
318
  export declare function bootstrapOrchestrator(config: AppConfig, hooks: OrchestratorHooks, options?: {
307
319
  otelSdk?: {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * CLI-secret overlay for `kici run` test dispatch.
3
+ *
4
+ * Wraps the orchestrator's environment `SecretResolver` and overlays the
5
+ * developer's CLI-uploaded local secrets on top of the env-resolved secrets,
6
+ * with CLI winning on collision. Passed to the shared dispatch core via
7
+ * `ProcessingDeps.secretResolver`, so the core's secret-resolution path is
8
+ * unchanged and oblivious to "test secrets".
9
+ *
10
+ * Precedence: env-resolved secrets → CLI context for the requested environment
11
+ * → CLI flat. The CLI flat overlay is applied last so a CLI flat key always
12
+ * wins.
13
+ */
14
+ import type { SecretResolverApi, ResolvedSecretMeta } from '../secrets/secret-resolver.js';
15
+ /** Decrypted CLI-uploaded local secrets: flat keys + per-context namespaces. */
16
+ export interface CliSecrets {
17
+ flat: Record<string, string>;
18
+ contexts: Record<string, Record<string, string>>;
19
+ }
20
+ export declare class DecoratingSecretResolver implements SecretResolverApi {
21
+ private readonly base;
22
+ private readonly cli;
23
+ constructor(base: SecretResolverApi, cli: CliSecrets);
24
+ resolveForJob(orgId: string, environmentName: string): Promise<Record<string, string>>;
25
+ resolveNamed(orgId: string, scope: string, key: string, opts?: {
26
+ store?: string;
27
+ runId?: string;
28
+ jobId?: string;
29
+ }): Promise<string | null>;
30
+ resolveForJobWithMeta(orgId: string, environmentName: string): Promise<Record<string, ResolvedSecretMeta>>;
31
+ }
32
+ //# sourceMappingURL=decorating-secret-resolver.d.ts.map