@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +40 -5
  3. package/bin/settings-schema.mjs +22 -0
  4. package/conformance/VERSION +1 -1
  5. package/conformance/cases/c48-s935-model-headers-transported/expected.json +115 -0
  6. package/conformance/cases/c48-s935-model-headers-transported/input.json +37 -0
  7. package/conformance/cases/c48-s935-model-headers-transported/meta.json +6 -0
  8. package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +98 -0
  9. package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/input.json +28 -0
  10. package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/meta.json +9 -0
  11. package/conformance/cases/c50-s784-lane-host-legal/expected.json +102 -0
  12. package/conformance/cases/c50-s784-lane-host-legal/input.json +29 -0
  13. package/conformance/cases/c50-s784-lane-host-legal/meta.json +5 -0
  14. package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +104 -0
  15. package/conformance/cases/c51-s784-lane-e2b-legal/input.json +32 -0
  16. package/conformance/cases/c51-s784-lane-e2b-legal/meta.json +5 -0
  17. package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +106 -0
  18. package/conformance/cases/c52-s784-lane-k8s-legal/input.json +34 -0
  19. package/conformance/cases/c52-s784-lane-k8s-legal/meta.json +5 -0
  20. package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +106 -0
  21. package/conformance/cases/c53-s784-lane-ssh-legal/input.json +34 -0
  22. package/conformance/cases/c53-s784-lane-ssh-legal/meta.json +5 -0
  23. package/conformance/cases/c54-s784-lane-adb-legal/expected.json +102 -0
  24. package/conformance/cases/c54-s784-lane-adb-legal/input.json +29 -0
  25. package/conformance/cases/c54-s784-lane-adb-legal/meta.json +5 -0
  26. package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +103 -0
  27. package/conformance/cases/c55-s784-lane-local-docker-legal/input.json +30 -0
  28. package/conformance/cases/c55-s784-lane-local-docker-legal/meta.json +5 -0
  29. package/conformance/cases/c56-s784-lane-device-legal/expected.json +109 -0
  30. package/conformance/cases/c56-s784-lane-device-legal/input.json +36 -0
  31. package/conformance/cases/c56-s784-lane-device-legal/meta.json +5 -0
  32. package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +83 -0
  33. package/conformance/cases/c57-s784-g1-enabled-no-lane/input.json +23 -0
  34. package/conformance/cases/c57-s784-g1-enabled-no-lane/meta.json +5 -0
  35. package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +83 -0
  36. package/conformance/cases/c58-s784-g2-multitenant-host/input.json +34 -0
  37. package/conformance/cases/c58-s784-g2-multitenant-host/meta.json +5 -0
  38. package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +83 -0
  39. package/conformance/cases/c59-s784-g3-k8s-missing-image/input.json +26 -0
  40. package/conformance/cases/c59-s784-g3-k8s-missing-image/meta.json +5 -0
  41. package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +83 -0
  42. package/conformance/cases/c60-s784-g3-e2b-missing-key/input.json +26 -0
  43. package/conformance/cases/c60-s784-g3-e2b-missing-key/meta.json +5 -0
  44. package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +83 -0
  45. package/conformance/cases/c61-s784-g3-other-lane-key/input.json +30 -0
  46. package/conformance/cases/c61-s784-g3-other-lane-key/meta.json +5 -0
  47. package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +83 -0
  48. package/conformance/cases/c62-s784-g7a-session-tidb/input.json +27 -0
  49. package/conformance/cases/c62-s784-g7a-session-tidb/meta.json +5 -0
  50. package/conformance/cases/c63-s784-host-two-replicas/expected.json +83 -0
  51. package/conformance/cases/c63-s784-host-two-replicas/input.json +27 -0
  52. package/conformance/cases/c63-s784-host-two-replicas/meta.json +5 -0
  53. package/conformance/cases/c64-s784-multitenant-local-store/expected.json +83 -0
  54. package/conformance/cases/c64-s784-multitenant-local-store/input.json +30 -0
  55. package/conformance/cases/c64-s784-multitenant-local-store/meta.json +5 -0
  56. package/conformance/cases/c65-s784-pg-without-db/expected.json +83 -0
  57. package/conformance/cases/c65-s784-pg-without-db/input.json +27 -0
  58. package/conformance/cases/c65-s784-pg-without-db/meta.json +5 -0
  59. package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +83 -0
  60. package/conformance/cases/c66-s784-memory-backend-mismatch/input.json +36 -0
  61. package/conformance/cases/c66-s784-memory-backend-mismatch/meta.json +5 -0
  62. package/conformance/cases/c67-s784-retired-field-loud/expected.json +83 -0
  63. package/conformance/cases/c67-s784-retired-field-loud/input.json +22 -0
  64. package/conformance/cases/c67-s784-retired-field-loud/meta.json +5 -0
  65. package/conformance/cases/c68-s784-device-local-store/expected.json +83 -0
  66. package/conformance/cases/c68-s784-device-local-store/input.json +26 -0
  67. package/conformance/cases/c68-s784-device-local-store/meta.json +5 -0
  68. package/conformance/cases/cli04-env-value-with-newline/expected.json +13 -2
  69. package/conformance/cases/cli04-env-value-with-newline/input.json +8 -1
  70. package/conformance/cases/cli04-env-value-with-newline/meta.json +1 -1
  71. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/expected.json +49 -0
  72. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/input.json +26 -0
  73. package/conformance/migrate-workers/m01-g5-k8s-env-lifted/meta.json +4 -0
  74. package/conformance/migrate-workers/m02-g5-idempotent/expected.json +38 -0
  75. package/conformance/migrate-workers/m02-g5-idempotent/input.json +33 -0
  76. package/conformance/migrate-workers/m02-g5-idempotent/meta.json +5 -0
  77. package/conformance/migrate-workers/m03-g5-refused-no-lane/expected.json +71 -0
  78. package/conformance/migrate-workers/m03-g5-refused-no-lane/input.json +42 -0
  79. package/conformance/migrate-workers/m03-g5-refused-no-lane/meta.json +4 -0
  80. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/expected.json +52 -0
  81. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/input.json +30 -0
  82. package/conformance/migrate-workers/m04-g7b-session-tidb-lifted/meta.json +4 -0
  83. package/dist/cli.d.ts +8 -0
  84. package/dist/cli.js +70 -0
  85. package/dist/execution-lane-index.d.ts +19 -0
  86. package/dist/execution-lane-index.js +23 -0
  87. package/dist/execution-lanes.d.ts +64 -0
  88. package/dist/execution-lanes.js +152 -0
  89. package/dist/file-edit.js +26 -21
  90. package/dist/hash.d.ts +3 -3
  91. package/dist/hash.js +3 -3
  92. package/dist/hooks.d.ts +12 -12
  93. package/dist/index.d.ts +2 -0
  94. package/dist/index.js +2 -0
  95. package/dist/migrate-workers.d.ts +63 -0
  96. package/dist/migrate-workers.js +331 -0
  97. package/dist/remote-exec.d.ts +23 -64
  98. package/dist/remote-exec.js +20 -71
  99. package/dist/secret-refs.d.ts +7 -1
  100. package/dist/secret-refs.js +9 -3
  101. package/dist/types.d.ts +1382 -430
  102. package/dist/types.js +298 -76
  103. package/package.json +9 -1
@@ -1,26 +1,29 @@
1
1
  /**
2
2
  * `remoteExec` — the execution-substrate contract (NET-NEW; not part of DOMAIN_SCHEMAS today).
3
3
  *
4
- * Per the product owner's decision the exec layer is user-configurable & pluggable. This SCHEMA is a
5
- * FORWARD-COMPATIBLE SUPERSET: it carries both the backends the worker service already implements today AND the
6
- * TOC single-machine backends the product owner added (the service implements those in PHASES — host + e2b first,
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 (product-owner scope; service implements in phases — macOS + Linux first):
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. macOS/Linux.
13
+ * agent). No isolation.
16
14
  * - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
17
- * - `remote-docker` a user-specified REMOTE host's docker (offload; isolated).
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_*, the remote-docker TLS/SSH creds) is carried here as an
21
- * env-NAME ref (the `ENV_NAME` regex from types.ts) — NEVER a literal value. The consumer resolves NAME → value
22
- * from its own env (service env / k8s secret / the TOC local `.env`), so this package never carries a
23
- * credential. This is SCHEMA + secret-as-env-NAME ONLY; the service owns the adapter implementations.
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?: "none" | "host" | "bridge" | undefined;
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?: "none" | "host" | "bridge" | undefined;
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>;
@@ -1,34 +1,37 @@
1
1
  /**
2
2
  * `remoteExec` — the execution-substrate contract (NET-NEW; not part of DOMAIN_SCHEMAS today).
3
3
  *
4
- * Per the product owner's decision the exec layer is user-configurable & pluggable. This SCHEMA is a
5
- * FORWARD-COMPATIBLE SUPERSET: it carries both the backends the worker service already implements today AND the
6
- * TOC single-machine backends the product owner added (the service implements those in PHASES — host + e2b first,
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 (product-owner scope; service implements in phases — macOS + Linux first):
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. macOS/Linux.
13
+ * agent). No isolation.
16
14
  * - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
17
- * - `remote-docker` a user-specified REMOTE host's docker (offload; isolated).
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_*, the remote-docker TLS/SSH creds) is carried here as an
21
- * env-NAME ref (the `ENV_NAME` regex from types.ts) — NEVER a literal value. The consumer resolves NAME → value
22
- * from its own env (service env / k8s secret / the TOC local `.env`), so this package never carries a
23
- * credential. This is SCHEMA + secret-as-env-NAME ONLY; the service owns the adapter implementations.
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, urlQueryFragmentHasSecret } from "./types.js";
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 shared by the two docker lanes (local-docker / remote-docker). `network` defaults to
31
- * `none` at the adapter (the safe posture for untrusted code — see the dual-mode design note). */
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, same as the
167
- // remote-docker dockerHost scans its embedded ssh user (a secret must go in privateKeyEnv, not the login). R14.
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"),
@@ -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 4 structured `secretRefs` OR the general
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[];
@@ -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.tidbPasswordEnv, "workers", `worker "${w.name}".secretRefs.tidbPasswordEnv`);
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 4 structured `secretRefs` OR the general
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;