@kici-dev/orchestrator 0.1.2 → 0.1.5
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 +26 -0
- package/dist/agent/ownership-tracker.d.ts +45 -3
- package/dist/audit/access-log.d.ts +7 -0
- package/dist/cli/api-client.d.ts +14 -0
- package/dist/cli/commands/cluster-name.d.ts +18 -0
- package/dist/cli/commands/db.d.ts +9 -6
- package/dist/cli/commands/org-settings.d.ts +2 -1
- package/dist/cli/commands/shared/secret-input.d.ts +46 -0
- package/dist/cli/commands/variable.d.ts +18 -0
- package/dist/cli/service/index.d.ts +1 -0
- package/dist/cli/service/launchd.d.ts +19 -0
- package/dist/cli/service/privilege.d.ts +21 -0
- package/dist/cli/wizard/orchestrator-wizard.d.ts +4 -2
- package/dist/cli.js +1056 -236
- package/dist/cluster/join-token.d.ts +21 -13
- package/dist/cluster/peer-client.d.ts +9 -0
- package/dist/config/cluster-id.d.ts +23 -0
- package/dist/config/cluster-name.d.ts +39 -0
- package/dist/config.d.ts +6 -0
- package/dist/dashboard/handler.d.ts +54 -2
- package/dist/db/migrations/019_generic_sources_change_notify.d.ts +25 -0
- package/dist/db/migrations/020_org_settings_dashboard_write_policy.d.ts +17 -0
- package/dist/db/migrations/021_check_run_tracking.d.ts +26 -0
- package/dist/db/migrations/022_scaler_manager_state.d.ts +29 -0
- package/dist/db/migrations/023_dispatch_queue_recovery_deadline.d.ts +26 -0
- package/dist/db/types.d.ts +137 -0
- package/dist/events/event-store.d.ts +9 -2
- package/dist/events/trust-store.d.ts +7 -0
- package/dist/events/types.d.ts +33 -0
- package/dist/metrics/scheduled-jobs.d.ts +26 -5
- package/dist/orchestrator-core.d.ts +7 -0
- package/dist/policy/dashboard-write-policy.d.ts +115 -0
- package/dist/queue/job-queue.d.ts +54 -1
- package/dist/reporting/check-run-tracking-store.d.ts +133 -0
- package/dist/reporting/commit-status.d.ts +87 -9
- package/dist/routes/admin-access-log.d.ts +1 -0
- package/dist/routes/admin-backends.d.ts +9 -1
- package/dist/routes/admin-cluster-name.d.ts +50 -0
- package/dist/routes/admin-db.d.ts +9 -1
- package/dist/routes/admin-environments.d.ts +10 -6
- package/dist/routes/admin-event-dlq.d.ts +1 -0
- package/dist/routes/admin-event-log.d.ts +1 -0
- package/dist/routes/admin-events.d.ts +19 -0
- package/dist/routes/admin-maintenance.d.ts +9 -1
- package/dist/routes/admin-org-settings.d.ts +10 -1
- package/dist/routes/admin-queue-execution.d.ts +1 -0
- package/dist/routes/admin-registrations.d.ts +1 -0
- package/dist/routes/admin-runs.d.ts +1 -0
- package/dist/routes/admin-scheduled-jobs.d.ts +1 -0
- package/dist/routes/admin-sources.d.ts +9 -1
- package/dist/routes/admin.d.ts +8 -0
- package/dist/scaler/manager.d.ts +38 -0
- package/dist/scaler/scaler-state-store.d.ts +102 -0
- package/dist/secrets/routing-key-scope.d.ts +43 -0
- package/dist/server.js +9846 -6884
- package/dist/stale-detector/stale-run-detector.d.ts +14 -2
- package/dist/standalone.js +6120 -3838
- package/dist/webhook/generic-sources-listener.d.ts +74 -0
- package/dist/webhook/register-source-bundle.d.ts +57 -0
- package/dist/ws/dashboard-backends-handler.d.ts +14 -0
- package/dist/ws/dashboard-env-handler.d.ts +7 -0
- package/dist/ws/dashboard-global-workflows-handler.d.ts +6 -0
- package/dist/ws/dashboard-registrations-handler.d.ts +7 -0
- package/dist/ws/platform-client.d.ts +30 -1
- package/package.json +3 -3
- package/sbom.spdx.json +40 -35
|
@@ -1,18 +1,39 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Success/failure counter for each tick of each scheduled job.
|
|
3
|
+
* Labels:
|
|
4
|
+
* - job: scheduled-job name (one of `OrchestratorScheduledJobName`)
|
|
5
|
+
* - result: success | failure
|
|
6
|
+
*/
|
|
2
7
|
export declare const jobRunsTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
|
|
3
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Histogram of per-tick duration, seconds.
|
|
10
|
+
* Labels:
|
|
11
|
+
* - job: scheduled-job name (one of `OrchestratorScheduledJobName`)
|
|
12
|
+
*/
|
|
4
13
|
export declare const jobDurationSeconds: import("@opentelemetry/api").Histogram<import("@opentelemetry/api").Attributes>;
|
|
5
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Unix timestamp (seconds) of the most recent successful tick.
|
|
16
|
+
* Labels:
|
|
17
|
+
* - job: scheduled-job name (one of `OrchestratorScheduledJobName`)
|
|
18
|
+
*/
|
|
6
19
|
export declare const jobLastSuccessTimestamp: {
|
|
7
20
|
set(labels: Record<string, string>, value: number): void;
|
|
8
21
|
reset(): void;
|
|
9
22
|
};
|
|
10
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Unix timestamp (seconds) of the most recent failed tick.
|
|
25
|
+
* Labels:
|
|
26
|
+
* - job: scheduled-job name (one of `OrchestratorScheduledJobName`)
|
|
27
|
+
*/
|
|
11
28
|
export declare const jobLastFailureTimestamp: {
|
|
12
29
|
set(labels: Record<string, string>, value: number): void;
|
|
13
30
|
reset(): void;
|
|
14
31
|
};
|
|
15
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Count of consecutive failures since last success. Resets to 0 on success.
|
|
34
|
+
* Labels:
|
|
35
|
+
* - job: scheduled-job name (one of `OrchestratorScheduledJobName`)
|
|
36
|
+
*/
|
|
16
37
|
export declare const jobConsecutiveFailures: {
|
|
17
38
|
set(labels: Record<string, string>, value: number): void;
|
|
18
39
|
reset(): void;
|
|
@@ -39,6 +39,7 @@ import type { SecretStore } from '@kici-dev/engine';
|
|
|
39
39
|
import type { AdminRouteDeps } from './routes/admin.js';
|
|
40
40
|
import { AgentTokenStore } from './agent/token-store.js';
|
|
41
41
|
import { OwnershipTracker } from './agent/ownership-tracker.js';
|
|
42
|
+
import { EventStore } from './events/event-store.js';
|
|
42
43
|
import { EventRouter } from './events/event-router.js';
|
|
43
44
|
import { EventEmitter } from './events/event-emitter.js';
|
|
44
45
|
import { TrustStore } from './events/trust-store.js';
|
|
@@ -87,6 +88,12 @@ export interface OrchestratorSubsystems {
|
|
|
87
88
|
lockFileCache: LockFileCache;
|
|
88
89
|
dedup: DedupCache;
|
|
89
90
|
eventRouter: EventRouter;
|
|
91
|
+
/**
|
|
92
|
+
* Event store for custom internal events (system + custom). Exposed
|
|
93
|
+
* here so the dashboard handler can serve the per-org DLQ surface
|
|
94
|
+
* (list / count / retry / discard) over the WS relay.
|
|
95
|
+
*/
|
|
96
|
+
eventStore: EventStore;
|
|
90
97
|
eventEmitter: EventEmitter;
|
|
91
98
|
genericSourceManager: GenericSourceManager;
|
|
92
99
|
trustStore: TrustStore;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-orchestrator policy controlling which dashboard.* write operations
|
|
3
|
+
* the orch accepts from Platform.
|
|
4
|
+
*
|
|
5
|
+
* Storage: a JSONB column on the existing `org_settings` table, keyed by
|
|
6
|
+
* `customer_id`. Empty object means every operation is enabled
|
|
7
|
+
* (permissive default at first-boot). Operators flip individual
|
|
8
|
+
* operations off via `kici-admin org-settings dashboard-writes set`.
|
|
9
|
+
*
|
|
10
|
+
* Three callers:
|
|
11
|
+
* - The kici-admin CLI mutates via `setDashboardWritePolicy`.
|
|
12
|
+
* - Mutating dashboard.* handlers check via `assertDashboardWriteAllowed`.
|
|
13
|
+
* - The Platform-bound WS publisher reads the full map via
|
|
14
|
+
* `getDashboardWritePolicy` to broadcast `orch.capabilities`.
|
|
15
|
+
*
|
|
16
|
+
* Reads are cached in-process (30 s TTL) — the policy changes
|
|
17
|
+
* infrequently and reading on every dashboard.* request is wasted IO.
|
|
18
|
+
* Writes invalidate the cache and emit a change event so the WS
|
|
19
|
+
* broadcaster can republish.
|
|
20
|
+
*/
|
|
21
|
+
import { EventEmitter } from 'node:events';
|
|
22
|
+
import { type Kysely } from 'kysely';
|
|
23
|
+
import { DashboardWriteOperation, type DashboardWritePolicyMap } from '@kici-dev/engine/protocol/dashboard-write-operations';
|
|
24
|
+
import type { Database } from '../db/types.js';
|
|
25
|
+
import type { ActorPrincipal } from '@kici-dev/engine';
|
|
26
|
+
/**
|
|
27
|
+
* Event bus for policy-change notifications. The WS publisher subscribes
|
|
28
|
+
* to `'changed'` so it can broadcast a fresh `orch.capabilities` to
|
|
29
|
+
* Platform whenever the operator flips a switch.
|
|
30
|
+
*/
|
|
31
|
+
export declare const dashboardWritePolicyEvents: EventEmitter<[never]>;
|
|
32
|
+
/**
|
|
33
|
+
* Error thrown by `assertDashboardWriteAllowed` when an operation is
|
|
34
|
+
* disabled. Carries the operation name + the descriptor's CLI hint so
|
|
35
|
+
* callers can surface a structured error to Platform / dashboard.
|
|
36
|
+
*/
|
|
37
|
+
export declare class DashboardWritePolicyDisabledError extends Error {
|
|
38
|
+
readonly operation: DashboardWriteOperation;
|
|
39
|
+
readonly cliEquivalent: string;
|
|
40
|
+
readonly code: "operation_disabled";
|
|
41
|
+
constructor(operation: DashboardWriteOperation, cliEquivalent: string);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Structured response envelope sent back to Platform when a mutating
|
|
45
|
+
* dashboard.* handler is rejected by the policy gate. Mirrors the
|
|
46
|
+
* Platform-side 403 body so the dashboard renders one consistent
|
|
47
|
+
* "operation disabled" affordance no matter which layer fires first.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildPolicyDeniedResponse(op: DashboardWriteOperation, responseType: string, requestId: string): Record<string, unknown>;
|
|
50
|
+
/**
|
|
51
|
+
* Clear the cache. Public for tests and for the rare case where an
|
|
52
|
+
* operator wants to force a fresh read after manual DB surgery.
|
|
53
|
+
*/
|
|
54
|
+
export declare function invalidateDashboardWritePolicyCache(customerId?: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Read the full policy map for a customer. Hits the in-process cache
|
|
57
|
+
* for 30 s after the first read; falls back to the DB on cache miss.
|
|
58
|
+
* Returns an empty map (everything enabled) when no `org_settings` row
|
|
59
|
+
* exists for the customer.
|
|
60
|
+
*/
|
|
61
|
+
export declare function getDashboardWritePolicy(db: Kysely<Database>, customerId: string): Promise<DashboardWritePolicyMap>;
|
|
62
|
+
/**
|
|
63
|
+
* Single-operation check. Convenience wrapper around
|
|
64
|
+
* `getDashboardWritePolicy` + the engine-side resolver.
|
|
65
|
+
*/
|
|
66
|
+
export declare function isDashboardWriteEnabled(db: Kysely<Database>, customerId: string, op: DashboardWriteOperation): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* Defense-in-depth gate for orch-side dashboard.* handlers. Throws
|
|
69
|
+
* `DashboardWritePolicyDisabledError` when the operation is disabled.
|
|
70
|
+
* Callers translate the error into the structured error envelope they
|
|
71
|
+
* send back to Platform.
|
|
72
|
+
*/
|
|
73
|
+
export declare function assertDashboardWriteAllowed(db: Kysely<Database>, customerId: string, op: DashboardWriteOperation): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Merge `updates` into the persisted policy and persist the result.
|
|
76
|
+
* Unknown keys in `updates` are rejected at the Zod schema layer
|
|
77
|
+
* before this function runs (kici-admin already validates). Any
|
|
78
|
+
* operation explicitly set to `true` is normalized away — the
|
|
79
|
+
* permissive default lives in the absence of the key, keeping the
|
|
80
|
+
* JSONB shape minimal.
|
|
81
|
+
*
|
|
82
|
+
* Each changed operation invokes the optional `onChange` callback
|
|
83
|
+
* once — the caller decides what to do with it (typically: write
|
|
84
|
+
* one `access_log` row per change).
|
|
85
|
+
*
|
|
86
|
+
* Emits `'changed'` on `dashboardWritePolicyEvents` with the new map
|
|
87
|
+
* so the WS publisher can rebroadcast capabilities.
|
|
88
|
+
*/
|
|
89
|
+
export declare function setDashboardWritePolicy(db: Kysely<Database>, customerId: string, updates: DashboardWritePolicyMap, options: {
|
|
90
|
+
actor: ActorPrincipal;
|
|
91
|
+
onChange?: (event: PolicyChangeEvent) => Promise<void>;
|
|
92
|
+
}): Promise<DashboardWritePolicyMap>;
|
|
93
|
+
/**
|
|
94
|
+
* Per-operation change event fired by `setDashboardWritePolicy` for
|
|
95
|
+
* each switch that actually flipped. The caller (admin HTTP route,
|
|
96
|
+
* test, future automation) decides what to do — typically writes one
|
|
97
|
+
* `access_log` row carrying `op`, `prior`, and `next` in `meta`.
|
|
98
|
+
*/
|
|
99
|
+
export interface PolicyChangeEvent {
|
|
100
|
+
actor: ActorPrincipal;
|
|
101
|
+
customerId: string;
|
|
102
|
+
op: DashboardWriteOperation;
|
|
103
|
+
prior: boolean;
|
|
104
|
+
next: boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Reset to the permissive defaults (everything enabled). Useful for
|
|
108
|
+
* the `kici-admin org-settings dashboard-writes reset` subcommand.
|
|
109
|
+
*/
|
|
110
|
+
export declare function resetDashboardWritePolicy(db: Kysely<Database>, customerId: string, options: {
|
|
111
|
+
actor: ActorPrincipal;
|
|
112
|
+
onChange?: (event: PolicyChangeEvent) => Promise<void>;
|
|
113
|
+
}): Promise<DashboardWritePolicyMap>;
|
|
114
|
+
export { resolveFullPolicyView } from '@kici-dev/engine/protocol/dashboard-write-operations';
|
|
115
|
+
//# sourceMappingURL=dashboard-write-policy.d.ts.map
|
|
@@ -298,8 +298,43 @@ export declare class JobQueue {
|
|
|
298
298
|
/**
|
|
299
299
|
* Mark a job as recovering (agent disconnected, within grace period).
|
|
300
300
|
* Only transitions from 'dispatched' state for safety.
|
|
301
|
+
*
|
|
302
|
+
* When `deadline` and `agentId` are provided, persists them so a
|
|
303
|
+
* replacement coord on Raft leader switch can recreate the recovery
|
|
304
|
+
* timer (via `getRecoveringJobs()` on boot) or expire the row in
|
|
305
|
+
* the leader-gated sweep (`sweepExpiredRecoveries()`).
|
|
301
306
|
*/
|
|
302
|
-
markRecovering(jobId: string): Promise<void>;
|
|
307
|
+
markRecovering(jobId: string, deadline?: Date, agentId?: string): Promise<void>;
|
|
308
|
+
/**
|
|
309
|
+
* List every job currently in `recovering` state with its persisted
|
|
310
|
+
* recovery deadline. Used at coord boot (`Dispatcher.recoverState()`)
|
|
311
|
+
* to recreate the in-memory `recoveringJobs` Map with fresh timers.
|
|
312
|
+
*
|
|
313
|
+
* Returns rows whose `recovery_deadline` is non-null (the populated
|
|
314
|
+
* subset). Recovering rows from before the migration carry NULL and
|
|
315
|
+
* are handled by the leader-gated sweep on its next pass.
|
|
316
|
+
*/
|
|
317
|
+
getRecoveringJobs(): Promise<Array<{
|
|
318
|
+
id: string;
|
|
319
|
+
runId: string;
|
|
320
|
+
agentId: string | null;
|
|
321
|
+
deadline: Date | null;
|
|
322
|
+
}>>;
|
|
323
|
+
/**
|
|
324
|
+
* Sweep every `recovering` row whose `recovery_deadline` is in the
|
|
325
|
+
* past, marking them `failed`. Returns the rows that flipped so the
|
|
326
|
+
* caller can fire the per-job `onRecoveryTimeout` hook in process.
|
|
327
|
+
*
|
|
328
|
+
* Intended for the leader-gated `Dispatcher.sweepExpiredRecoveries`
|
|
329
|
+
* tick — running on N coords would still be correct (the WHERE
|
|
330
|
+
* `status='recovering'` clause prevents double-failure) but only one
|
|
331
|
+
* needs to do the work.
|
|
332
|
+
*/
|
|
333
|
+
sweepExpiredRecoveries(now: Date): Promise<Array<{
|
|
334
|
+
id: string;
|
|
335
|
+
runId: string;
|
|
336
|
+
agentId: string | null;
|
|
337
|
+
}>>;
|
|
303
338
|
/**
|
|
304
339
|
* Mark a job as failed only if it is still in 'recovering' state.
|
|
305
340
|
* Uses optimistic concurrency to avoid failing jobs that were reclaimed.
|
|
@@ -321,6 +356,24 @@ export declare class JobQueue {
|
|
|
321
356
|
runId: string;
|
|
322
357
|
status: DispatchQueueStatus;
|
|
323
358
|
} | null>;
|
|
359
|
+
/**
|
|
360
|
+
* HA-safe ownership check. Returns true if the DB shows that
|
|
361
|
+
* `agentId` previously held `jobId` according to any of:
|
|
362
|
+
*
|
|
363
|
+
* - `status='dispatched'` AND the registry-managed bookkeeping
|
|
364
|
+
* records the agent assignment (caller-side `agentJobs` map),
|
|
365
|
+
* - `status='recovering'` AND `recovery_agent_id = <agent>` (so a
|
|
366
|
+
* replacement coord still recognises in-flight chunks), OR
|
|
367
|
+
* - the row is already terminal (`completed` / `failed` /
|
|
368
|
+
* `expired`) — late `log.chunk` chunks from the agent's drain
|
|
369
|
+
* window are accepted as benign duplicates rather than
|
|
370
|
+
* rejected.
|
|
371
|
+
*
|
|
372
|
+
* Used by `OwnershipTracker.validateAsync` so a Raft leader switch
|
|
373
|
+
* doesn't turn the next 30s of legitimate per-job chunks into a
|
|
374
|
+
* stream of ownership violations.
|
|
375
|
+
*/
|
|
376
|
+
hasAgentOwnedJob(agentId: string, jobId: string): Promise<boolean>;
|
|
324
377
|
/**
|
|
325
378
|
* Get all jobs matching a given status.
|
|
326
379
|
* Used on startup to find 'dispatched' jobs from a previous instance for recovery.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import type { Database } from '../db/types.js';
|
|
3
|
+
import type { StepProgressEntry } from './check-run-summary.js';
|
|
4
|
+
/**
|
|
5
|
+
* Composite key identifying a single check-run row.
|
|
6
|
+
*
|
|
7
|
+
* Matches the table primary key `(provider, owner, repo, sha, check_name)`.
|
|
8
|
+
* Used by the L1 in-memory cache and as the parameter shape for every
|
|
9
|
+
* store method.
|
|
10
|
+
*/
|
|
11
|
+
export interface CheckRunTrackingKey {
|
|
12
|
+
provider: string;
|
|
13
|
+
owner: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
sha: string;
|
|
16
|
+
checkName: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Snapshot of all per-key check-run state. Mirrors the columns of the
|
|
20
|
+
* `check_run_tracking` table with the in-memory shapes the consumer
|
|
21
|
+
* already uses.
|
|
22
|
+
*/
|
|
23
|
+
export interface CheckRunTrackingState {
|
|
24
|
+
/** GitHub Checks API check-run ID. Undefined when not yet created. */
|
|
25
|
+
checkRunId?: number;
|
|
26
|
+
/** Build check-run lifecycle marker. */
|
|
27
|
+
buildCreationState?: 'pending' | 'completed';
|
|
28
|
+
/** Live step-progress entries shown in the check run's `output.summary`. */
|
|
29
|
+
stepProgress: StepProgressEntry[];
|
|
30
|
+
/** Timestamp the first running-step transition was sent to GitHub. */
|
|
31
|
+
inProgressSentAt?: Date;
|
|
32
|
+
/** KiCI run this check-run belongs to. Used by `cleanupRun`. */
|
|
33
|
+
runId?: string;
|
|
34
|
+
/** Last persisted update time; powers debounce-after-failover recovery. */
|
|
35
|
+
updatedAt?: Date;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* DB persistence for `CommitStatusReporter` check-run state.
|
|
39
|
+
*
|
|
40
|
+
* Backed by the `check_run_tracking` table — one row per
|
|
41
|
+
* `(provider, owner, repo, sha, check_name)`. Replaces six in-memory
|
|
42
|
+
* `Map`s previously held inside `CommitStatusReporter`:
|
|
43
|
+
*
|
|
44
|
+
* - `checkRunIds` → `check_run_id` column
|
|
45
|
+
* - `pendingBuildCreations` → `build_creation_state` column
|
|
46
|
+
* - `stepProgress` → `step_progress_json` column
|
|
47
|
+
* - `inProgressSent` → `in_progress_sent_at` column
|
|
48
|
+
* - `runIdToKeys` → indexed `run_id` column + `listKeysByRunId`
|
|
49
|
+
*
|
|
50
|
+
* The `progressTimers` Map is intentionally NOT persisted — debounce
|
|
51
|
+
* timers are reconstructed on demand. After a coord failover the very
|
|
52
|
+
* next `updateStepProgress` either flushes immediately (debounce window
|
|
53
|
+
* elapsed) or starts a fresh timer.
|
|
54
|
+
*
|
|
55
|
+
* The consumer keeps an L1 in-memory cache in front of this store; the
|
|
56
|
+
* store itself is stateless beyond the connection it holds.
|
|
57
|
+
*/
|
|
58
|
+
export declare class CheckRunTrackingStore {
|
|
59
|
+
private readonly db;
|
|
60
|
+
constructor(db: Kysely<Database>);
|
|
61
|
+
/**
|
|
62
|
+
* Atomically set / overwrite the check-run ID for a key.
|
|
63
|
+
*
|
|
64
|
+
* Performed as an upsert so a re-issued setPending after a coord
|
|
65
|
+
* failover replaces the prior ID rather than silently leaving a row
|
|
66
|
+
* mismatched with the GitHub-side state.
|
|
67
|
+
*/
|
|
68
|
+
setCheckRunId(key: CheckRunTrackingKey, checkRunId: number): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Lookup the check-run ID for a key. Returns undefined if no row exists
|
|
71
|
+
* yet OR the row exists but the GitHub create has not finished
|
|
72
|
+
* persisting an ID (the build-creation in-flight window).
|
|
73
|
+
*/
|
|
74
|
+
getCheckRunId(key: CheckRunTrackingKey): Promise<number | undefined>;
|
|
75
|
+
/**
|
|
76
|
+
* Mark a build check-run as having an in-flight create. Returns true if
|
|
77
|
+
* this caller won the race (no prior row, or row had no in-flight state).
|
|
78
|
+
* Used to prevent a replacement coord from re-issuing a `checks.create()`
|
|
79
|
+
* against the same SHA when the original create is still pending.
|
|
80
|
+
*/
|
|
81
|
+
markBuildCreationPending(key: CheckRunTrackingKey, runId?: string): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Mark a build check-run create as complete. Idempotent.
|
|
84
|
+
*/
|
|
85
|
+
markBuildCreationComplete(key: CheckRunTrackingKey): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Replace the step-progress array for a key.
|
|
88
|
+
*/
|
|
89
|
+
setStepProgress(key: CheckRunTrackingKey, steps: StepProgressEntry[], runId?: string): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Mark the first in-progress transition as sent. Used to keep the
|
|
92
|
+
* single "did we already kick this check run into in_progress?" guard
|
|
93
|
+
* cluster-wide.
|
|
94
|
+
*/
|
|
95
|
+
markInProgressSent(key: CheckRunTrackingKey, runId?: string): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Get the full state snapshot for a key. Used by the L1 cache to
|
|
98
|
+
* hydrate on miss and by tests to verify the on-disk layout. Returns
|
|
99
|
+
* undefined when no row exists.
|
|
100
|
+
*/
|
|
101
|
+
getState(key: CheckRunTrackingKey): Promise<CheckRunTrackingState | undefined>;
|
|
102
|
+
/**
|
|
103
|
+
* Delete a single row. Returns true if the row existed.
|
|
104
|
+
*/
|
|
105
|
+
deleteRow(key: CheckRunTrackingKey): Promise<boolean>;
|
|
106
|
+
/**
|
|
107
|
+
* List every key currently tracked for a runId. Used by `cleanupRun`
|
|
108
|
+
* to reproduce the runId → keys reverse index that the in-memory map
|
|
109
|
+
* provided. Index `idx_check_run_tracking_run_id` keeps this O(matches).
|
|
110
|
+
*/
|
|
111
|
+
listKeysByRunId(runId: string): Promise<CheckRunTrackingKey[]>;
|
|
112
|
+
/**
|
|
113
|
+
* Delete every row for a runId. Mirrors the bulk-cleanup semantics of
|
|
114
|
+
* `cleanupRun` so a single call from execution-tracker prune releases
|
|
115
|
+
* all rows for the run.
|
|
116
|
+
*/
|
|
117
|
+
deleteByRunId(runId: string): Promise<number>;
|
|
118
|
+
private selectRow;
|
|
119
|
+
private upsertRow;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Convert a raw DB row to the in-memory `CheckRunTrackingState` shape.
|
|
123
|
+
* Exported for direct use from tests that bypass the store.
|
|
124
|
+
*/
|
|
125
|
+
export declare function rowToState(row: {
|
|
126
|
+
check_run_id: number | string | null;
|
|
127
|
+
build_creation_state: string | null;
|
|
128
|
+
step_progress_json: unknown;
|
|
129
|
+
in_progress_sent_at: Date | null;
|
|
130
|
+
run_id: string | null;
|
|
131
|
+
updated_at: Date;
|
|
132
|
+
}): CheckRunTrackingState;
|
|
133
|
+
//# sourceMappingURL=check-run-tracking-store.d.ts.map
|
|
@@ -29,6 +29,7 @@ import { type GitHubAppConfig } from '../providers/github/auth.js';
|
|
|
29
29
|
import type { ProviderRegistry } from '../provider-registry.js';
|
|
30
30
|
import type { StepLogBuffer } from './step-log-buffer.js';
|
|
31
31
|
import { type SourceLocationData } from './check-run-summary.js';
|
|
32
|
+
import type { CheckRunTrackingStore } from './check-run-tracking-store.js';
|
|
32
33
|
import { ExecutionJobStatus } from '@kici-dev/engine';
|
|
33
34
|
/**
|
|
34
35
|
* Dependencies for the CommitStatusReporter.
|
|
@@ -48,6 +49,15 @@ interface CommitStatusReporterDeps {
|
|
|
48
49
|
githubConfig?: GitHubAppConfig;
|
|
49
50
|
/** Step log buffer for enriched failure summaries. */
|
|
50
51
|
stepLogBuffer?: StepLogBuffer;
|
|
52
|
+
/**
|
|
53
|
+
* DB-backed tracking store. When provided, every per-key state mutation
|
|
54
|
+
* (check-run ID, step-progress array, build creation marker,
|
|
55
|
+
* in-progress-sent flag) is written through to the store so a replacement
|
|
56
|
+
* coord on Raft leader switch can recover the state. When omitted (the
|
|
57
|
+
* back-compat path used by unit tests that don't need HA correctness),
|
|
58
|
+
* the reporter operates entirely from in-memory `Map`s.
|
|
59
|
+
*/
|
|
60
|
+
trackingStore?: CheckRunTrackingStore;
|
|
51
61
|
/** Resolver for step source locations from the lock file (for annotations). */
|
|
52
62
|
getStepSourceLocations?: (workflowName: string, jobName: string) => SourceLocationData[] | undefined;
|
|
53
63
|
/**
|
|
@@ -198,16 +208,42 @@ interface UpdateStepProgressOptions {
|
|
|
198
208
|
*/
|
|
199
209
|
export declare class CommitStatusReporter {
|
|
200
210
|
private deps;
|
|
201
|
-
/**
|
|
211
|
+
/**
|
|
212
|
+
* L1 cache: composite key → check run ID.
|
|
213
|
+
*
|
|
214
|
+
* Backed by `check_run_tracking.check_run_id` when `deps.trackingStore`
|
|
215
|
+
* is wired. On a miss the cache falls through to the store; on a store
|
|
216
|
+
* miss the lookup returns undefined and the caller logs + skips.
|
|
217
|
+
*
|
|
218
|
+
* Without the store this Map IS the source of truth (single-coord
|
|
219
|
+
* deployments, unit tests).
|
|
220
|
+
*/
|
|
202
221
|
private readonly checkRunIds;
|
|
203
|
-
/**
|
|
222
|
+
/**
|
|
223
|
+
* L1 cache: in-flight build-creation promises. The DB-backed counterpart
|
|
224
|
+
* lives in `check_run_tracking.build_creation_state`; this Map is needed
|
|
225
|
+
* locally so a same-process `setBuildComplete` can await the
|
|
226
|
+
* in-progress `setBuildPending` promise (the DB column is a state
|
|
227
|
+
* marker, not an awaitable).
|
|
228
|
+
*/
|
|
204
229
|
private readonly pendingBuildCreations;
|
|
205
|
-
/**
|
|
230
|
+
/** L1 cache: step-progress entries (synced to `check_run_tracking.step_progress_json`). */
|
|
206
231
|
private readonly stepProgress;
|
|
207
|
-
/**
|
|
232
|
+
/**
|
|
233
|
+
* L1 cache: per-key debounce timers. NOT persisted — on coord failover
|
|
234
|
+
* the next update either flushes immediately (because the DB row's
|
|
235
|
+
* `updated_at` is older than the debounce window) or starts a fresh
|
|
236
|
+
* timer.
|
|
237
|
+
*/
|
|
208
238
|
private readonly progressTimers;
|
|
209
|
-
/**
|
|
239
|
+
/** L1 cache: first in-progress sent flag (synced to `check_run_tracking.in_progress_sent_at`). */
|
|
210
240
|
private readonly inProgressSent;
|
|
241
|
+
/**
|
|
242
|
+
* L1 cache: runId → set of check-run composite keys. Synced to the
|
|
243
|
+
* indexed `check_run_tracking.run_id` column so a replacement coord can
|
|
244
|
+
* still find every key for a runId at cleanup time.
|
|
245
|
+
*/
|
|
246
|
+
private readonly runIdToKeys;
|
|
211
247
|
constructor(deps: CommitStatusReporterDeps);
|
|
212
248
|
/**
|
|
213
249
|
* Update the provider registry used for per-routing-key credential lookup.
|
|
@@ -298,14 +334,56 @@ export declare class CommitStatusReporter {
|
|
|
298
334
|
*/
|
|
299
335
|
setBuildComplete(opts: SetBuildCompleteOptions): void;
|
|
300
336
|
/**
|
|
301
|
-
* Clean up step
|
|
302
|
-
* Called when execution tracker prunes
|
|
337
|
+
* Clean up step-progress entries, debounce timers, and DB rows for a
|
|
338
|
+
* completed run. Called when the execution tracker prunes the run.
|
|
339
|
+
*
|
|
340
|
+
* In-memory cleanup is synchronous; the DB cleanup is fire-and-forget
|
|
341
|
+
* because the caller (run-pruning hook) is on the response-shaping path
|
|
342
|
+
* and shouldn't block on a network round-trip. Failure logs but does
|
|
343
|
+
* not propagate.
|
|
303
344
|
*/
|
|
304
345
|
cleanupRun(runId: string): void;
|
|
305
|
-
/**
|
|
306
|
-
|
|
346
|
+
/**
|
|
347
|
+
* Hydrate the L1 caches from the DB after a leader switch (or any
|
|
348
|
+
* boot-time recovery). Called once on coord become-leader so the
|
|
349
|
+
* runIdToKeys reverse map is populated for any future cleanupRun calls
|
|
350
|
+
* without requiring a DB round-trip per cleanup. If no store is wired,
|
|
351
|
+
* this is a no-op.
|
|
352
|
+
*/
|
|
353
|
+
recoverState(): Promise<void>;
|
|
307
354
|
/** Track a check run key associated with a runId for later cleanup. */
|
|
308
355
|
private trackRunKey;
|
|
356
|
+
/**
|
|
357
|
+
* Parse a composite L1 cache key back into the (provider, owner, repo,
|
|
358
|
+
* sha, check_name) tuple used by the store. The key format is fixed by
|
|
359
|
+
* `checkRunKey()`; provider defaults to 'github' because today's
|
|
360
|
+
* reporter only writes check runs for GitHub.
|
|
361
|
+
*/
|
|
362
|
+
private parseKey;
|
|
363
|
+
/**
|
|
364
|
+
* Write-through helper: persist a check-run ID to L1 + the store.
|
|
365
|
+
* Used by `setPending` / `setBuildPending` after a successful
|
|
366
|
+
* `checks.create()`.
|
|
367
|
+
*/
|
|
368
|
+
private persistCheckRunId;
|
|
369
|
+
/**
|
|
370
|
+
* Read-through helper: look up a check-run ID. Checks L1 first, falls
|
|
371
|
+
* through to the store on miss, caches the result on hit. Returns
|
|
372
|
+
* undefined when neither layer has the ID — the caller logs + skips.
|
|
373
|
+
*/
|
|
374
|
+
private resolveCheckRunId;
|
|
375
|
+
/**
|
|
376
|
+
* Write-through helper: persist updated step-progress entries.
|
|
377
|
+
*/
|
|
378
|
+
private persistStepProgress;
|
|
379
|
+
/**
|
|
380
|
+
* Write-through helper: mark the first running-step transition as sent.
|
|
381
|
+
*/
|
|
382
|
+
private persistInProgressSent;
|
|
383
|
+
/**
|
|
384
|
+
* Write-through helper: stamp `build_creation_state = 'pending'`.
|
|
385
|
+
*/
|
|
386
|
+
private persistBuildCreationPending;
|
|
309
387
|
/**
|
|
310
388
|
* Resolve GitHub App credentials for a given routing key.
|
|
311
389
|
*
|
|
@@ -12,6 +12,14 @@ import { Hono } from 'hono';
|
|
|
12
12
|
import type { BackendSyncManager } from '@kici-dev/engine';
|
|
13
13
|
import type { BackendRegistry } from '../secrets/backend-registry.js';
|
|
14
14
|
import type { BackendHealthChecker } from '../secrets/backend-health.js';
|
|
15
|
+
import type { Role } from '../secrets/rbac.js';
|
|
16
|
+
type AdminBackendsEnv = {
|
|
17
|
+
Variables: {
|
|
18
|
+
role: Role;
|
|
19
|
+
userId: string;
|
|
20
|
+
routingKey: string | null;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
15
23
|
interface BackendRouteDeps {
|
|
16
24
|
registry: BackendRegistry;
|
|
17
25
|
healthChecker: BackendHealthChecker;
|
|
@@ -23,6 +31,6 @@ interface BackendRouteDeps {
|
|
|
23
31
|
* @param deps - Backend route dependencies (registry, health checker, sync manager)
|
|
24
32
|
* @returns Hono app with backend routes
|
|
25
33
|
*/
|
|
26
|
-
export declare function createBackendRoutes(deps: BackendRouteDeps): Hono
|
|
34
|
+
export declare function createBackendRoutes(deps: BackendRouteDeps): Hono<AdminBackendsEnv>;
|
|
27
35
|
export {};
|
|
28
36
|
//# sourceMappingURL=admin-backends.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin API routes for managing this orchestrator's cluster name
|
|
3
|
+
* (`cluster_meta.cluster_name`).
|
|
4
|
+
*
|
|
5
|
+
* The cluster name is the human-friendly identifier that surfaces in
|
|
6
|
+
* Platform's connection registry and in the dashboard's per-orch URL
|
|
7
|
+
* segment (`/orgs/:cId/orchestrators/:clusterName/...`). Operators
|
|
8
|
+
* read and rename it via `kici-admin cluster-name {get,set}`.
|
|
9
|
+
*
|
|
10
|
+
* Routes:
|
|
11
|
+
*
|
|
12
|
+
* - `GET /api/v1/admin/cluster-name` →
|
|
13
|
+
* `{ clusterName: string, looksAutoGenerated: boolean }`
|
|
14
|
+
* - `PUT /api/v1/admin/cluster-name` → body `{ name: string }`,
|
|
15
|
+
* validates via the shared `clusterNameSchema` and persists.
|
|
16
|
+
* Returns `{ clusterName, prior }`. Renames are recorded in
|
|
17
|
+
* `access_log` so the audit trail captures who changed it and when.
|
|
18
|
+
*
|
|
19
|
+
* Mutation requires `secret.write` (same posture as the rest of
|
|
20
|
+
* admin-org-settings). The orchestrator publishes the new name on the
|
|
21
|
+
* next `source.register` — the route response includes a hint so the
|
|
22
|
+
* CLI can tell the operator to reconnect to make Platform aware.
|
|
23
|
+
*/
|
|
24
|
+
import { Hono } from 'hono';
|
|
25
|
+
import type { Kysely } from 'kysely';
|
|
26
|
+
import { CLUSTER_NAME_REGEX } from '@kici-dev/engine/protocol/cluster-name';
|
|
27
|
+
import type { Database } from '../db/types.js';
|
|
28
|
+
import type { RbacEnforcer, Role } from '../secrets/rbac.js';
|
|
29
|
+
import type { AccessLogWriter } from '../audit/access-log.js';
|
|
30
|
+
interface ClusterNameRouteDeps {
|
|
31
|
+
db: Kysely<Database>;
|
|
32
|
+
rbac: RbacEnforcer;
|
|
33
|
+
/**
|
|
34
|
+
* Optional — when wired, each rename emits one `access_log` row
|
|
35
|
+
* (`cluster_name.update`) with `orgId=null` carrying the prior + new
|
|
36
|
+
* value in `meta`. Cluster-name is orch-scoped, not org-scoped, so
|
|
37
|
+
* the row's `orgId` is null by design.
|
|
38
|
+
*/
|
|
39
|
+
accessLog?: AccessLogWriter;
|
|
40
|
+
}
|
|
41
|
+
type AdminEnv = {
|
|
42
|
+
Variables: {
|
|
43
|
+
role: Role;
|
|
44
|
+
userId: string;
|
|
45
|
+
routingKey: string | null;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export declare function createClusterNameRoutes(deps: ClusterNameRouteDeps): Hono<AdminEnv>;
|
|
49
|
+
export { CLUSTER_NAME_REGEX };
|
|
50
|
+
//# sourceMappingURL=admin-cluster-name.d.ts.map
|
|
@@ -11,10 +11,18 @@
|
|
|
11
11
|
import { Hono } from 'hono';
|
|
12
12
|
import type { Kysely } from 'kysely';
|
|
13
13
|
import type pg from 'pg';
|
|
14
|
+
import type { Role } from '../secrets/rbac.js';
|
|
14
15
|
interface DbRouteDeps {
|
|
15
16
|
db: Kysely<any>;
|
|
16
17
|
pool: pg.Pool;
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
type AdminDbEnv = {
|
|
20
|
+
Variables: {
|
|
21
|
+
role: Role;
|
|
22
|
+
userId: string;
|
|
23
|
+
routingKey: string | null;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export declare function createDbRoutes(deps: DbRouteDeps): Hono<AdminDbEnv>;
|
|
19
27
|
export {};
|
|
20
28
|
//# sourceMappingURL=admin-db.d.ts.map
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Admin API routes for environment management.
|
|
3
3
|
*
|
|
4
|
-
* POST /api/v1/admin/environments
|
|
5
|
-
* POST /api/v1/admin/environments/:name/bind
|
|
6
|
-
* PATCH /api/v1/admin/environments/:name/policy
|
|
7
|
-
* GET /api/v1/admin/environments?orgId=<id>
|
|
8
|
-
* GET /api/v1/admin/environments/:name?orgId=<id>
|
|
9
|
-
* POST /api/v1/admin/environments/templates
|
|
4
|
+
* POST /api/v1/admin/environments — create (upsert)
|
|
5
|
+
* POST /api/v1/admin/environments/:name/bind — bind a scope pattern
|
|
6
|
+
* PATCH /api/v1/admin/environments/:name/policy — update policy fields
|
|
7
|
+
* GET /api/v1/admin/environments?orgId=<id> — list environments
|
|
8
|
+
* GET /api/v1/admin/environments/:name?orgId=<id> — show env + vars + bindings
|
|
9
|
+
* POST /api/v1/admin/environments/templates — create/update a template
|
|
10
|
+
* GET /api/v1/admin/environments/:name/variables?orgId=<id> — list org-level variables
|
|
11
|
+
* PUT /api/v1/admin/environments/:name/variables/:key?orgId=<id> — upsert variable
|
|
12
|
+
* DELETE /api/v1/admin/environments/:name/variables/:key?orgId=<id> — delete variable
|
|
10
13
|
*
|
|
11
14
|
* Backs the `kici-admin environment` dual-mode CLI. Offline (direct-DB) mode
|
|
12
15
|
* bypasses this router entirely — the CLI calls `*Direct` helpers from
|
|
@@ -29,6 +32,7 @@ type AdminEnvEnv = {
|
|
|
29
32
|
Variables: {
|
|
30
33
|
role: Role;
|
|
31
34
|
userId: string;
|
|
35
|
+
routingKey: string | null;
|
|
32
36
|
};
|
|
33
37
|
};
|
|
34
38
|
/**
|