@kici-dev/orchestrator 0.1.3 → 0.1.6

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 (73) hide show
  1. package/dist/agent/dispatcher.d.ts +26 -0
  2. package/dist/agent/ownership-tracker.d.ts +45 -3
  3. package/dist/app.d.ts +9 -3
  4. package/dist/audit/access-log.d.ts +7 -0
  5. package/dist/cli/api-client.d.ts +14 -0
  6. package/dist/cli/commands/cluster-name.d.ts +18 -0
  7. package/dist/cli/commands/db.d.ts +9 -6
  8. package/dist/cli/commands/org-settings.d.ts +2 -1
  9. package/dist/cli/commands/shared/secret-input.d.ts +46 -0
  10. package/dist/cli/commands/variable.d.ts +18 -0
  11. package/dist/cli/service/index.d.ts +1 -0
  12. package/dist/cli/service/launchd.d.ts +19 -0
  13. package/dist/cli/service/privilege.d.ts +21 -0
  14. package/dist/cli/wizard/orchestrator-wizard.d.ts +4 -2
  15. package/dist/cli.js +1074 -225
  16. package/dist/cluster/coordinator.d.ts +3 -3
  17. package/dist/cluster/join-token.d.ts +21 -13
  18. package/dist/cluster/peer-client.d.ts +9 -0
  19. package/dist/config/cluster-id.d.ts +23 -0
  20. package/dist/config/cluster-name.d.ts +39 -0
  21. package/dist/config.d.ts +8 -0
  22. package/dist/dashboard/handler.d.ts +54 -2
  23. package/dist/db/migrations/019_generic_sources_change_notify.d.ts +25 -0
  24. package/dist/db/migrations/020_org_settings_dashboard_write_policy.d.ts +17 -0
  25. package/dist/db/migrations/021_check_run_tracking.d.ts +26 -0
  26. package/dist/db/migrations/022_scaler_manager_state.d.ts +29 -0
  27. package/dist/db/migrations/023_dispatch_queue_recovery_deadline.d.ts +26 -0
  28. package/dist/db/types.d.ts +137 -0
  29. package/dist/events/event-store.d.ts +9 -2
  30. package/dist/events/trust-store.d.ts +7 -0
  31. package/dist/events/types.d.ts +33 -0
  32. package/dist/metrics/scheduled-jobs.d.ts +26 -5
  33. package/dist/orchestrator-core.d.ts +16 -2
  34. package/dist/pipeline/processor.d.ts +2 -2
  35. package/dist/pipeline/rerun.d.ts +2 -2
  36. package/dist/pipeline/test-pipeline.d.ts +2 -2
  37. package/dist/policy/dashboard-write-policy.d.ts +115 -0
  38. package/dist/queue/job-queue.d.ts +54 -1
  39. package/dist/reporting/{commit-status.d.ts → check-run-reporter.d.ts} +94 -16
  40. package/dist/reporting/check-run-tracking-store.d.ts +133 -0
  41. package/dist/routes/admin-access-log.d.ts +1 -0
  42. package/dist/routes/admin-backends.d.ts +9 -1
  43. package/dist/routes/admin-cluster-name.d.ts +50 -0
  44. package/dist/routes/admin-db.d.ts +9 -1
  45. package/dist/routes/admin-environments.d.ts +10 -6
  46. package/dist/routes/admin-event-dlq.d.ts +1 -0
  47. package/dist/routes/admin-event-log.d.ts +1 -0
  48. package/dist/routes/admin-events.d.ts +19 -0
  49. package/dist/routes/admin-maintenance.d.ts +9 -1
  50. package/dist/routes/admin-org-settings.d.ts +10 -1
  51. package/dist/routes/admin-queue-execution.d.ts +1 -0
  52. package/dist/routes/admin-registrations.d.ts +1 -0
  53. package/dist/routes/admin-runs.d.ts +1 -0
  54. package/dist/routes/admin-scheduled-jobs.d.ts +1 -0
  55. package/dist/routes/admin-sources.d.ts +24 -1
  56. package/dist/routes/admin.d.ts +22 -0
  57. package/dist/scaler/manager.d.ts +38 -0
  58. package/dist/scaler/scaler-state-store.d.ts +102 -0
  59. package/dist/secrets/routing-key-scope.d.ts +43 -0
  60. package/dist/server.js +10098 -6961
  61. package/dist/sources/build-platform-sources.d.ts +26 -0
  62. package/dist/sources/source-manager.d.ts +14 -0
  63. package/dist/stale-detector/stale-run-detector.d.ts +17 -5
  64. package/dist/standalone.js +7525 -5172
  65. package/dist/webhook/generic-sources-listener.d.ts +89 -0
  66. package/dist/webhook/register-source-bundle.d.ts +57 -0
  67. package/dist/ws/dashboard-backends-handler.d.ts +14 -0
  68. package/dist/ws/dashboard-env-handler.d.ts +7 -0
  69. package/dist/ws/dashboard-global-workflows-handler.d.ts +6 -0
  70. package/dist/ws/dashboard-registrations-handler.d.ts +7 -0
  71. package/dist/ws/platform-client.d.ts +57 -2
  72. package/package.json +3 -3
  73. package/sbom.spdx.json +40 -35
@@ -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 `CheckRunReporter` 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 `CheckRunReporter`:
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
@@ -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>;
@@ -13,6 +13,9 @@ import type { GenericSourceManager } from '../webhook/generic-sources.js';
13
13
  import type { TrustStore } from '../events/trust-store.js';
14
14
  import type { TokenManager } from '../secrets/token-manager.js';
15
15
  import type { RbacEnforcer, Role } from '../secrets/rbac.js';
16
+ import type { AppConfig } from '../config.js';
17
+ import type { ProviderRegistry } from '../provider-registry.js';
18
+ import type { SecretResolver } from '../secrets/secret-resolver.js';
16
19
  /**
17
20
  * Dependencies for admin event routes.
18
21
  */
@@ -21,6 +24,21 @@ interface AdminEventRouteDeps {
21
24
  trustStore: TrustStore;
22
25
  tokenManager: TokenManager;
23
26
  rbac: RbacEnforcer;
27
+ /**
28
+ * The in-process bundle registry. The POST /generic-sources handler
29
+ * registers an internal / universal-git bundle into this registry
30
+ * immediately after the source row lands in the DB, so the next
31
+ * webhook against that source resolves the right normalizer without
32
+ * waiting for an orchestrator restart.
33
+ */
34
+ providerRegistry: ProviderRegistry;
35
+ /** Needed by `registerProviderBundleForSource` to gate internal-bundle
36
+ * registration on `canServeGenericProviderType` and read the
37
+ * `internalProviderRepoPath` / `internalProviderCloneUrl` config. */
38
+ config: AppConfig;
39
+ /** Required for universal-git source registration — `null` is allowed;
40
+ * rows with `git_config` are skipped + metric-bumped in that case. */
41
+ secretResolver: SecretResolver | null;
24
42
  /**
25
43
  * Optional — when provided, the `POST /api/v1/admin/events/emit` route is
26
44
  * mounted so operators can INSERT into `kici_events` + `pg_notify` via HTTP.
@@ -33,6 +51,7 @@ type AdminEventEnv = {
33
51
  Variables: {
34
52
  role: Role;
35
53
  userId: string;
54
+ routingKey: string | null;
36
55
  };
37
56
  };
38
57
  /**
@@ -14,9 +14,17 @@
14
14
  */
15
15
  import { Hono } from 'hono';
16
16
  import type { Kysely } from 'kysely';
17
+ import type { Role } from '../secrets/rbac.js';
17
18
  interface MaintenanceRouteDeps {
18
19
  db: Kysely<any>;
19
20
  }
20
- export declare function createMaintenanceRoutes(deps: MaintenanceRouteDeps): Hono;
21
+ type AdminMaintenanceEnv = {
22
+ Variables: {
23
+ role: Role;
24
+ userId: string;
25
+ routingKey: string | null;
26
+ };
27
+ };
28
+ export declare function createMaintenanceRoutes(deps: MaintenanceRouteDeps): Hono<AdminMaintenanceEnv>;
21
29
  export {};
22
30
  //# sourceMappingURL=admin-maintenance.d.ts.map
@@ -11,11 +11,20 @@
11
11
  */
12
12
  import { Hono } from 'hono';
13
13
  import { type Kysely } from 'kysely';
14
+ import { DashboardWriteOperation } from '@kici-dev/engine/protocol/dashboard-write-operations';
14
15
  import type { Database } from '../db/types.js';
15
16
  import type { RbacEnforcer, Role } from '../secrets/rbac.js';
17
+ import type { AccessLogWriter } from '../audit/access-log.js';
16
18
  interface OrgSettingsRouteDeps {
17
19
  db: Kysely<Database>;
18
20
  rbac: RbacEnforcer;
21
+ /**
22
+ * Optional — when wired, each `dashboard_write_policy` flip emits one
23
+ * `access_log` row (`org_settings.dashboard_write_policy.update`)
24
+ * carrying the operation name + prior/next state in `actor_meta`.
25
+ * Reset calls additionally stamp `reset: true`.
26
+ */
27
+ accessLog?: AccessLogWriter;
19
28
  }
20
29
  type AdminEnv = {
21
30
  Variables: {
@@ -25,5 +34,5 @@ type AdminEnv = {
25
34
  };
26
35
  };
27
36
  export declare function createOrgSettingsRoutes(deps: OrgSettingsRouteDeps): Hono<AdminEnv>;
28
- export {};
37
+ export { DashboardWriteOperation };
29
38
  //# sourceMappingURL=admin-org-settings.d.ts.map
@@ -20,6 +20,7 @@ type AdminQEEnv = {
20
20
  Variables: {
21
21
  role: Role;
22
22
  userId: string;
23
+ routingKey: string | null;
23
24
  };
24
25
  };
25
26
  export declare function createAdminQueueExecutionRoutes(deps: AdminQueueExecutionRoutesDeps): Hono<AdminQEEnv>;
@@ -24,6 +24,7 @@ type AdminRegEnv = {
24
24
  Variables: {
25
25
  role: Role;
26
26
  userId: string;
27
+ routingKey: string | null;
27
28
  };
28
29
  };
29
30
  /**
@@ -44,6 +44,7 @@ type AdminRunEnv = {
44
44
  Variables: {
45
45
  role: Role;
46
46
  userId: string;
47
+ routingKey: string | null;
47
48
  };
48
49
  };
49
50
  /**
@@ -29,6 +29,7 @@ type AdminEnv = {
29
29
  Variables: {
30
30
  role: Role;
31
31
  userId: string;
32
+ routingKey: string | null;
32
33
  };
33
34
  };
34
35
  export declare function createAdminScheduledJobsRoutes(deps: AdminScheduledJobsRoutesDeps): Hono<AdminEnv>;
@@ -10,9 +10,32 @@
10
10
  */
11
11
  import { Hono } from 'hono';
12
12
  import type { SourceStore } from '../sources/source-store.js';
13
+ import type { Role } from '../secrets/rbac.js';
13
14
  interface SourceRouteDeps {
14
15
  sourceStore: SourceStore;
16
+ /**
17
+ * Resolve the public webhook URL for a freshly added source so the CLI can
18
+ * print it. Platform/hybrid mode registers the source with the Platform and
19
+ * reads the URL from the `source.register.ack`; independent mode returns a
20
+ * null URL with an explanatory `webhookNote`. Omitted in deployments with no
21
+ * resolver wired (the route then returns `webhookUrl: null`).
22
+ */
23
+ resolveSourceWebhookUrl?: (params: {
24
+ routingKey: string;
25
+ provider: string;
26
+ sourceId: string;
27
+ }) => Promise<{
28
+ webhookUrl: string | null;
29
+ webhookNote?: string;
30
+ }>;
15
31
  }
16
- export declare function createSourceRoutes(deps: SourceRouteDeps): Hono;
32
+ type AdminSourcesEnv = {
33
+ Variables: {
34
+ role: Role;
35
+ userId: string;
36
+ routingKey: string | null;
37
+ };
38
+ };
39
+ export declare function createSourceRoutes(deps: SourceRouteDeps): Hono<AdminSourcesEnv>;
17
40
  export {};
18
41
  //# sourceMappingURL=admin-sources.d.ts.map
@@ -23,6 +23,7 @@ import type { BackendHealthChecker } from '../secrets/backend-health.js';
23
23
  import type { BackendSyncManager } from '@kici-dev/engine';
24
24
  import type { Kysely } from 'kysely';
25
25
  import type pg from 'pg';
26
+ import type { AccessLogWriter } from '../audit/access-log.js';
26
27
  /**
27
28
  * Dependencies for admin API routes.
28
29
  */
@@ -62,6 +63,20 @@ export interface AdminRouteDeps {
62
63
  broadcastAgentTokenRevoke?: (tokenId: string) => void;
63
64
  /** Optional -- for source management endpoints. */
64
65
  sourceStore?: SourceStore;
66
+ /**
67
+ * Optional -- resolves the public webhook URL for a newly added source so
68
+ * `kici-admin source add` can print it. Wired in platform/hybrid mode to
69
+ * register-and-await the Platform ack; independent mode returns a null URL
70
+ * with a note.
71
+ */
72
+ resolveSourceWebhookUrl?: (params: {
73
+ routingKey: string;
74
+ provider: string;
75
+ sourceId: string;
76
+ }) => Promise<{
77
+ webhookUrl: string | null;
78
+ webhookNote?: string;
79
+ }>;
65
80
  /** Optional -- for DB migration endpoints. */
66
81
  db?: Kysely<any>;
67
82
  /** Optional -- for DB migration endpoints. */
@@ -83,6 +98,13 @@ export interface AdminRouteDeps {
83
98
  * response reports `reEncryptedConfigs: 0`.
84
99
  */
85
100
  sharedStore?: SharedConfigStore;
101
+ /**
102
+ * Optional -- attribution writer for routes that emit an `access_log`
103
+ * row directly (today: org-settings dashboard-write policy flips). When
104
+ * unset, those routes execute the mutation without recording — the
105
+ * write is best-effort, never gating.
106
+ */
107
+ accessLog?: AccessLogWriter;
86
108
  }
87
109
  /** Hono env type for admin routes with context variables. */
88
110
  type AdminEnv = {
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import type { ResourceRequest } from '@kici-dev/engine';
10
10
  import type { ScalerBackend, ScalerConfig, ScaleResult, ScalerEvent, ResourceCap, ValidationResult } from './types.js';
11
+ import type { ScalerStateStore, ScalerStateRecovery } from './scaler-state-store.js';
11
12
  /**
12
13
  * Resolved per-job resource amounts (cpus + bytes) for both `requests` and
13
14
  * `limits`. The scaler manager produces this from the job's declared resources
@@ -125,6 +126,14 @@ export declare class ScalerManager {
125
126
  */
126
127
  private readonly onScalerEvent?;
127
128
  private readonly warmPool;
129
+ /**
130
+ * Optional DB-backed state store. When wired (production path), every
131
+ * mutation to `spawningAgents` / `agentJobCorrelation` / `reservations`
132
+ * is write-through-cached to Postgres so a coord crash mid-spawn no
133
+ * longer orphans agents, strands reservations, or loses correlation.
134
+ * Unit tests can omit the store and operate from in-memory Maps only.
135
+ */
136
+ private readonly stateStore?;
128
137
  constructor(deps: {
129
138
  config: ScalerConfig;
130
139
  backends: Array<{
@@ -133,6 +142,11 @@ export declare class ScalerManager {
133
142
  }>;
134
143
  /** Callback for relaying scaler events with runId/jobId context. */
135
144
  onScalerEvent?: (runId: string, jobId: string, event: ScalerEvent) => void;
145
+ /**
146
+ * Optional DB-backed state store. Tests omit it; production wires it
147
+ * up via the orchestrator-core bootstrap.
148
+ */
149
+ stateStore?: ScalerStateStore;
136
150
  /**
137
151
  * Optional machine-ledger options. When `machinePools` are configured,
138
152
  * the manager initializes a `MachineLedger` keyed off this directory and
@@ -316,6 +330,30 @@ export declare class ScalerManager {
316
330
  * entries would leak in spawningAgents forever.
317
331
  */
318
332
  private pruneStaleSpawningEntries;
333
+ private persistSpawningAgent;
334
+ private deleteSpawningAgentFromStore;
335
+ private persistReservation;
336
+ private deleteReservationFromStore;
337
+ private persistAgentJob;
338
+ private deleteAgentJobFromStore;
339
+ /**
340
+ * Hydrate the in-memory Maps from the DB-backed state store after a
341
+ * coord boot or Raft leader switch. Reconstructs:
342
+ *
343
+ * - `spawningAgents` (with `boundJobId` preserved for eager-dispatch on register)
344
+ * - `agentJobCorrelation` (so scaler-lifecycle events route correctly)
345
+ * - `reservations` + `perScalerUsage` (so the cap-check critical
346
+ * section reflects the cluster-wide truth, not the local empty
347
+ * starting state)
348
+ *
349
+ * The `globalUsage` counter is recomputed from `perScalerUsage` to
350
+ * keep the cap math consistent. `eventBuffer` is NOT restored — events
351
+ * emitted by the previous coord before correlation are lost (see
352
+ * wishlist for the rationale).
353
+ *
354
+ * No-op when no store is wired (unit-test path).
355
+ */
356
+ recoverState(): Promise<ScalerStateRecovery>;
319
357
  private generateAgentId;
320
358
  /**
321
359
  * Start log forwarding for a scaler-managed agent if its backend supports LogCapture.
@@ -0,0 +1,102 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { Database } from '../db/types.js';
3
+ import type { ScalerEvent } from './types.js';
4
+ /**
5
+ * Snapshot of a spawning-agent record. Mirrors the row shape in
6
+ * `scaler_spawning_agents`.
7
+ */
8
+ export interface SpawningAgentSnapshot {
9
+ agentId: string;
10
+ scalerName: string;
11
+ labelSet: string[];
12
+ runId?: string;
13
+ jobId?: string;
14
+ boundJobId?: string;
15
+ spawnedAt: Date;
16
+ }
17
+ /**
18
+ * Snapshot of an agent-job correlation. Mirrors the row shape in
19
+ * `scaler_agent_jobs`.
20
+ */
21
+ export interface AgentJobCorrelationSnapshot {
22
+ agentId: string;
23
+ runId: string;
24
+ jobId: string;
25
+ }
26
+ /**
27
+ * Snapshot of a resource reservation. Mirrors the row shape in
28
+ * `scaler_reservations`.
29
+ */
30
+ export interface ReservationSnapshot {
31
+ agentId: string;
32
+ scalerName: string;
33
+ cpus: number;
34
+ memBytes: number;
35
+ }
36
+ /**
37
+ * DB persistence for `ScalerManager` HA-critical state.
38
+ *
39
+ * Backed by three tables — `scaler_spawning_agents`, `scaler_agent_jobs`,
40
+ * `scaler_reservations` — so a Raft leader switch / coord crash no
41
+ * longer:
42
+ *
43
+ * - orphans an agent that is mid-spawn (lost `boundJobId` → eager
44
+ * dispatch silently downgraded to a generic queue drain),
45
+ * - strands a reservation (resource counted as used until the agent's
46
+ * backend GC eventually disconnects, minutes later),
47
+ * - drops the agent → run/job correlation (execution-tracker loses
48
+ * scaler-lifecycle events emitted by the new coord).
49
+ *
50
+ * The consumer keeps the in-memory Maps as L1 caches. On boot /
51
+ * become-leader the caches are hydrated via `recoverState()`.
52
+ *
53
+ * `perScalerUsage` / `globalUsage` are NOT stored — they are derived
54
+ * state recomputed from `SUM(...) FROM scaler_reservations` on
55
+ * recovery, which means the on-disk reservation rows are the single
56
+ * source of truth for the cap-check critical section.
57
+ *
58
+ * The `eventBuffer` Map is also not persisted: events emitted before
59
+ * correlation are observability, not correctness. A coord crash before
60
+ * `correlateAgentToJob()` runs accepts losing those events (see the
61
+ * wishlist for the rationale).
62
+ */
63
+ export declare class ScalerStateStore {
64
+ private readonly db;
65
+ constructor(db: Kysely<Database>);
66
+ upsertSpawningAgent(snapshot: SpawningAgentSnapshot): Promise<void>;
67
+ deleteSpawningAgent(agentId: string): Promise<void>;
68
+ listSpawningAgents(): Promise<SpawningAgentSnapshot[]>;
69
+ /**
70
+ * Delete every spawning-agent row whose `spawned_at` is older than the
71
+ * given cutoff. Used by the leader-gated GC sweep so a coord that
72
+ * crashed mid-spawn doesn't leave the row blocking the spawn-timeout
73
+ * detection forever. Returns the row count GC'd.
74
+ */
75
+ sweepStaleSpawningAgents(olderThan: Date): Promise<number>;
76
+ upsertAgentJob(snapshot: AgentJobCorrelationSnapshot): Promise<void>;
77
+ deleteAgentJob(agentId: string): Promise<void>;
78
+ listAgentJobs(): Promise<AgentJobCorrelationSnapshot[]>;
79
+ upsertReservation(snapshot: ReservationSnapshot): Promise<void>;
80
+ deleteReservation(agentId: string): Promise<void>;
81
+ listReservations(): Promise<ReservationSnapshot[]>;
82
+ }
83
+ /**
84
+ * Aggregate event surface for "the scaler manager fully replayed its
85
+ * state from the DB after a leader switch". Kept here (vs in
86
+ * manager.ts) so `ScalerManager.recoverState()` can declare a clean
87
+ * return type. `bufferedEventsLost` always returns 0 today — the
88
+ * `eventBuffer` Map is intentionally not persisted — but the field
89
+ * exists so a future buffer-table addition is type-compatible.
90
+ */
91
+ export interface ScalerStateRecovery {
92
+ spawningAgentsRehydrated: number;
93
+ agentJobsRehydrated: number;
94
+ reservationsRehydrated: number;
95
+ bufferedEventsLost: number;
96
+ }
97
+ /**
98
+ * Re-export for the buffered-events note above; sole reason
99
+ * `ScalerEvent` is imported is to keep that comment compile-checked.
100
+ */
101
+ export type { ScalerEvent };
102
+ //# sourceMappingURL=scaler-state-store.d.ts.map