@cat-factory/integrations 0.30.0 → 0.32.0

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 (28) hide show
  1. package/dist/index.d.ts +4 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +9 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/modules/environments/EnvironmentConnectionService.d.ts +24 -6
  6. package/dist/modules/environments/EnvironmentConnectionService.d.ts.map +1 -1
  7. package/dist/modules/environments/EnvironmentConnectionService.js +50 -12
  8. package/dist/modules/environments/EnvironmentConnectionService.js.map +1 -1
  9. package/dist/modules/environments/environments.logic.d.ts.map +1 -1
  10. package/dist/modules/environments/environments.logic.js +2 -99
  11. package/dist/modules/environments/environments.logic.js.map +1 -1
  12. package/dist/modules/kubernetes/KubernetesRunnerTransport.d.ts +29 -0
  13. package/dist/modules/kubernetes/KubernetesRunnerTransport.d.ts.map +1 -0
  14. package/dist/modules/kubernetes/KubernetesRunnerTransport.js +202 -0
  15. package/dist/modules/kubernetes/KubernetesRunnerTransport.js.map +1 -0
  16. package/dist/modules/kubernetes/kubernetes.logic.d.ts +54 -0
  17. package/dist/modules/kubernetes/kubernetes.logic.d.ts.map +1 -0
  18. package/dist/modules/kubernetes/kubernetes.logic.js +169 -0
  19. package/dist/modules/kubernetes/kubernetes.logic.js.map +1 -0
  20. package/dist/modules/runners/RunnerPoolConnectionService.d.ts +35 -23
  21. package/dist/modules/runners/RunnerPoolConnectionService.d.ts.map +1 -1
  22. package/dist/modules/runners/RunnerPoolConnectionService.js +94 -50
  23. package/dist/modules/runners/RunnerPoolConnectionService.js.map +1 -1
  24. package/dist/modules/runners/runner-backends.d.ts +51 -0
  25. package/dist/modules/runners/runner-backends.d.ts.map +1 -0
  26. package/dist/modules/runners/runner-backends.js +101 -0
  27. package/dist/modules/runners/runner-backends.js.map +1 -0
  28. package/package.json +3 -3
@@ -0,0 +1,202 @@
1
+ import { apiBase, buildPodManifest, classifyPodReadiness, KUBERNETES_TOKEN_KEY, podName, podUrl, podsUrl, proxyUrl, } from './kubernetes.logic.js';
2
+ // Native Kubernetes runner transport (target k8s 1.35+). One bare Pod per RUN,
3
+ // named deterministically from `ref.runId`; every step of the run re-attaches to
4
+ // that pod by `ref.jobId` — mirroring CloudflareContainerTransport's per-run model
5
+ // and the harness's per-run-container assumption. The orchestrator reaches the
6
+ // per-pod executor-harness HTTP server through the kube-apiserver POD-PROXY
7
+ // subresource, so it needs only HTTPS to the apiserver (no in-cluster networking,
8
+ // no per-run Service/Ingress) and the full RunnerJobView fidelity is preserved
9
+ // verbatim — the harness is unchanged.
10
+ //
11
+ // Auth to the apiserver is a Bearer ServiceAccount token (secret key `apiToken`),
12
+ // needing RBAC `create/get/delete` on `pods` and `create/get` on `pods/proxy` in
13
+ // the namespace. The pod itself has no Service, so its harness is reachable only
14
+ // via the RBAC-gated proxy — no inbound harness shared secret is required.
15
+ // The eviction marker the engine classifies (job.logic `isContainerEvictionError`):
16
+ // a 404 from the proxy means the pod vanished (deleted/crashed/evicted).
17
+ const EVICTION_ERROR = 'Job not found (container evicted or crashed)';
18
+ const DISPATCH_TIMEOUT_MS = 30_000;
19
+ const POLL_TIMEOUT_MS = 30_000;
20
+ // Bounded readiness wait inside dispatch. The engine treats `dispatch` as blocking
21
+ // until the runner has accepted the job (a plain dispatch throw hard-fails the run as
22
+ // `failureKind: 'dispatch'`), exactly like the Cloudflare container backend, so we
23
+ // must wait here rather than fail fast. The window is generous enough to cover a cold
24
+ // first image pull in one shot; on a readiness failure we surface a RECOVERABLE
25
+ // eviction (see EVICTION_ERROR) so the durable driver re-drives — by then the pod is
26
+ // created (ensurePod 409s) and its image is cached, so the re-drive proceeds.
27
+ const READY_WAIT_MS = 120_000;
28
+ const READY_POLL_INTERVAL_MS = 1_500;
29
+ export class KubernetesRunnerTransport {
30
+ config;
31
+ resolveSecret;
32
+ constructor(config, resolveSecret) {
33
+ this.config = config;
34
+ this.resolveSecret = resolveSecret;
35
+ }
36
+ async dispatch(ref, spec, kind = 'agent', options) {
37
+ const name = podName(ref.runId);
38
+ await this.ensurePod(name, ref.runId, options);
39
+ await this.waitForPodReady(name);
40
+ const res = await this.proxyFetch('POST', name, '/jobs', { ...spec, kind }, DISPATCH_TIMEOUT_MS);
41
+ if (!res.ok) {
42
+ throw new Error(`Container dispatch failed (HTTP ${res.status}): ${await safeText(res)}`);
43
+ }
44
+ }
45
+ async poll(ref) {
46
+ const name = podName(ref.runId);
47
+ const res = await this.proxyFetch('GET', name, `/jobs/${encodeURIComponent(ref.jobId)}`, undefined, POLL_TIMEOUT_MS);
48
+ if (res.status === 404) {
49
+ // The pod-proxy 404s when the pod is gone (deleted/crashed/evicted) — the
50
+ // harness keeps a finished job's view, so a 404 is the pod vanishing, not a
51
+ // forgotten job. Report it as the eviction the engine recovers from.
52
+ return { state: 'failed', error: EVICTION_ERROR };
53
+ }
54
+ if (!res.ok) {
55
+ throw new Error(`Container job poll failed (HTTP ${res.status}): ${await safeText(res)}`);
56
+ }
57
+ return (await res.json());
58
+ }
59
+ /** Reclaim the run's pod (idempotent — a missing pod is a no-op). */
60
+ async release(ref) {
61
+ const name = podName(ref.runId);
62
+ const res = await this.apiFetch('DELETE', podUrl(this.config, name), undefined, DISPATCH_TIMEOUT_MS);
63
+ // A 404 means the pod is already gone — idempotent success. Any other failure
64
+ // (e.g. a 403 from a token lacking `delete`, or a transient 5xx) must NOT be
65
+ // swallowed: a bare Pod (restartPolicy: Never, no owner ref / Job TTL) is not
66
+ // garbage-collected, so a silently-dropped delete leaks the pod (and its node
67
+ // slot) indefinitely. Throw so the caller's best-effort wrapper records it (the
68
+ // LoggingRunnerTransport logs a `release` failure instead of a false success).
69
+ if (!res.ok && res.status !== 404) {
70
+ throw new Error(`Failed to release runner pod '${name}' (HTTP ${res.status}): ${await safeText(res)}`);
71
+ }
72
+ }
73
+ /** Probe the apiserver with the configured token (lists pods; nothing created). */
74
+ async testConnection() {
75
+ try {
76
+ const res = await this.apiFetch('GET', `${podsUrl(this.config)}?limit=1`, undefined, DISPATCH_TIMEOUT_MS);
77
+ if (res.ok) {
78
+ return {
79
+ ok: true,
80
+ message: `Reached ${apiBase(this.config)} (namespace ${this.config.namespace}).`,
81
+ };
82
+ }
83
+ return {
84
+ ok: false,
85
+ message: `apiserver responded ${res.status}: ${await safeText(res)}`,
86
+ };
87
+ }
88
+ catch (err) {
89
+ return { ok: false, message: err instanceof Error ? err.message : String(err) };
90
+ }
91
+ }
92
+ // --- internals ----------------------------------------------------------
93
+ async ensurePod(name, runId, options) {
94
+ const manifest = buildPodManifest(this.config, runId, name, options);
95
+ const res = await this.apiFetch('POST', podsUrl(this.config), manifest, DISPATCH_TIMEOUT_MS);
96
+ // 409 AlreadyExists ⇒ the run's pod is already up (a later step or a replay):
97
+ // idempotent re-attach, exactly like CloudflareContainerTransport.
98
+ if (res.ok || res.status === 409)
99
+ return;
100
+ throw new Error(`Failed to create runner pod (HTTP ${res.status}): ${await safeText(res)}`);
101
+ }
102
+ async waitForPodReady(name) {
103
+ const deadline = Date.now() + READY_WAIT_MS;
104
+ // Every readiness failure carries the eviction marker so the engine recovers it by
105
+ // re-driving the step (the re-drive re-attaches to the existing pod, by then ready
106
+ // / image-cached) instead of hard-failing the run on a cold pull or a transient
107
+ // pod blip. See EVICTION_ERROR and job.logic `isContainerEvictionError`.
108
+ const recoverable = (reason) => new Error(`${reason} (container evicted or crashed)`);
109
+ for (;;) {
110
+ const res = await this.apiFetch('GET', podUrl(this.config, name), undefined, POLL_TIMEOUT_MS);
111
+ if (res.status === 404) {
112
+ throw recoverable(`Runner pod '${name}' vanished before it became ready`);
113
+ }
114
+ if (res.ok) {
115
+ const readiness = classifyPodReadiness(await res.json());
116
+ if (readiness === 'ready')
117
+ return;
118
+ if (readiness === 'gone') {
119
+ throw recoverable(`Runner pod '${name}' terminated before serving`);
120
+ }
121
+ }
122
+ if (Date.now() >= deadline) {
123
+ throw recoverable(`Runner pod '${name}' not ready within ${READY_WAIT_MS}ms`);
124
+ }
125
+ await sleep(READY_POLL_INTERVAL_MS);
126
+ }
127
+ }
128
+ proxyFetch(method, name, path, body, timeoutMs) {
129
+ return this.apiFetch(method, proxyUrl(this.config, name, path), body, timeoutMs);
130
+ }
131
+ async apiFetch(method, url, body, timeoutMs) {
132
+ const token = this.resolveSecret(KUBERNETES_TOKEN_KEY);
133
+ if (!token)
134
+ throw new Error(`Missing Kubernetes ServiceAccount token ('${KUBERNETES_TOKEN_KEY}')`);
135
+ const headers = {
136
+ authorization: `Bearer ${token}`,
137
+ accept: 'application/json',
138
+ };
139
+ let payload;
140
+ if (body !== undefined && method !== 'GET' && method !== 'DELETE') {
141
+ payload = JSON.stringify(body);
142
+ headers['content-type'] = 'application/json';
143
+ }
144
+ const init = {
145
+ method,
146
+ headers,
147
+ body: payload,
148
+ signal: AbortSignal.timeout(timeoutMs),
149
+ };
150
+ const dispatcher = await this.tlsDispatcher();
151
+ if (dispatcher)
152
+ init.dispatcher = dispatcher;
153
+ return fetch(url, init);
154
+ }
155
+ /**
156
+ * Build the undici dispatcher carrying the cluster CA / insecure-skip flag, when
157
+ * configured. A kube-apiserver usually presents a private CA, which `fetch` can't
158
+ * verify without this. Loaded lazily (Node only) so the Worker bundle never pulls
159
+ * in `undici`; on a runtime without it, a custom-CA/insecure config fails clearly.
160
+ *
161
+ * The Agent is cached at MODULE scope keyed by the CA/insecure pair, not per
162
+ * instance: the wiring builds a fresh transport on every dispatch/poll resolve, so
163
+ * a per-instance cache would create (and abandon) one Agent — a TLS connection pool
164
+ * — per poll tick, defeating keep-alive and leaking sockets.
165
+ */
166
+ async tlsDispatcher() {
167
+ if (!this.config.caCertPem && !this.config.insecureSkipTlsVerify)
168
+ return undefined;
169
+ const key = `${this.config.insecureSkipTlsVerify ? 'insecure' : 'verify'}:${this.config.caCertPem ?? ''}`;
170
+ const existing = tlsDispatcherCache.get(key);
171
+ if (existing)
172
+ return existing;
173
+ // Variable specifier so bundlers don't statically resolve `undici`.
174
+ const moduleName = 'undici';
175
+ const undici = (await import(moduleName).catch(() => null));
176
+ if (!undici) {
177
+ throw new Error('Kubernetes custom CA / insecure TLS requires the Node runtime (undici is unavailable).');
178
+ }
179
+ const agent = new undici.Agent({
180
+ connect: {
181
+ ca: this.config.caCertPem,
182
+ rejectUnauthorized: !this.config.insecureSkipTlsVerify,
183
+ },
184
+ });
185
+ tlsDispatcherCache.set(key, agent);
186
+ return agent;
187
+ }
188
+ }
189
+ /** Module-scoped undici Agent cache, keyed by the CA/insecure pair (see tlsDispatcher). */
190
+ const tlsDispatcherCache = new Map();
191
+ async function safeText(res) {
192
+ try {
193
+ return (await res.text()).slice(0, 300);
194
+ }
195
+ catch {
196
+ return '(no body)';
197
+ }
198
+ }
199
+ function sleep(ms) {
200
+ return new Promise((resolve) => setTimeout(resolve, ms));
201
+ }
202
+ //# sourceMappingURL=KubernetesRunnerTransport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KubernetesRunnerTransport.js","sourceRoot":"","sources":["../../../src/modules/kubernetes/KubernetesRunnerTransport.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,OAAO,EACP,QAAQ,GACT,MAAM,uBAAuB,CAAA;AAE9B,+EAA+E;AAC/E,iFAAiF;AACjF,mFAAmF;AACnF,+EAA+E;AAC/E,4EAA4E;AAC5E,kFAAkF;AAClF,+EAA+E;AAC/E,uCAAuC;AACvC,EAAE;AACF,kFAAkF;AAClF,iFAAiF;AACjF,iFAAiF;AACjF,2EAA2E;AAE3E,oFAAoF;AACpF,yEAAyE;AACzE,MAAM,cAAc,GAAG,8CAA8C,CAAA;AAErE,MAAM,mBAAmB,GAAG,MAAM,CAAA;AAClC,MAAM,eAAe,GAAG,MAAM,CAAA;AAC9B,mFAAmF;AACnF,sFAAsF;AACtF,mFAAmF;AACnF,sFAAsF;AACtF,gFAAgF;AAChF,qFAAqF;AACrF,8EAA8E;AAC9E,MAAM,aAAa,GAAG,OAAO,CAAA;AAC7B,MAAM,sBAAsB,GAAG,KAAK,CAAA;AAEpC,MAAM,OAAO,yBAAyB;IAEjB,MAAM;IACN,aAAa;IAFhC,YACmB,MAA8B,EAC9B,aAA6B;sBAD7B,MAAM;6BACN,aAAa;IAC7B,CAAC;IAEJ,KAAK,CAAC,QAAQ,CACZ,GAAiB,EACjB,IAA6B,EAC7B,IAAI,GAAuB,OAAO,EAClC,OAA+B;QAE/B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC/B,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAC9C,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;QAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,EAAE,mBAAmB,CAAC,CAAA;QAChG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,mCAAmC,GAAG,CAAC,MAAM,MAAM,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC3F,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAiB;QAC1B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAC/B,KAAK,EACL,IAAI,EACJ,SAAS,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EACxC,SAAS,EACT,eAAe,CAChB,CAAA;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,0EAA0E;YAC1E,4EAA4E;YAC5E,qEAAqE;YACrE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,CAAA;QACnD,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,mCAAmC,GAAG,CAAC,MAAM,MAAM,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC3F,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAkB,CAAA;IAC5C,CAAC;IAED,qEAAqE;IACrE,KAAK,CAAC,OAAO,CAAC,GAAiB;QAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC/B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAC7B,QAAQ,EACR,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EACzB,SAAS,EACT,mBAAmB,CACpB,CAAA;QACD,8EAA8E;QAC9E,6EAA6E;QAC7E,8EAA8E;QAC9E,8EAA8E;QAC9E,gFAAgF;QAChF,+EAA+E;QAC/E,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,iCAAiC,IAAI,WAAW,GAAG,CAAC,MAAM,MAAM,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,CACtF,CAAA;QACH,CAAC;IACH,CAAC;IAED,mFAAmF;IACnF,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAC7B,KAAK,EACL,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EACjC,SAAS,EACT,mBAAmB,CACpB,CAAA;YACD,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;gBACX,OAAO;oBACL,EAAE,EAAE,IAAI;oBACR,OAAO,EAAE,WAAW,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI;iBACjF,CAAA;YACH,CAAC;YACD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,OAAO,EAAE,uBAAuB,GAAG,CAAC,MAAM,KAAK,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE;aACrE,CAAA;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAA;QACjF,CAAC;IACH,CAAC;IAED,2EAA2E;IAEnE,KAAK,CAAC,SAAS,CACrB,IAAY,EACZ,KAAa,EACb,OAA+B;QAE/B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;QACpE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAA;QAC5F,8EAA8E;QAC9E,mEAAmE;QACnE,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;YAAE,OAAM;QACxC,MAAM,IAAI,KAAK,CAAC,qCAAqC,GAAG,CAAC,MAAM,MAAM,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAC7F,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,IAAY;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,CAAA;QAC3C,mFAAmF;QACnF,mFAAmF;QACnF,gFAAgF;QAChF,yEAAyE;QACzE,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,GAAG,MAAM,iCAAiC,CAAC,CAAA;QAC7F,SAAS,CAAC;YACR,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,eAAe,CAAC,CAAA;YAC7F,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvB,MAAM,WAAW,CAAC,eAAe,IAAI,mCAAmC,CAAC,CAAA;YAC3E,CAAC;YACD,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;gBACX,MAAM,SAAS,GAAG,oBAAoB,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;gBACxD,IAAI,SAAS,KAAK,OAAO;oBAAE,OAAM;gBACjC,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;oBACzB,MAAM,WAAW,CAAC,eAAe,IAAI,6BAA6B,CAAC,CAAA;gBACrE,CAAC;YACH,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC3B,MAAM,WAAW,CAAC,eAAe,IAAI,sBAAsB,aAAa,IAAI,CAAC,CAAA;YAC/E,CAAC;YACD,MAAM,KAAK,CAAC,sBAAsB,CAAC,CAAA;QACrC,CAAC;IACH,CAAC;IAEO,UAAU,CAChB,MAAc,EACd,IAAY,EACZ,IAAY,EACZ,IAAa,EACb,SAAiB;QAEjB,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;IAClF,CAAC;IAEO,KAAK,CAAC,QAAQ,CACpB,MAAc,EACd,GAAW,EACX,IAAa,EACb,SAAiB;QAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAA;QACtD,IAAI,CAAC,KAAK;YACR,MAAM,IAAI,KAAK,CAAC,6CAA6C,oBAAoB,IAAI,CAAC,CAAA;QACxF,MAAM,OAAO,GAA2B;YACtC,aAAa,EAAE,UAAU,KAAK,EAAE;YAChC,MAAM,EAAE,kBAAkB;SAC3B,CAAA;QACD,IAAI,OAA2B,CAAA;QAC/B,IAAI,IAAI,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YAClE,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;YAC9B,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAA;QAC9C,CAAC;QACD,MAAM,IAAI,GAA2C;YACnD,MAAM;YACN,OAAO;YACP,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;SACvC,CAAA;QACD,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAA;QAC7C,IAAI,UAAU;YAAE,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5C,OAAO,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACzB,CAAC;IAED;;;;;;;;;;OAUG;IACK,KAAK,CAAC,aAAa;QACzB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,qBAAqB;YAAE,OAAO,SAAS,CAAA;QAClF,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,EAAE,CAAA;QACzG,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC5C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;QAC7B,oEAAoE;QACpE,MAAM,UAAU,GAAG,QAAQ,CAAA;QAC3B,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAElD,CAAA;QACR,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,wFAAwF,CACzF,CAAA;QACH,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC;YAC7B,OAAO,EAAE;gBACP,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;gBACzB,kBAAkB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,qBAAqB;aACvD;SACF,CAAC,CAAA;QACF,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAClC,OAAO,KAAK,CAAA;IACd,CAAC;CACF;AAED,2FAA2F;AAC3F,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAmB,CAAA;AAErD,KAAK,UAAU,QAAQ,CAAC,GAAa;IACnC,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAA;IACpB,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAC1D,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { KubernetesRunnerConfig, RunnerDispatchOptions } from '@cat-factory/kernel';
2
+ /**
3
+ * The secret-bundle key the Kubernetes backend reads the ServiceAccount token from.
4
+ * Re-exported from the wire contract (the single source of truth shared with the SPA
5
+ * connect form) so the key is defined once.
6
+ */
7
+ export declare const KUBERNETES_TOKEN_KEY = "apiToken";
8
+ /** Default port the executor-harness HTTP server listens on inside the pod. */
9
+ export declare const DEFAULT_HARNESS_PORT = 8080;
10
+ /** Deterministic per-RUN pod name (one pod per run; steps re-attach to it). */
11
+ export declare function podName(runId: string): string;
12
+ /** kube-apiserver root with any trailing slash stripped. */
13
+ export declare function apiBase(config: KubernetesRunnerConfig): string;
14
+ /** Collection URL for pods in the configured namespace. */
15
+ export declare function podsUrl(config: KubernetesRunnerConfig): string;
16
+ /** A single pod's URL. */
17
+ export declare function podUrl(config: KubernetesRunnerConfig, name: string): string;
18
+ /**
19
+ * The apiserver POD-PROXY subresource URL for the pod's harness HTTP server:
20
+ * `…/pods/<name>:<port>/proxy<path>`. Reaching this requires only HTTPS to the
21
+ * apiserver (RBAC `pods/proxy`), so no in-cluster networking / per-run Service is
22
+ * needed. `path` must begin with `/`.
23
+ */
24
+ export declare function proxyUrl(config: KubernetesRunnerConfig, name: string, path: string): string;
25
+ /** Resolve the image variant a dispatch needs (the heavier UI image when asked + configured). */
26
+ export declare function resolveImage(config: KubernetesRunnerConfig, options?: RunnerDispatchOptions): string;
27
+ /** Resolve the pod resource block for a dispatch (per-size override, else the default). */
28
+ export declare function resolveResources(config: KubernetesRunnerConfig, options?: RunnerDispatchOptions): {
29
+ requests?: Record<string, string>;
30
+ limits?: Record<string, string>;
31
+ } | undefined;
32
+ /**
33
+ * Build the bare-Pod manifest for a run. A bare Pod (not a Job) because the harness
34
+ * is a long-lived HTTP server we own the lifecycle of (create on first dispatch,
35
+ * delete on release) — Job completion semantics would fight that. The pod is
36
+ * reachable ONLY through the apiserver pod-proxy (no Service), so the harness needs
37
+ * no inbound shared secret here: access is gated by the SA's `pods/proxy` RBAC.
38
+ */
39
+ export declare function buildPodManifest(config: KubernetesRunnerConfig, runId: string, name: string, options?: RunnerDispatchOptions): Record<string, unknown>;
40
+ /** The readiness verdict from a pod's `status`. */
41
+ export type PodReadiness = 'ready' | 'pending' | 'gone';
42
+ /** Classify a pod's status JSON: ready to serve, still pending, or terminally gone. */
43
+ export declare function classifyPodReadiness(pod: unknown): PodReadiness;
44
+ /**
45
+ * Validate the apiserver URL at the write boundary. Unlike the manifest pool's
46
+ * STRICT policy (no private hosts), a kube-apiserver is routinely a private IP or
47
+ * a cluster DNS name, so private hosts are ALLOWED here — the operator is
48
+ * explicitly pointing at their cluster. We still require https and reject the
49
+ * cloud-metadata endpoints (anti-SSRF), including their obfuscated IP encodings
50
+ * (bare integer, IPv4-mapped IPv6) and the full link-local range — see the shared
51
+ * {@link isCloudMetadataHost} classifier.
52
+ */
53
+ export declare function assertApiServerUrlSafe(rawUrl: string): void;
54
+ //# sourceMappingURL=kubernetes.logic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kubernetes.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/kubernetes/kubernetes.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AASxF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,aAAqC,CAAA;AAEtE,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,OAAO,CAAA;AAExC,+EAA+E;AAC/E,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS7C;AAED,4DAA4D;AAC5D,wBAAgB,OAAO,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAE9D;AAED,2DAA2D;AAC3D,wBAAgB,OAAO,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAE9D;AAED,0BAA0B;AAC1B,wBAAgB,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,sBAAsB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ3F;AAED,iGAAiG;AACjG,wBAAgB,YAAY,CAC1B,MAAM,EAAE,sBAAsB,EAC9B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,MAAM,CAGR;AAED,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,sBAAsB,EAC9B,OAAO,CAAC,EAAE,qBAAqB,GAC9B;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,SAAS,CAcpF;AASD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,sBAAsB,EAC9B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,qBAAqB,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA+BzB;AAUD,mDAAmD;AACnD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAA;AAEvD,uFAAuF;AACvF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAU/D;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAa3D"}
@@ -0,0 +1,169 @@
1
+ import { isCloudMetadataHost } from '@cat-factory/kernel';
2
+ import { KUBERNETES_RUNNER_TOKEN_SECRET_KEY } from '@cat-factory/contracts';
3
+ // Pure helpers for the native Kubernetes runner backend. No I/O here — URL
4
+ // building, the per-run pod-name derivation, the pod manifest, and the readiness
5
+ // classification are all pure so they unit-test in isolation. The transport
6
+ // (KubernetesRunnerTransport) does the actual kube-apiserver `fetch`es.
7
+ /**
8
+ * The secret-bundle key the Kubernetes backend reads the ServiceAccount token from.
9
+ * Re-exported from the wire contract (the single source of truth shared with the SPA
10
+ * connect form) so the key is defined once.
11
+ */
12
+ export const KUBERNETES_TOKEN_KEY = KUBERNETES_RUNNER_TOKEN_SECRET_KEY;
13
+ /** Default port the executor-harness HTTP server listens on inside the pod. */
14
+ export const DEFAULT_HARNESS_PORT = 8080;
15
+ /** Deterministic per-RUN pod name (one pod per run; steps re-attach to it). */
16
+ export function podName(runId) {
17
+ const sanitized = runId
18
+ .toLowerCase()
19
+ .replace(/[^a-z0-9-]/g, '-')
20
+ .replace(/-+/g, '-')
21
+ .replace(/^-+|-+$/g, '');
22
+ // RFC1123 label: <=63 chars, starts/ends alphanumeric. Reserve room for the prefix.
23
+ const body = sanitized.slice(0, 63 - 'cf-run-'.length).replace(/-+$/g, '') || 'run';
24
+ return `cf-run-${body}`;
25
+ }
26
+ /** kube-apiserver root with any trailing slash stripped. */
27
+ export function apiBase(config) {
28
+ return config.apiServerUrl.trim().replace(/\/+$/, '');
29
+ }
30
+ /** Collection URL for pods in the configured namespace. */
31
+ export function podsUrl(config) {
32
+ return `${apiBase(config)}/api/v1/namespaces/${config.namespace}/pods`;
33
+ }
34
+ /** A single pod's URL. */
35
+ export function podUrl(config, name) {
36
+ return `${podsUrl(config)}/${encodeURIComponent(name)}`;
37
+ }
38
+ /**
39
+ * The apiserver POD-PROXY subresource URL for the pod's harness HTTP server:
40
+ * `…/pods/<name>:<port>/proxy<path>`. Reaching this requires only HTTPS to the
41
+ * apiserver (RBAC `pods/proxy`), so no in-cluster networking / per-run Service is
42
+ * needed. `path` must begin with `/`.
43
+ */
44
+ export function proxyUrl(config, name, path) {
45
+ const port = config.harnessPort ?? DEFAULT_HARNESS_PORT;
46
+ const p = path.startsWith('/') ? path : `/${path}`;
47
+ // The apiserver pod-proxy subresource addresses the target as a literal
48
+ // `pods/<name>:<port>/proxy` path segment — kubectl/client-go send the colon
49
+ // UNENCODED. Encode the name (RFC1123, so a no-op in practice) but keep the
50
+ // `:<port>` literal so the apiserver parses the name:port pair.
51
+ return `${podsUrl(config)}/${encodeURIComponent(name)}:${port}/proxy${p}`;
52
+ }
53
+ /** Resolve the image variant a dispatch needs (the heavier UI image when asked + configured). */
54
+ export function resolveImage(config, options) {
55
+ if (options?.image === 'ui' && config.imageUi)
56
+ return config.imageUi;
57
+ return config.image;
58
+ }
59
+ /** Resolve the pod resource block for a dispatch (per-size override, else the default). */
60
+ export function resolveResources(config, options) {
61
+ const sizeOverride = options?.instanceSize
62
+ ? config.resourcesBySize?.[options.instanceSize]
63
+ : undefined;
64
+ // A per-size override is the t-shirt size for this run: it sets BOTH the request and
65
+ // the limit (requests == limits ⇒ Guaranteed QoS). Applying it to the limit alone
66
+ // while keeping a larger default request produces requests > limits, which the
67
+ // apiserver rejects with a 422 — so a smaller size could never start.
68
+ const requests = sizeOverride ?? config.resources?.requests;
69
+ const limits = sizeOverride ?? config.resources?.limits;
70
+ const out = {};
71
+ if (requests)
72
+ out.requests = quantities(requests);
73
+ if (limits)
74
+ out.limits = quantities(limits);
75
+ return out.requests || out.limits ? out : undefined;
76
+ }
77
+ function quantities(q) {
78
+ const out = {};
79
+ if (q.cpu)
80
+ out.cpu = q.cpu;
81
+ if (q.memory)
82
+ out.memory = q.memory;
83
+ return out;
84
+ }
85
+ /**
86
+ * Build the bare-Pod manifest for a run. A bare Pod (not a Job) because the harness
87
+ * is a long-lived HTTP server we own the lifecycle of (create on first dispatch,
88
+ * delete on release) — Job completion semantics would fight that. The pod is
89
+ * reachable ONLY through the apiserver pod-proxy (no Service), so the harness needs
90
+ * no inbound shared secret here: access is gated by the SA's `pods/proxy` RBAC.
91
+ */
92
+ export function buildPodManifest(config, runId, name, options) {
93
+ const port = config.harnessPort ?? DEFAULT_HARNESS_PORT;
94
+ const resources = resolveResources(config, options);
95
+ const container = {
96
+ name: 'executor',
97
+ image: resolveImage(config, options),
98
+ ports: [{ containerPort: port }],
99
+ env: [{ name: 'PORT', value: String(port) }],
100
+ ...(resources ? { resources } : {}),
101
+ };
102
+ const spec = {
103
+ restartPolicy: 'Never',
104
+ containers: [container],
105
+ ...(config.serviceAccountName ? { serviceAccountName: config.serviceAccountName } : {}),
106
+ ...(config.imagePullSecretName
107
+ ? { imagePullSecrets: [{ name: config.imagePullSecretName }] }
108
+ : {}),
109
+ ...(config.nodeSelector ? { nodeSelector: config.nodeSelector } : {}),
110
+ ...(config.tolerations ? { tolerations: config.tolerations } : {}),
111
+ };
112
+ return {
113
+ apiVersion: 'v1',
114
+ kind: 'Pod',
115
+ metadata: {
116
+ name,
117
+ namespace: config.namespace,
118
+ labels: { 'cat-factory.runId': labelValue(runId), ...config.labels },
119
+ ...(config.annotations ? { annotations: config.annotations } : {}),
120
+ },
121
+ spec,
122
+ };
123
+ }
124
+ /** Coerce an arbitrary id into a valid label value (<=63 chars, alnum/._-). */
125
+ function labelValue(value) {
126
+ return value
127
+ .replace(/[^A-Za-z0-9._-]/g, '-')
128
+ .replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/g, '')
129
+ .slice(0, 63);
130
+ }
131
+ /** Classify a pod's status JSON: ready to serve, still pending, or terminally gone. */
132
+ export function classifyPodReadiness(pod) {
133
+ const status = pod?.status;
134
+ const phase = typeof status?.phase === 'string' ? status.phase : undefined;
135
+ if (phase === 'Succeeded' || phase === 'Failed')
136
+ return 'gone';
137
+ if (phase !== 'Running')
138
+ return 'pending';
139
+ const conditions = Array.isArray(status?.conditions)
140
+ ? status.conditions
141
+ : [];
142
+ const ready = conditions.find((c) => c.type === 'Ready');
143
+ return ready?.status === 'True' ? 'ready' : 'pending';
144
+ }
145
+ /**
146
+ * Validate the apiserver URL at the write boundary. Unlike the manifest pool's
147
+ * STRICT policy (no private hosts), a kube-apiserver is routinely a private IP or
148
+ * a cluster DNS name, so private hosts are ALLOWED here — the operator is
149
+ * explicitly pointing at their cluster. We still require https and reject the
150
+ * cloud-metadata endpoints (anti-SSRF), including their obfuscated IP encodings
151
+ * (bare integer, IPv4-mapped IPv6) and the full link-local range — see the shared
152
+ * {@link isCloudMetadataHost} classifier.
153
+ */
154
+ export function assertApiServerUrlSafe(rawUrl) {
155
+ let url;
156
+ try {
157
+ url = new URL(rawUrl);
158
+ }
159
+ catch {
160
+ throw new Error(`Invalid Kubernetes apiserver URL: ${rawUrl}`);
161
+ }
162
+ if (url.protocol !== 'https:') {
163
+ throw new Error('Kubernetes apiserver URL must use https.');
164
+ }
165
+ if (isCloudMetadataHost(url.hostname)) {
166
+ throw new Error('Kubernetes apiserver URL must not target the cloud metadata endpoint.');
167
+ }
168
+ }
169
+ //# sourceMappingURL=kubernetes.logic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kubernetes.logic.js","sourceRoot":"","sources":["../../../src/modules/kubernetes/kubernetes.logic.ts"],"names":[],"mappings":"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"}
@@ -1,57 +1,69 @@
1
1
  import type { Clock } from '@cat-factory/kernel';
2
2
  import type { RunnerPoolConnectionRecord, RunnerPoolConnectionRepository } from '@cat-factory/kernel';
3
3
  import type { SecretCipher } from '@cat-factory/kernel';
4
- import type { SecretResolver, UrlSafetyPolicy } from '@cat-factory/kernel';
5
- import type { ConnectionTestResult, ProviderDescriptor, RunnerPoolConnection, RunnerPoolManifest, RunnerPoolProvider, TestRunnerPoolConnectionInput } from '@cat-factory/kernel';
4
+ import type { UrlSafetyPolicy } from '@cat-factory/kernel';
5
+ import type { ConnectionTestResult, ProviderDescriptor, RunnerBackendConfig, RunnerPoolConnection, RunnerPoolProvider, RunnerTransport, TestRunnerPoolConnectionInput } from '@cat-factory/kernel';
6
6
  import type { WorkspaceRepository } from '@cat-factory/kernel';
7
7
  export interface RunnerPoolConnectionServiceDependencies {
8
8
  runnerPoolConnectionRepository: RunnerPoolConnectionRepository;
9
9
  workspaceRepository: WorkspaceRepository;
10
10
  secretCipher: SecretCipher;
11
11
  clock: Clock;
12
- /** URL/host safety policy applied to a registered manifest. Defaults to strict. */
12
+ /** URL/host safety policy applied to a manifest backend. Defaults to strict. */
13
13
  urlPolicy?: UrlSafetyPolicy;
14
- /** The injected pool provider, so the service can surface describe/test to the UI. */
14
+ /**
15
+ * Whether this deployment runtime can honor a backend's custom TLS trust material
16
+ * (a private CA / insecure-skip). The Cloudflare Worker cannot, so it sets `false`
17
+ * and a Kubernetes config with a CA is rejected at registration. Absent ⇒ supported.
18
+ */
19
+ customTlsSupported?: boolean;
20
+ /** Injected manifest HTTP provider (its OAuth cache shared / a native pool adapter). */
15
21
  runnerPoolProvider?: RunnerPoolProvider;
16
- /** What the injected provider is (see EnvironmentConnectionService). Defaults `manifest`. */
17
- providerKind?: 'native' | 'manifest';
18
- providerId?: string;
19
- providerLabel?: string;
20
22
  }
21
- export interface ResolvedRunnerPool {
22
- manifest: RunnerPoolManifest;
23
- resolveSecret: SecretResolver;
23
+ /** A resolved runner backend: the live transport + its identity (for provisioning logs). */
24
+ export interface ResolvedRunnerBackend {
25
+ transport: RunnerTransport;
26
+ kind: string;
27
+ providerId: string;
24
28
  }
25
29
  export declare class RunnerPoolConnectionService {
26
30
  private readonly deps;
27
31
  constructor(deps: RunnerPoolConnectionServiceDependencies);
28
- /** Register (or replace) a workspace's runner pool. */
32
+ /** The per-call context a backend provider needs to build/test a transport. */
33
+ private context;
34
+ private provider;
35
+ /** The write-boundary safety options (URL policy + this runtime's TLS capability). */
36
+ private safetyOptions;
37
+ /** Register (or replace) a workspace's runner backend. */
29
38
  register(workspaceId: string, input: {
30
- manifest: RunnerPoolManifest;
39
+ config: RunnerBackendConfig;
31
40
  secrets: Record<string, string>;
32
41
  }): Promise<RunnerPoolConnection>;
33
- /** Rotate/replace the secret bundle without re-sending the manifest. */
42
+ /** Rotate/replace the secret bundle without re-sending the config. */
34
43
  updateSecrets(workspaceId: string, secrets: Record<string, string>): Promise<RunnerPoolConnection>;
35
- /** Describe the pool provider's config fields + test availability for the UI. */
44
+ /** Describe the backend's config fields + test availability for the UI. */
36
45
  describeProvider(workspaceId: string): Promise<ProviderDescriptor>;
37
- /** Probe a candidate pool connection before saving (nothing is persisted). */
46
+ /** Probe a candidate backend connection before saving (nothing is persisted). */
38
47
  testConnection(workspaceId: string, input: TestRunnerPoolConnectionInput): Promise<ConnectionTestResult>;
39
48
  /** The workspace's current connection (safe metadata), or null. */
40
49
  getConnection(workspaceId: string): Promise<RunnerPoolConnection | null>;
41
- /** Resolve the live connection + parsed manifest, or throw if not registered. */
50
+ /** Resolve the live connection + parsed config, or throw if not registered. */
42
51
  requireConnection(workspaceId: string): Promise<{
43
52
  record: RunnerPoolConnectionRecord;
44
- manifest: RunnerPoolManifest;
53
+ config: RunnerBackendConfig;
45
54
  }>;
46
55
  /**
47
- * Resolve the workspace's pool (parsed manifest + a secret resolver over its
48
- * decrypted bundle), or null when it has no live pool registered. Used by the
49
- * container executor to pick the self-hosted runner backend per job.
56
+ * Resolve the workspace's runner backend into a live {@link RunnerTransport} (the
57
+ * provider builds it from the stored config + a secret resolver over its decrypted
58
+ * bundle), or null when it has no live backend registered / its kind is no longer
59
+ * registered. Used by the wiring to pick the dispatch backend per job.
50
60
  */
51
- resolve(workspaceId: string): Promise<ResolvedRunnerPool | null>;
52
- /** Unregister the pool (tombstones the binding). */
61
+ resolve(workspaceId: string): Promise<ResolvedRunnerBackend | null>;
62
+ /** Unregister the backend (tombstones the binding). */
53
63
  unregister(workspaceId: string): Promise<void>;
54
64
  private decryptSecrets;
55
65
  private toConnection;
66
+ /** Parse the stored discriminated config, tolerating a malformed/legacy blob. */
67
+ private parseConfig;
56
68
  }
57
69
  //# sourceMappingURL=RunnerPoolConnectionService.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RunnerPoolConnectionService.d.ts","sourceRoot":"","sources":["../../../src/modules/runners/RunnerPoolConnectionService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EACV,0BAA0B,EAC1B,8BAA8B,EAC/B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1E,OAAO,KAAK,EACV,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,6BAA6B,EAC9B,MAAM,qBAAqB,CAAA;AAG5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAU9D,MAAM,WAAW,uCAAuC;IACtD,8BAA8B,EAAE,8BAA8B,CAAA;IAC9D,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,YAAY,EAAE,YAAY,CAAA;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,mFAAmF;IACnF,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;IACvC,6FAA6F;IAC7F,YAAY,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;IACpC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,aAAa,EAAE,cAAc,CAAA;CAC9B;AAED,qBAAa,2BAA2B;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,uCAAuC,EAAI;IAE9E,uDAAuD;IACjD,QAAQ,CACZ,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE;QAAE,QAAQ,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GACvE,OAAO,CAAC,oBAAoB,CAAC,CAwB/B;IAED,wEAAwE;IAClE,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,oBAAoB,CAAC,CAU/B;IAED,iFAAiF;IAC3E,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAyBvE;IAED,8EAA8E;IACxE,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,6BAA6B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAe/B;IAED,mEAAmE;IAC7D,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAK7E;IAED,iFAAiF;IAC3E,iBAAiB,CACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QAAE,MAAM,EAAE,0BAA0B,CAAC;QAAC,QAAQ,EAAE,kBAAkB,CAAA;KAAE,CAAC,CAO/E;IAED;;;;OAIG;IACG,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAMrE;IAED,oDAAoD;IAC9C,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAInD;YAEa,cAAc;IAQ5B,OAAO,CAAC,YAAY;CAYrB"}
1
+ {"version":3,"file":"RunnerPoolConnectionService.d.ts","sourceRoot":"","sources":["../../../src/modules/runners/RunnerPoolConnectionService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EACV,0BAA0B,EAC1B,8BAA8B,EAC/B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,KAAK,EACV,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,6BAA6B,EAC9B,MAAM,qBAAqB,CAAA;AAG5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAiB9D,MAAM,WAAW,uCAAuC;IACtD,8BAA8B,EAAE,8BAA8B,CAAA;IAC9D,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,YAAY,EAAE,YAAY,CAAA;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,gFAAgF;IAChF,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,wFAAwF;IACxF,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;CACxC;AAED,4FAA4F;AAC5F,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,eAAe,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,qBAAa,2BAA2B;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,uCAAuC,EAAI;IAE9E,+EAA+E;IAC/E,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,QAAQ;IAMhB,sFAAsF;IACtF,OAAO,CAAC,aAAa;IASrB,0DAA0D;IACpD,QAAQ,CACZ,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE;QAAE,MAAM,EAAE,mBAAmB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GACtE,OAAO,CAAC,oBAAoB,CAAC,CA2B/B;IAED,sEAAsE;IAChE,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,oBAAoB,CAAC,CAW/B;IAED,2EAA2E;IACrE,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA6BvE;IAED,iFAAiF;IAC3E,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,6BAA6B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAU/B;IAED,mEAAmE;IAC7D,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAK7E;IAED,+EAA+E;IACzE,iBAAiB,CACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QAAE,MAAM,EAAE,0BAA0B,CAAC;QAAC,MAAM,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAO9E;IAED;;;;;OAKG;IACG,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAYxE;IAED,uDAAuD;IACjD,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAInD;YAEa,cAAc;IAQ5B,OAAO,CAAC,YAAY;IAmBpB,iFAAiF;IACjF,OAAO,CAAC,WAAW;CAOpB"}