@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.
Files changed (66) hide show
  1. package/dist/agent/dispatcher.d.ts +26 -0
  2. package/dist/agent/ownership-tracker.d.ts +45 -3
  3. package/dist/audit/access-log.d.ts +7 -0
  4. package/dist/cli/api-client.d.ts +14 -0
  5. package/dist/cli/commands/cluster-name.d.ts +18 -0
  6. package/dist/cli/commands/db.d.ts +9 -6
  7. package/dist/cli/commands/org-settings.d.ts +2 -1
  8. package/dist/cli/commands/shared/secret-input.d.ts +46 -0
  9. package/dist/cli/commands/variable.d.ts +18 -0
  10. package/dist/cli/service/index.d.ts +1 -0
  11. package/dist/cli/service/launchd.d.ts +19 -0
  12. package/dist/cli/service/privilege.d.ts +21 -0
  13. package/dist/cli/wizard/orchestrator-wizard.d.ts +4 -2
  14. package/dist/cli.js +1056 -236
  15. package/dist/cluster/join-token.d.ts +21 -13
  16. package/dist/cluster/peer-client.d.ts +9 -0
  17. package/dist/config/cluster-id.d.ts +23 -0
  18. package/dist/config/cluster-name.d.ts +39 -0
  19. package/dist/config.d.ts +6 -0
  20. package/dist/dashboard/handler.d.ts +54 -2
  21. package/dist/db/migrations/019_generic_sources_change_notify.d.ts +25 -0
  22. package/dist/db/migrations/020_org_settings_dashboard_write_policy.d.ts +17 -0
  23. package/dist/db/migrations/021_check_run_tracking.d.ts +26 -0
  24. package/dist/db/migrations/022_scaler_manager_state.d.ts +29 -0
  25. package/dist/db/migrations/023_dispatch_queue_recovery_deadline.d.ts +26 -0
  26. package/dist/db/types.d.ts +137 -0
  27. package/dist/events/event-store.d.ts +9 -2
  28. package/dist/events/trust-store.d.ts +7 -0
  29. package/dist/events/types.d.ts +33 -0
  30. package/dist/metrics/scheduled-jobs.d.ts +26 -5
  31. package/dist/orchestrator-core.d.ts +7 -0
  32. package/dist/policy/dashboard-write-policy.d.ts +115 -0
  33. package/dist/queue/job-queue.d.ts +54 -1
  34. package/dist/reporting/check-run-tracking-store.d.ts +133 -0
  35. package/dist/reporting/commit-status.d.ts +87 -9
  36. package/dist/routes/admin-access-log.d.ts +1 -0
  37. package/dist/routes/admin-backends.d.ts +9 -1
  38. package/dist/routes/admin-cluster-name.d.ts +50 -0
  39. package/dist/routes/admin-db.d.ts +9 -1
  40. package/dist/routes/admin-environments.d.ts +10 -6
  41. package/dist/routes/admin-event-dlq.d.ts +1 -0
  42. package/dist/routes/admin-event-log.d.ts +1 -0
  43. package/dist/routes/admin-events.d.ts +19 -0
  44. package/dist/routes/admin-maintenance.d.ts +9 -1
  45. package/dist/routes/admin-org-settings.d.ts +10 -1
  46. package/dist/routes/admin-queue-execution.d.ts +1 -0
  47. package/dist/routes/admin-registrations.d.ts +1 -0
  48. package/dist/routes/admin-runs.d.ts +1 -0
  49. package/dist/routes/admin-scheduled-jobs.d.ts +1 -0
  50. package/dist/routes/admin-sources.d.ts +9 -1
  51. package/dist/routes/admin.d.ts +8 -0
  52. package/dist/scaler/manager.d.ts +38 -0
  53. package/dist/scaler/scaler-state-store.d.ts +102 -0
  54. package/dist/secrets/routing-key-scope.d.ts +43 -0
  55. package/dist/server.js +9846 -6884
  56. package/dist/stale-detector/stale-run-detector.d.ts +14 -2
  57. package/dist/standalone.js +6120 -3838
  58. package/dist/webhook/generic-sources-listener.d.ts +74 -0
  59. package/dist/webhook/register-source-bundle.d.ts +57 -0
  60. package/dist/ws/dashboard-backends-handler.d.ts +14 -0
  61. package/dist/ws/dashboard-env-handler.d.ts +7 -0
  62. package/dist/ws/dashboard-global-workflows-handler.d.ts +6 -0
  63. package/dist/ws/dashboard-registrations-handler.d.ts +7 -0
  64. package/dist/ws/platform-client.d.ts +30 -1
  65. package/package.json +3 -3
  66. package/sbom.spdx.json +40 -35
@@ -1,18 +1,39 @@
1
- /** Success/failure counter for each tick of each scheduled job. */
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
- /** Histogram of per-tick duration, seconds. */
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
- /** Unix timestamp (seconds) of the most recent successful tick. */
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
- /** Unix timestamp (seconds) of the most recent failed tick. */
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
- /** Count of consecutive failures since last success. Resets to 0 on success. */
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
- /** Map from composite key to check run ID for subsequent updates. */
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
- /** In-flight build check run creation promises, keyed by composite key. */
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
- /** Step progress entries per check run key. */
230
+ /** L1 cache: step-progress entries (synced to `check_run_tracking.step_progress_json`). */
206
231
  private readonly stepProgress;
207
- /** Debounce timers for in_progress updates per check run key. */
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
- /** Track whether the first in_progress transition has been sent per check run key. */
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 progress entries and pending timers for a completed run.
302
- * Called when execution tracker prunes a run from memory.
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
- /** Map from runId to check run keys for cleanup. */
306
- private readonly runIdToKeys;
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
  *
@@ -27,6 +27,7 @@ type AdminEnv = {
27
27
  Variables: {
28
28
  role: Role;
29
29
  userId: string;
30
+ routingKey: string | null;
30
31
  };
31
32
  };
32
33
  export declare function createAdminAccessLogRoutes(deps: AdminAccessLogRoutesDeps): Hono<AdminEnv>;
@@ -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
- export declare function createDbRoutes(deps: DbRouteDeps): Hono;
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 — 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
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
  /**
@@ -32,6 +32,7 @@ type AdminEnv = {
32
32
  Variables: {
33
33
  role: Role;
34
34
  userId: string;
35
+ routingKey: string | null;
35
36
  };
36
37
  };
37
38
  export declare function createAdminEventDlqRoutes(deps: AdminEventDlqRoutesDeps): Hono<AdminEnv>;
@@ -35,6 +35,7 @@ type AdminEnv = {
35
35
  Variables: {
36
36
  role: Role;
37
37
  userId: string;
38
+ routingKey: string | null;
38
39
  };
39
40
  };
40
41
  export declare function createAdminEventLogRoutes(deps: AdminEventLogRoutesDeps): Hono<AdminEnv>;