@sema-agent/settings-schema 6.3.0 → 7.0.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.
- package/CHANGELOG.md +207 -0
- package/README.md +40 -5
- package/bin/settings-schema.mjs +22 -0
- package/conformance/VERSION +1 -1
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +115 -0
- package/conformance/cases/c48-s935-model-headers-transported/input.json +37 -0
- package/conformance/cases/c48-s935-model-headers-transported/meta.json +6 -0
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +98 -0
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/input.json +28 -0
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/meta.json +9 -0
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +102 -0
- package/conformance/cases/c50-s784-lane-host-legal/input.json +29 -0
- package/conformance/cases/c50-s784-lane-host-legal/meta.json +5 -0
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +104 -0
- package/conformance/cases/c51-s784-lane-e2b-legal/input.json +32 -0
- package/conformance/cases/c51-s784-lane-e2b-legal/meta.json +5 -0
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +106 -0
- package/conformance/cases/c52-s784-lane-k8s-legal/input.json +34 -0
- package/conformance/cases/c52-s784-lane-k8s-legal/meta.json +5 -0
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +106 -0
- package/conformance/cases/c53-s784-lane-ssh-legal/input.json +34 -0
- package/conformance/cases/c53-s784-lane-ssh-legal/meta.json +5 -0
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +102 -0
- package/conformance/cases/c54-s784-lane-adb-legal/input.json +29 -0
- package/conformance/cases/c54-s784-lane-adb-legal/meta.json +5 -0
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +103 -0
- package/conformance/cases/c55-s784-lane-local-docker-legal/input.json +30 -0
- package/conformance/cases/c55-s784-lane-local-docker-legal/meta.json +5 -0
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +109 -0
- package/conformance/cases/c56-s784-lane-device-legal/input.json +36 -0
- package/conformance/cases/c56-s784-lane-device-legal/meta.json +5 -0
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +83 -0
- package/conformance/cases/c57-s784-g1-enabled-no-lane/input.json +23 -0
- package/conformance/cases/c57-s784-g1-enabled-no-lane/meta.json +5 -0
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +83 -0
- package/conformance/cases/c58-s784-g2-multitenant-host/input.json +34 -0
- package/conformance/cases/c58-s784-g2-multitenant-host/meta.json +5 -0
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +83 -0
- package/conformance/cases/c59-s784-g3-k8s-missing-image/input.json +26 -0
- package/conformance/cases/c59-s784-g3-k8s-missing-image/meta.json +5 -0
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +83 -0
- package/conformance/cases/c60-s784-g3-e2b-missing-key/input.json +26 -0
- package/conformance/cases/c60-s784-g3-e2b-missing-key/meta.json +5 -0
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +83 -0
- package/conformance/cases/c61-s784-g3-other-lane-key/input.json +30 -0
- package/conformance/cases/c61-s784-g3-other-lane-key/meta.json +5 -0
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +83 -0
- package/conformance/cases/c62-s784-g7a-session-tidb/input.json +27 -0
- package/conformance/cases/c62-s784-g7a-session-tidb/meta.json +5 -0
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +83 -0
- package/conformance/cases/c63-s784-host-two-replicas/input.json +27 -0
- package/conformance/cases/c63-s784-host-two-replicas/meta.json +5 -0
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +83 -0
- package/conformance/cases/c64-s784-multitenant-local-store/input.json +30 -0
- package/conformance/cases/c64-s784-multitenant-local-store/meta.json +5 -0
- package/conformance/cases/c65-s784-pg-without-db/expected.json +83 -0
- package/conformance/cases/c65-s784-pg-without-db/input.json +27 -0
- package/conformance/cases/c65-s784-pg-without-db/meta.json +5 -0
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +83 -0
- package/conformance/cases/c66-s784-memory-backend-mismatch/input.json +36 -0
- package/conformance/cases/c66-s784-memory-backend-mismatch/meta.json +5 -0
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +83 -0
- package/conformance/cases/c67-s784-retired-field-loud/input.json +22 -0
- package/conformance/cases/c67-s784-retired-field-loud/meta.json +5 -0
- package/conformance/cases/c68-s784-device-local-store/expected.json +83 -0
- package/conformance/cases/c68-s784-device-local-store/input.json +26 -0
- package/conformance/cases/c68-s784-device-local-store/meta.json +5 -0
- package/conformance/cases/cli04-env-value-with-newline/expected.json +13 -2
- package/conformance/cases/cli04-env-value-with-newline/input.json +8 -1
- package/conformance/cases/cli04-env-value-with-newline/meta.json +1 -1
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/expected.json +49 -0
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/input.json +26 -0
- package/conformance/migrate-workers/m01-g5-k8s-env-lifted/meta.json +4 -0
- package/conformance/migrate-workers/m02-g5-idempotent/expected.json +38 -0
- package/conformance/migrate-workers/m02-g5-idempotent/input.json +33 -0
- package/conformance/migrate-workers/m02-g5-idempotent/meta.json +5 -0
- package/conformance/migrate-workers/m03-g5-refused-no-lane/expected.json +71 -0
- package/conformance/migrate-workers/m03-g5-refused-no-lane/input.json +42 -0
- package/conformance/migrate-workers/m03-g5-refused-no-lane/meta.json +4 -0
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/expected.json +52 -0
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/input.json +30 -0
- package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/meta.json +4 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +70 -0
- package/dist/execution-lane-index.d.ts +19 -0
- package/dist/execution-lane-index.js +23 -0
- package/dist/execution-lanes.d.ts +64 -0
- package/dist/execution-lanes.js +152 -0
- package/dist/file-edit.js +26 -21
- package/dist/hash.d.ts +3 -3
- package/dist/hash.js +3 -3
- package/dist/hooks.d.ts +12 -12
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/migrate-workers.d.ts +63 -0
- package/dist/migrate-workers.js +331 -0
- package/dist/remote-exec.d.ts +23 -64
- package/dist/remote-exec.js +20 -71
- package/dist/secret-refs.d.ts +7 -1
- package/dist/secret-refs.js +9 -3
- package/dist/types.d.ts +1382 -430
- package/dist/types.js +298 -76
- package/package.json +9 -1
package/dist/remote-exec.d.ts
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `remoteExec` — the execution-substrate contract (NET-NEW; not part of DOMAIN_SCHEMAS today).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* local/remote-docker fast-follow). The schema LEADS; runtime support is phased. The discriminator is `provider`:
|
|
8
|
-
* FLEET (implemented today, the worker service's config module):
|
|
4
|
+
* The exec layer is user-configurable & pluggable. This SCHEMA carries the per-machine `config.d/remote-exec.json`
|
|
5
|
+
* shape for the lanes a consumer can drive from that file. The discriminator is `provider`:
|
|
6
|
+
* FLEET:
|
|
9
7
|
* - `e2b` per-task E2B Firecracker VM (isolated, suspendable). The default code-agent worker.
|
|
10
8
|
* - `k8s` per-task Kata pod on our k3s (isolated, NOT suspendable). Self-hosted sandbox lane.
|
|
11
9
|
* - `ssh` a real host over SSH (NOT isolated/suspendable). AI-orchestrated batch deploy.
|
|
12
10
|
* - `adb` a real device over ADB (NOT isolated/suspendable). On-device APP testing.
|
|
13
|
-
* TOC single-machine (
|
|
11
|
+
* TOC single-machine (macOS + Linux):
|
|
14
12
|
* - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
|
|
15
|
-
* agent). No isolation.
|
|
13
|
+
* agent). No isolation.
|
|
16
14
|
* - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
|
|
17
|
-
*
|
|
15
|
+
*
|
|
16
|
+
* 7.0.0: every `provider` word here is an execution lane word (`EXECUTION_LANE_WORDS`, `execution-lanes.ts` — the ONE
|
|
17
|
+
* vocabulary; `test/execution-lanes-7.0.0.test.ts` pins `provider ⊆ EXECUTION_LANE_WORDS`, at runtime and at compile time).
|
|
18
|
+
* The `remote-docker` variant was DELETED (zero aliases): no engine read point ever turned that word into a running lane
|
|
19
|
+
* (the engine refuses `REMOTE_EXEC=remote-docker` at boot and only warned "unwired" when this file named it). A remote
|
|
20
|
+
* docker daemon is the `local-docker` lane pointed at it (`DOCKER_HOST` — in a WorkerSpec, `runtime.execution.coords`).
|
|
18
21
|
*
|
|
19
22
|
* SECRET BOUNDARY (identical to the rest of the contract): every secret VALUE the consumer reads from its own
|
|
20
|
-
* env (E2B_API_KEY, K8S_TOKEN, SSH_PRIVATE_KEY, MINIO_
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
* env (E2B_API_KEY, K8S_TOKEN, SSH_PRIVATE_KEY, MINIO_*) is carried here as an env-NAME ref (the `ENV_NAME` regex
|
|
24
|
+
* from types.ts) — NEVER a literal value. The consumer resolves NAME → value from its own env (service env / k8s
|
|
25
|
+
* secret / the TOC local `.env`), so this package never carries a credential. This is SCHEMA + secret-as-env-NAME
|
|
26
|
+
* ONLY; the service owns the adapter implementations.
|
|
24
27
|
*/
|
|
25
28
|
import { z } from "zod";
|
|
26
29
|
export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodObject<{
|
|
@@ -61,7 +64,7 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodO
|
|
|
61
64
|
memory?: string | undefined;
|
|
62
65
|
workdir?: string | undefined;
|
|
63
66
|
cpus?: number | undefined;
|
|
64
|
-
network?: "
|
|
67
|
+
network?: "host" | "none" | "bridge" | undefined;
|
|
65
68
|
mountPath?: string | undefined;
|
|
66
69
|
}, {
|
|
67
70
|
provider: "local-docker";
|
|
@@ -69,52 +72,8 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodO
|
|
|
69
72
|
memory?: string | undefined;
|
|
70
73
|
workdir?: string | undefined;
|
|
71
74
|
cpus?: number | undefined;
|
|
72
|
-
network?: "
|
|
73
|
-
mountPath?: string | undefined;
|
|
74
|
-
}>, z.ZodObject<{
|
|
75
|
-
/** The remote docker endpoint — a DOCKER_HOST url: `ssh://user@host` or `tcp://host:2376`. A `user@` is OK
|
|
76
|
-
* (it's the ssh target, not a secret); an embedded PASSWORD is rejected — credentials go in the env-NAME
|
|
77
|
-
* fields below, NEVER in the URL (the secret-as-env-NAME boundary). */
|
|
78
|
-
dockerHost: z.ZodEffects<z.ZodString, string, string>;
|
|
79
|
-
/** env-NAMEs for a tcp+TLS endpoint (NEVER the cert/key bytes). */
|
|
80
|
-
tlsCertEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
81
|
-
tlsKeyEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
82
|
-
tlsCaEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
83
|
-
/** env-NAME for the SSH private key when dockerHost is `ssh://…` (NEVER the key value). */
|
|
84
|
-
sshKeyEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
85
|
-
image: z.ZodEffects<z.ZodString, string, string>;
|
|
86
|
-
memory: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
87
|
-
cpus: z.ZodOptional<z.ZodNumber>;
|
|
88
|
-
network: z.ZodOptional<z.ZodEnum<["none", "bridge", "host"]>>;
|
|
89
|
-
workdir: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
90
|
-
mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
91
|
-
provider: z.ZodLiteral<"remote-docker">;
|
|
92
|
-
}, "strip", z.ZodTypeAny, {
|
|
93
|
-
provider: "remote-docker";
|
|
94
|
-
image: string;
|
|
95
|
-
dockerHost: string;
|
|
96
|
-
memory?: string | undefined;
|
|
97
|
-
workdir?: string | undefined;
|
|
98
|
-
cpus?: number | undefined;
|
|
99
|
-
network?: "none" | "host" | "bridge" | undefined;
|
|
100
|
-
mountPath?: string | undefined;
|
|
101
|
-
tlsCertEnv?: string | undefined;
|
|
102
|
-
tlsKeyEnv?: string | undefined;
|
|
103
|
-
tlsCaEnv?: string | undefined;
|
|
104
|
-
sshKeyEnv?: string | undefined;
|
|
105
|
-
}, {
|
|
106
|
-
provider: "remote-docker";
|
|
107
|
-
image: string;
|
|
108
|
-
dockerHost: string;
|
|
109
|
-
memory?: string | undefined;
|
|
110
|
-
workdir?: string | undefined;
|
|
111
|
-
cpus?: number | undefined;
|
|
112
|
-
network?: "none" | "host" | "bridge" | undefined;
|
|
75
|
+
network?: "host" | "none" | "bridge" | undefined;
|
|
113
76
|
mountPath?: string | undefined;
|
|
114
|
-
tlsCertEnv?: string | undefined;
|
|
115
|
-
tlsKeyEnv?: string | undefined;
|
|
116
|
-
tlsCaEnv?: string | undefined;
|
|
117
|
-
sshKeyEnv?: string | undefined;
|
|
118
77
|
}>, z.ZodObject<{
|
|
119
78
|
provider: z.ZodLiteral<"e2b">;
|
|
120
79
|
/** env-NAME holding the E2B API key (service: E2B_API_KEY). NEVER the key. */
|
|
@@ -192,8 +151,8 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodO
|
|
|
192
151
|
}, "strip", z.ZodTypeAny, {
|
|
193
152
|
provider: "k8s";
|
|
194
153
|
image: string;
|
|
195
|
-
cpu?: string | undefined;
|
|
196
154
|
memory?: string | undefined;
|
|
155
|
+
cpu?: string | undefined;
|
|
197
156
|
mountPath?: string | undefined;
|
|
198
157
|
timeoutMs?: number | undefined;
|
|
199
158
|
apiUrl?: string | undefined;
|
|
@@ -214,8 +173,8 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodO
|
|
|
214
173
|
}, {
|
|
215
174
|
provider: "k8s";
|
|
216
175
|
image: string;
|
|
217
|
-
cpu?: string | undefined;
|
|
218
176
|
memory?: string | undefined;
|
|
177
|
+
cpu?: string | undefined;
|
|
219
178
|
mountPath?: string | undefined;
|
|
220
179
|
timeoutMs?: number | undefined;
|
|
221
180
|
apiUrl?: string | undefined;
|
|
@@ -242,19 +201,19 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodO
|
|
|
242
201
|
privateKeyEnv: z.ZodEffects<z.ZodString, string, string>;
|
|
243
202
|
mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
244
203
|
}, "strip", z.ZodTypeAny, {
|
|
245
|
-
provider: "ssh";
|
|
246
204
|
host: string;
|
|
205
|
+
provider: "ssh";
|
|
247
206
|
username: string;
|
|
248
207
|
privateKeyEnv: string;
|
|
249
|
-
mountPath?: string | undefined;
|
|
250
208
|
port?: number | undefined;
|
|
209
|
+
mountPath?: string | undefined;
|
|
251
210
|
}, {
|
|
252
|
-
provider: "ssh";
|
|
253
211
|
host: string;
|
|
212
|
+
provider: "ssh";
|
|
254
213
|
username: string;
|
|
255
214
|
privateKeyEnv: string;
|
|
256
|
-
mountPath?: string | undefined;
|
|
257
215
|
port?: number | undefined;
|
|
216
|
+
mountPath?: string | undefined;
|
|
258
217
|
}>, z.ZodObject<{
|
|
259
218
|
provider: z.ZodLiteral<"adb">;
|
|
260
219
|
serial: z.ZodEffects<z.ZodString, string, string>;
|
package/dist/remote-exec.js
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `remoteExec` — the execution-substrate contract (NET-NEW; not part of DOMAIN_SCHEMAS today).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* local/remote-docker fast-follow). The schema LEADS; runtime support is phased. The discriminator is `provider`:
|
|
8
|
-
* FLEET (implemented today, the worker service's config module):
|
|
4
|
+
* The exec layer is user-configurable & pluggable. This SCHEMA carries the per-machine `config.d/remote-exec.json`
|
|
5
|
+
* shape for the lanes a consumer can drive from that file. The discriminator is `provider`:
|
|
6
|
+
* FLEET:
|
|
9
7
|
* - `e2b` per-task E2B Firecracker VM (isolated, suspendable). The default code-agent worker.
|
|
10
8
|
* - `k8s` per-task Kata pod on our k3s (isolated, NOT suspendable). Self-hosted sandbox lane.
|
|
11
9
|
* - `ssh` a real host over SSH (NOT isolated/suspendable). AI-orchestrated batch deploy.
|
|
12
10
|
* - `adb` a real device over ADB (NOT isolated/suspendable). On-device APP testing.
|
|
13
|
-
* TOC single-machine (
|
|
11
|
+
* TOC single-machine (macOS + Linux):
|
|
14
12
|
* - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
|
|
15
|
-
* agent). No isolation.
|
|
13
|
+
* agent). No isolation.
|
|
16
14
|
* - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
|
|
17
|
-
*
|
|
15
|
+
*
|
|
16
|
+
* 7.0.0: every `provider` word here is an execution lane word (`EXECUTION_LANE_WORDS`, `execution-lanes.ts` — the ONE
|
|
17
|
+
* vocabulary; `test/execution-lanes-7.0.0.test.ts` pins `provider ⊆ EXECUTION_LANE_WORDS`, at runtime and at compile time).
|
|
18
|
+
* The `remote-docker` variant was DELETED (zero aliases): no engine read point ever turned that word into a running lane
|
|
19
|
+
* (the engine refuses `REMOTE_EXEC=remote-docker` at boot and only warned "unwired" when this file named it). A remote
|
|
20
|
+
* docker daemon is the `local-docker` lane pointed at it (`DOCKER_HOST` — in a WorkerSpec, `runtime.execution.coords`).
|
|
18
21
|
*
|
|
19
22
|
* SECRET BOUNDARY (identical to the rest of the contract): every secret VALUE the consumer reads from its own
|
|
20
|
-
* env (E2B_API_KEY, K8S_TOKEN, SSH_PRIVATE_KEY, MINIO_
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
* env (E2B_API_KEY, K8S_TOKEN, SSH_PRIVATE_KEY, MINIO_*) is carried here as an env-NAME ref (the `ENV_NAME` regex
|
|
24
|
+
* from types.ts) — NEVER a literal value. The consumer resolves NAME → value from its own env (service env / k8s
|
|
25
|
+
* secret / the TOC local `.env`), so this package never carries a credential. This is SCHEMA + secret-as-env-NAME
|
|
26
|
+
* ONLY; the service owns the adapter implementations.
|
|
24
27
|
*/
|
|
25
28
|
import { z } from "zod";
|
|
26
|
-
import { ENV_NAME, K8S_QUANTITY, NON_SECRET_URL, TCP_PORT, containsSecretToken, hasNul
|
|
29
|
+
import { ENV_NAME, K8S_QUANTITY, NON_SECRET_URL, TCP_PORT, containsSecretToken, hasNul } from "./types.js";
|
|
27
30
|
/** A non-empty exec-bound string (an image / path / shell / arg the adapter feeds to spawn()/fs). Rejects a NUL
|
|
28
31
|
* byte uniformly — a NUL validates here but makes Node throw at spawn/boot (fail-OPEN → runtime crash). R20. */
|
|
29
32
|
const execStr = z.string().min(1).refine((s) => !hasNul(s), "must not contain a NUL byte");
|
|
30
|
-
/** Container knobs
|
|
31
|
-
*
|
|
33
|
+
/** Container knobs of the `local-docker` lane. `network` defaults to `none` at the adapter (the safe posture for
|
|
34
|
+
* untrusted code — see the dual-mode design note). */
|
|
32
35
|
const DOCKER_FIELDS = {
|
|
33
36
|
/** Sandbox image (required — a run must be reproducible). */
|
|
34
37
|
image: execStr,
|
|
@@ -58,60 +61,6 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
|
|
|
58
61
|
// ── local-docker: containers on this machine's docker (isolated; enables parallel fan-out). No secrets
|
|
59
62
|
// (local docker.sock). Service implements this fast-follow after host.
|
|
60
63
|
z.object({ provider: z.literal("local-docker"), ...DOCKER_FIELDS }),
|
|
61
|
-
// ── remote-docker: a user-specified remote host's docker. Creds (TLS for tcp://, key for ssh://) are
|
|
62
|
-
// env-NAME refs, never bytes. Service implements this fast-follow.
|
|
63
|
-
z.object({
|
|
64
|
-
provider: z.literal("remote-docker"),
|
|
65
|
-
...DOCKER_FIELDS,
|
|
66
|
-
/** The remote docker endpoint — a DOCKER_HOST url: `ssh://user@host` or `tcp://host:2376`. A `user@` is OK
|
|
67
|
-
* (it's the ssh target, not a secret); an embedded PASSWORD is rejected — credentials go in the env-NAME
|
|
68
|
-
* fields below, NEVER in the URL (the secret-as-env-NAME boundary). */
|
|
69
|
-
dockerHost: z
|
|
70
|
-
.string()
|
|
71
|
-
.min(1)
|
|
72
|
-
.refine((s) => {
|
|
73
|
-
if (hasNul(s))
|
|
74
|
-
return false; // a NUL in DOCKER_HOST crashes the adapter at spawn — reject (R20)
|
|
75
|
-
try {
|
|
76
|
-
const u = new URL(s);
|
|
77
|
-
if (u.password)
|
|
78
|
-
return false; // a `user@` ssh target is fine; an embedded PASSWORD is not
|
|
79
|
-
// a secret can also ride in the username (`ssh://sk-live-…@host`) or the query/fragment — scan them
|
|
80
|
-
// (Round-4). The userinfo password is already rejected above.
|
|
81
|
-
let user = u.username;
|
|
82
|
-
try {
|
|
83
|
-
user = decodeURIComponent(u.username);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
/* keep raw */
|
|
87
|
-
}
|
|
88
|
-
if (user && containsSecretToken(user))
|
|
89
|
-
return false;
|
|
90
|
-
return !urlQueryFragmentHasSecret(u);
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
// unparseable (e.g. a bare socket path) → reject a `:pw@` userinfo-password pattern AND any
|
|
94
|
-
// secret-shaped TOKEN anywhere (containsSecretToken tokenizes on URL/query delimiters incl =&, so a
|
|
95
|
-
// mid-string `/var/run/sk-…` or `socket?token=sk-…` is caught despite the ^-anchored regex). R4/5.
|
|
96
|
-
if (/:[^/@]+@/.test(s))
|
|
97
|
-
return false;
|
|
98
|
-
if (containsSecretToken(s))
|
|
99
|
-
return false;
|
|
100
|
-
try {
|
|
101
|
-
return !containsSecretToken(decodeURIComponent(s)); // also catch percent-encoded secrets
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return true; // malformed %-escape → the raw scan above already passed
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}, "no credentials in dockerHost (password / secret-shaped user/query/fragment) — put them in the env-NAME fields (sshKeyEnv / tls*Env)"),
|
|
108
|
-
/** env-NAMEs for a tcp+TLS endpoint (NEVER the cert/key bytes). */
|
|
109
|
-
tlsCertEnv: ENV_NAME.optional(),
|
|
110
|
-
tlsKeyEnv: ENV_NAME.optional(),
|
|
111
|
-
tlsCaEnv: ENV_NAME.optional(),
|
|
112
|
-
/** env-NAME for the SSH private key when dockerHost is `ssh://…` (NEVER the key value). */
|
|
113
|
-
sshKeyEnv: ENV_NAME.optional(),
|
|
114
|
-
}),
|
|
115
64
|
// ── e2b: the user's E2B account (service provider "e2b"; REMOTE_EXEC=e2b + E2B_API_KEY).
|
|
116
65
|
z.object({
|
|
117
66
|
provider: z.literal("e2b"),
|
|
@@ -163,8 +112,8 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
|
|
|
163
112
|
// ── ssh: a real host over SSH (service provider "ssh"; REMOTE_EXEC=ssh + SSH_HOST/SSH_USER/SSH_PRIVATE_KEY).
|
|
164
113
|
z.object({
|
|
165
114
|
provider: z.literal("ssh"),
|
|
166
|
-
// host + username are persisted to config.d/remote-exec.json — scan for a secret-shaped literal
|
|
167
|
-
//
|
|
115
|
+
// host + username are persisted to config.d/remote-exec.json — scan for a secret-shaped literal (a secret must go
|
|
116
|
+
// in privateKeyEnv, not the login). R14.
|
|
168
117
|
host: execStr.refine((s) => !containsSecretToken(s), "no secret literal in ssh host — credentials go in privateKeyEnv"),
|
|
169
118
|
port: TCP_PORT.optional(),
|
|
170
119
|
username: execStr.refine((s) => !containsSecretToken(s), "no secret literal in ssh username — credentials go in privateKeyEnv"),
|
package/dist/secret-refs.d.ts
CHANGED
|
@@ -22,7 +22,8 @@ export interface EnvRef {
|
|
|
22
22
|
* (integrations domain DELETED in 0.6.0 — OA/Gitea coordinates ride worker.env/secretEnv, EXPERT-REDESIGN §9.) */
|
|
23
23
|
export declare function collectEnvRefs(eff: EffectiveConfig): EnvRef[];
|
|
24
24
|
/**
|
|
25
|
-
* Names of workers that reference a given env-NAME — via any of the
|
|
25
|
+
* Names of workers that reference a given env-NAME — via any of the structured `secretRefs`, the execution lane's
|
|
26
|
+
* `runtime.execution.secretRefs` (7.0.0: where E2B_API_KEY / SSH_PRIVATE_KEY / K8S_TOKEN … live now), OR the general
|
|
26
27
|
* `secretEnv` passthrough. Drives batch-rotate (POST /secrets/<name>/rotate): after a credential write, recreate
|
|
27
28
|
* exactly the workers that project it. Reads the raw workers domain (the same shape collectEnvRefs would see).
|
|
28
29
|
*/
|
|
@@ -30,4 +31,9 @@ export declare function workersReferencingSecret(workers: {
|
|
|
30
31
|
name: string;
|
|
31
32
|
secretRefs?: Record<string, string | undefined>;
|
|
32
33
|
secretEnv?: Record<string, string>;
|
|
34
|
+
runtime?: {
|
|
35
|
+
execution?: {
|
|
36
|
+
secretRefs?: Record<string, string>;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
33
39
|
}[], envName: string): string[];
|
package/dist/secret-refs.js
CHANGED
|
@@ -16,7 +16,11 @@ export function collectEnvRefs(eff) {
|
|
|
16
16
|
add(r.apiKeyEnv, "workers", `worker "${w.name}".secretRefs.apiKeyEnv`);
|
|
17
17
|
add(r.serviceAuthTokenEnv, "workers", `worker "${w.name}".secretRefs.serviceAuthTokenEnv`);
|
|
18
18
|
add(r.gitTokenEnv, "workers", `worker "${w.name}".secretRefs.gitTokenEnv`);
|
|
19
|
-
add(r.
|
|
19
|
+
add(r.dbPasswordEnv, "workers", `worker "${w.name}".secretRefs.dbPasswordEnv`);
|
|
20
|
+
// 7.0.0: the execution lane's secrets (E2B_API_KEY / SSH_PRIVATE_KEY / K8S_TOKEN …) moved out of the general
|
|
21
|
+
// `secretEnv` passthrough into `runtime.execution.secretRefs` — still env-NAMEs the host must provide.
|
|
22
|
+
for (const [key, envName] of Object.entries(w.runtime.execution?.secretRefs ?? {}))
|
|
23
|
+
add(envName, "workers", `worker "${w.name}".runtime.execution.secretRefs.${key}`);
|
|
20
24
|
// the general secretEnv passthrough also references env-NAMEs (worker-env.ts merges them) — include them
|
|
21
25
|
// so the rotation inventory isn't blind to app-specific tokens like OA_SERVICE_TOKEN→OA_CALLBACK_TOKEN.
|
|
22
26
|
for (const [key, envName] of Object.entries(w.secretEnv ?? {}))
|
|
@@ -32,7 +36,8 @@ export function collectEnvRefs(eff) {
|
|
|
32
36
|
return [...map.entries()].map(([envName, refs]) => ({ envName, refs })).sort((a, b) => a.envName.localeCompare(b.envName));
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
35
|
-
* Names of workers that reference a given env-NAME — via any of the
|
|
39
|
+
* Names of workers that reference a given env-NAME — via any of the structured `secretRefs`, the execution lane's
|
|
40
|
+
* `runtime.execution.secretRefs` (7.0.0: where E2B_API_KEY / SSH_PRIVATE_KEY / K8S_TOKEN … live now), OR the general
|
|
36
41
|
* `secretEnv` passthrough. Drives batch-rotate (POST /secrets/<name>/rotate): after a credential write, recreate
|
|
37
42
|
* exactly the workers that project it. Reads the raw workers domain (the same shape collectEnvRefs would see).
|
|
38
43
|
*/
|
|
@@ -40,8 +45,9 @@ export function workersReferencingSecret(workers, envName) {
|
|
|
40
45
|
const out = [];
|
|
41
46
|
for (const w of workers) {
|
|
42
47
|
const inStructured = Object.values(w.secretRefs ?? {}).includes(envName);
|
|
48
|
+
const inLane = Object.values(w.runtime?.execution?.secretRefs ?? {}).includes(envName);
|
|
43
49
|
const inPassthrough = Object.values(w.secretEnv ?? {}).includes(envName);
|
|
44
|
-
if (inStructured || inPassthrough)
|
|
50
|
+
if (inStructured || inLane || inPassthrough)
|
|
45
51
|
out.push(w.name);
|
|
46
52
|
}
|
|
47
53
|
return out;
|