@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.
- package/dist/agent/dispatcher.d.ts +44 -0
- package/dist/agent/host-roster-reaper.d.ts +2 -1
- package/dist/agent/host-roster.d.ts +57 -3
- package/dist/agent/token-store.d.ts +29 -0
- package/dist/approvals/step-approval-bridge.d.ts +5 -0
- package/dist/cli/commands/source-manifest.d.ts +8 -0
- package/dist/cli/service/compose.d.ts +13 -0
- package/dist/cli/service/deploy-env.d.ts +31 -0
- package/dist/cli.js +687 -219
- package/dist/config.d.ts +6 -0
- package/dist/dashboard/needs-edges.d.ts +4 -3
- package/dist/db/migrations/047_needs_run_on.d.ts +4 -0
- package/dist/db/migrations/048_host_reboot_pending.d.ts +19 -0
- package/dist/db/migrations/049_held_runs_payload.d.ts +14 -0
- package/dist/db/migrations/050_sources_slug.d.ts +17 -0
- package/dist/db/migrations/051_binding_host_pattern.d.ts +18 -0
- package/dist/db/migrations/052_host_reach_metadata.d.ts +19 -0
- package/dist/db/migrations/053_agent_token_single_use.d.ts +17 -0
- package/dist/db/types.d.ts +50 -4
- package/dist/deployment/deployment-identity.d.ts +9 -0
- package/dist/entry-helpers.d.ts +7 -0
- package/dist/environments/binding-store.d.ts +12 -3
- package/dist/environments/held-runs.d.ts +31 -1
- package/dist/github-app-name-refresher/github-app-name-refresher.d.ts +77 -0
- package/dist/index.js +21 -2
- package/dist/metrics/prometheus.d.ts +2 -0
- package/dist/orchestrator-core.d.ts +13 -1
- package/dist/pipeline/decorating-secret-resolver.d.ts +32 -0
- package/dist/pipeline/dispatch-matched-workflow.d.ts +132 -5
- package/dist/pipeline/install-secrets-resolver.d.ts +2 -2
- package/dist/pipeline/needs-scheduler.d.ts +22 -13
- package/dist/pipeline/processor.d.ts +2 -2
- package/dist/pipeline/test-pipeline.d.ts +37 -59
- package/dist/providers/github/manifest.d.ts +25 -0
- package/dist/reporting/log-writer.d.ts +19 -0
- package/dist/routes/admin-sources.d.ts +7 -0
- package/dist/scaler/manager.d.ts +17 -0
- package/dist/secrets/secret-resolver.d.ts +33 -5
- package/dist/server.js +21664 -19907
- package/dist/sources/source-store.d.ts +4 -0
- package/dist/sources/source-validator.d.ts +2 -0
- package/dist/stale-detector/reboot-deadline-sweep.d.ts +29 -0
- package/dist/standalone.js +25263 -24075
- package/dist/ws/agent-handler.d.ts +11 -6
- package/dist/ws/bringup-api.d.ts +76 -0
- package/dist/ws/dashboard-fleet-handler.d.ts +43 -0
- package/dist/ws/dashboard-fleet-write-handler.d.ts +60 -0
- package/dist/ws/fleet-runs-on-all.d.ts +19 -0
- package/dist/ws/platform-client.d.ts +16 -1
- package/dist/ws/test-relay-handlers.d.ts +13 -2
- package/installer-image-digests.json +3 -3
- package/package.json +4 -4
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
12
|
+
run_on: string;
|
|
12
13
|
}>): Map<string, JobNeedEdge[]>;
|
|
13
14
|
//# sourceMappingURL=needs-edges.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
|
package/dist/db/types.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
490
|
-
|
|
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 `
|
|
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
|
package/dist/entry-helpers.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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,
|
|
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:
|
|
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
|