@sema-ai/registry-core 0.10.3

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 +131 -0
  2. package/README.md +84 -0
  3. package/dist/api/auth-bridge.d.ts +332 -0
  4. package/dist/api/auth-bridge.d.ts.map +1 -0
  5. package/dist/api/auth-bridge.js +211 -0
  6. package/dist/api/auth-bridge.js.map +1 -0
  7. package/dist/api/auth.d.ts +258 -0
  8. package/dist/api/auth.d.ts.map +1 -0
  9. package/dist/api/auth.js +204 -0
  10. package/dist/api/auth.js.map +1 -0
  11. package/dist/api/scopes.d.ts +353 -0
  12. package/dist/api/scopes.d.ts.map +1 -0
  13. package/dist/api/scopes.js +229 -0
  14. package/dist/api/scopes.js.map +1 -0
  15. package/dist/bundle.d.ts +14 -0
  16. package/dist/bundle.d.ts.map +1 -0
  17. package/dist/bundle.js +68 -0
  18. package/dist/bundle.js.map +1 -0
  19. package/dist/config-fns.d.ts +236 -0
  20. package/dist/config-fns.d.ts.map +1 -0
  21. package/dist/config-fns.js +272 -0
  22. package/dist/config-fns.js.map +1 -0
  23. package/dist/cross-domain.d.ts +35 -0
  24. package/dist/cross-domain.d.ts.map +1 -0
  25. package/dist/cross-domain.js +119 -0
  26. package/dist/cross-domain.js.map +1 -0
  27. package/dist/file-edit.d.ts +37 -0
  28. package/dist/file-edit.d.ts.map +1 -0
  29. package/dist/file-edit.js +126 -0
  30. package/dist/file-edit.js.map +1 -0
  31. package/dist/file-store.d.ts +39 -0
  32. package/dist/file-store.d.ts.map +1 -0
  33. package/dist/file-store.js +142 -0
  34. package/dist/file-store.js.map +1 -0
  35. package/dist/fleet.d.ts +479 -0
  36. package/dist/fleet.d.ts.map +1 -0
  37. package/dist/fleet.js +303 -0
  38. package/dist/fleet.js.map +1 -0
  39. package/dist/hash.d.ts +33 -0
  40. package/dist/hash.d.ts.map +1 -0
  41. package/dist/hash.js +60 -0
  42. package/dist/hash.js.map +1 -0
  43. package/dist/hooks.d.ts +5478 -0
  44. package/dist/hooks.d.ts.map +1 -0
  45. package/dist/hooks.js +628 -0
  46. package/dist/hooks.js.map +1 -0
  47. package/dist/index.d.ts +23 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +27 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/local-load.d.ts +33 -0
  52. package/dist/local-load.d.ts.map +1 -0
  53. package/dist/local-load.js +164 -0
  54. package/dist/local-load.js.map +1 -0
  55. package/dist/migrate.d.ts +245 -0
  56. package/dist/migrate.d.ts.map +1 -0
  57. package/dist/migrate.js +543 -0
  58. package/dist/migrate.js.map +1 -0
  59. package/dist/node.d.ts +12 -0
  60. package/dist/node.d.ts.map +1 -0
  61. package/dist/node.js +12 -0
  62. package/dist/node.js.map +1 -0
  63. package/dist/reader.d.ts +19 -0
  64. package/dist/reader.d.ts.map +1 -0
  65. package/dist/reader.js +2 -0
  66. package/dist/reader.js.map +1 -0
  67. package/dist/remote-exec.d.ts +275 -0
  68. package/dist/remote-exec.d.ts.map +1 -0
  69. package/dist/remote-exec.js +183 -0
  70. package/dist/remote-exec.js.map +1 -0
  71. package/dist/resolve-roster.d.ts +29 -0
  72. package/dist/resolve-roster.d.ts.map +1 -0
  73. package/dist/resolve-roster.js +105 -0
  74. package/dist/resolve-roster.js.map +1 -0
  75. package/dist/safety-merge-spec.d.ts +328 -0
  76. package/dist/safety-merge-spec.d.ts.map +1 -0
  77. package/dist/safety-merge-spec.js +65 -0
  78. package/dist/safety-merge-spec.js.map +1 -0
  79. package/dist/scheduler-store-node.d.ts +15 -0
  80. package/dist/scheduler-store-node.d.ts.map +1 -0
  81. package/dist/scheduler-store-node.js +43 -0
  82. package/dist/scheduler-store-node.js.map +1 -0
  83. package/dist/scheduler-store.d.ts +65 -0
  84. package/dist/scheduler-store.d.ts.map +1 -0
  85. package/dist/scheduler-store.js +59 -0
  86. package/dist/scheduler-store.js.map +1 -0
  87. package/dist/secret-refs.d.ts +34 -0
  88. package/dist/secret-refs.d.ts.map +1 -0
  89. package/dist/secret-refs.js +49 -0
  90. package/dist/secret-refs.js.map +1 -0
  91. package/dist/sha256.d.ts +17 -0
  92. package/dist/sha256.d.ts.map +1 -0
  93. package/dist/sha256.js +115 -0
  94. package/dist/sha256.js.map +1 -0
  95. package/dist/skills-manifest.d.ts +13 -0
  96. package/dist/skills-manifest.d.ts.map +1 -0
  97. package/dist/skills-manifest.js +55 -0
  98. package/dist/skills-manifest.js.map +1 -0
  99. package/dist/types.d.ts +9911 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +1727 -0
  102. package/dist/types.js.map +1 -0
  103. package/package.json +130 -0
@@ -0,0 +1,19 @@
1
+ import type { DomainConfig, DomainName, EffectiveConfig } from "./types.js";
2
+ /**
3
+ * The MINIMAL read surface a consumer needs to resolve config — a local file source (FileConfigStore), the
4
+ * TOC desktop, or a test fixture implements this WITHOUT stubbing the ~32 publish/RBAC/worker-status/lifecycle
5
+ * methods of sema-registry's full ConfigStore.
6
+ *
7
+ * sema-registry's fat `ConfigStore` should `extends ConfigReader` (every store impl already has all three
8
+ * methods, so the extension is satisfied automatically). The signatures here MUST match store.ts exactly
9
+ * (incl. the `<K extends DomainName>` generic and the `| undefined` on getDomain) or that `extends` fails.
10
+ */
11
+ export interface ConfigReader {
12
+ /** The stored payload for a domain, validated; `undefined` if never set. */
13
+ getDomain<K extends DomainName>(domain: K): Promise<DomainConfig[K] | undefined>;
14
+ /** All domains merged (defaults for unset) + a monotonic version — what a consumer pulls. */
15
+ getEffective(): Promise<EffectiveConfig>;
16
+ /** The current monotonic version ALONE — a cheap read so a conditional poll can short-circuit. */
17
+ getVersion(): Promise<number>;
18
+ }
19
+ //# sourceMappingURL=reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,SAAS,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACjF,6FAA6F;IAC7F,YAAY,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACzC,kGAAkG;IAClG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/B"}
package/dist/reader.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.js","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":""}
@@ -0,0 +1,275 @@
1
+ /**
2
+ * `remoteExec` — the execution-substrate contract (NET-NEW; not part of DOMAIN_SCHEMAS today).
3
+ *
4
+ * Per Clay's decision the exec layer is user-configurable & pluggable. This SCHEMA is a FORWARD-COMPATIBLE
5
+ * SUPERSET: it carries both the backends sema-server already implements today AND the TOC single-machine
6
+ * backends Clay added (the service implements those in PHASES — host + e2b first, local/remote-docker
7
+ * fast-follow). The schema LEADS; runtime support is phased. The discriminator is `provider`:
8
+ * FLEET (implemented today, sema-server/src/config.ts):
9
+ * - `e2b` per-task E2B Firecracker VM (isolated, suspendable). The default code-agent worker.
10
+ * - `k8s` per-task Kata pod on our k3s (isolated, NOT suspendable). Self-hosted sandbox lane.
11
+ * - `ssh` a real host over SSH (NOT isolated/suspendable). AI-orchestrated batch deploy.
12
+ * - `adb` a real device over ADB (NOT isolated/suspendable). On-device APP testing.
13
+ * TOC single-machine (Clay; service implements in phases — macOS + Linux first):
14
+ * - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
15
+ * agent). No isolation. macOS/Linux.
16
+ * - `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).
18
+ *
19
+ * 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.
24
+ */
25
+ import { z } from "zod";
26
+ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodObject<{
27
+ provider: z.ZodLiteral<"host">;
28
+ /** Working ROOT for the agent's tools; absent = the workspace/cwd. (≡ sema-server's resolved
29
+ * `workspaceBase` — the adapter runs each task under `<workdir>/ai-agent-host-<id>`.) */
30
+ workdir: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
31
+ /** Optional writable-path allowlist bounding what the host-exec tools may touch (defence for a TOC box). */
32
+ allowedPaths: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
33
+ /** Shell to spawn (absent = the platform default). */
34
+ shell: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
35
+ /** Per-COMMAND timeout (ms) for the host lane — host is long-lived so this bounds a single exec, not the
36
+ * task (mirrors e2b/k8s `timeoutMs`; = sema-server's `commandTimeoutMs`). Absent = adapter default. */
37
+ commandTimeoutMs: z.ZodOptional<z.ZodNumber>;
38
+ }, "strip", z.ZodTypeAny, {
39
+ provider: "host";
40
+ workdir?: string | undefined;
41
+ allowedPaths?: string[] | undefined;
42
+ shell?: string | undefined;
43
+ commandTimeoutMs?: number | undefined;
44
+ }, {
45
+ provider: "host";
46
+ workdir?: string | undefined;
47
+ allowedPaths?: string[] | undefined;
48
+ shell?: string | undefined;
49
+ commandTimeoutMs?: number | undefined;
50
+ }>, z.ZodObject<{
51
+ image: z.ZodEffects<z.ZodString, string, string>;
52
+ memory: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
53
+ cpus: z.ZodOptional<z.ZodNumber>;
54
+ network: z.ZodOptional<z.ZodEnum<["none", "bridge", "host"]>>;
55
+ workdir: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
56
+ mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
57
+ provider: z.ZodLiteral<"local-docker">;
58
+ }, "strip", z.ZodTypeAny, {
59
+ provider: "local-docker";
60
+ image: string;
61
+ memory?: string | undefined;
62
+ workdir?: string | undefined;
63
+ cpus?: number | undefined;
64
+ network?: "host" | "none" | "bridge" | undefined;
65
+ mountPath?: string | undefined;
66
+ }, {
67
+ provider: "local-docker";
68
+ image: string;
69
+ memory?: string | undefined;
70
+ workdir?: string | undefined;
71
+ cpus?: number | undefined;
72
+ network?: "host" | "none" | "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?: "host" | "none" | "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?: "host" | "none" | "bridge" | undefined;
113
+ mountPath?: string | undefined;
114
+ tlsCertEnv?: string | undefined;
115
+ tlsKeyEnv?: string | undefined;
116
+ tlsCaEnv?: string | undefined;
117
+ sshKeyEnv?: string | undefined;
118
+ }>, z.ZodObject<{
119
+ provider: z.ZodLiteral<"e2b">;
120
+ /** env-NAME holding the E2B API key (service: E2B_API_KEY). NEVER the key. */
121
+ apiKeyEnv: z.ZodEffects<z.ZodString, string, string>;
122
+ /** E2B template id (service: E2B_TEMPLATE). */
123
+ template: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
124
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
125
+ /** Idle/liveness bound for a command (service: E2B_LIVENESS_MS, the [49] hang-fix). */
126
+ livenessMs: z.ZodOptional<z.ZodNumber>;
127
+ allowInternetAccess: z.ZodOptional<z.ZodBoolean>;
128
+ /** Which of THIS process's env-NAMEs to forward into the sandbox (service: E2B_SANDBOX_ENV, a CSV of NAMEs). */
129
+ sandboxEnv: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
130
+ }, "strip", z.ZodTypeAny, {
131
+ provider: "e2b";
132
+ apiKeyEnv: string;
133
+ template?: string | undefined;
134
+ timeoutMs?: number | undefined;
135
+ livenessMs?: number | undefined;
136
+ allowInternetAccess?: boolean | undefined;
137
+ sandboxEnv?: string[] | undefined;
138
+ }, {
139
+ provider: "e2b";
140
+ apiKeyEnv: string;
141
+ template?: string | undefined;
142
+ timeoutMs?: number | undefined;
143
+ livenessMs?: number | undefined;
144
+ allowInternetAccess?: boolean | undefined;
145
+ sandboxEnv?: string[] | undefined;
146
+ }>, z.ZodObject<{
147
+ provider: z.ZodLiteral<"k8s">;
148
+ /** Sandbox container image (service: K8S_SANDBOX_IMAGE). Required so a run is reproducible. */
149
+ image: z.ZodEffects<z.ZodString, string, string>;
150
+ /** Kube API URL (service: K8S_API_URL); absent → in-cluster service account. NO embedded credentials. */
151
+ apiUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
152
+ /** env-NAME holding the kube bearer token (service: K8S_TOKEN). NEVER the token. */
153
+ tokenEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
154
+ /** env-NAME holding the base64 CA cert (service: K8S_CA_CERT_B64). NEVER the cert bytes. */
155
+ caCertEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
156
+ insecureTls: z.ZodOptional<z.ZodBoolean>;
157
+ namespace: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
158
+ runtimeClass: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
159
+ mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
160
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
161
+ /** Sandbox pod resource limits (service: K8S_MEMORY / K8S_CPU; adapter defaults 2Gi / 1 cpu). */
162
+ memory: z.ZodOptional<z.ZodString>;
163
+ cpu: z.ZodOptional<z.ZodString>;
164
+ /** Workspace-durable suspend/resume via MinIO/S3 (service: MINIO_*). Creds are env-NAME refs; the sandbox
165
+ * only ever sees a presigned URL. accessKeyEnv + secretKeyEnv required to enable (mirrors the service's
166
+ * "all three of endpoint/access/secret required" gate). */
167
+ s3Snapshot: z.ZodOptional<z.ZodObject<{
168
+ endpoint: z.ZodEffects<z.ZodString, string, string>;
169
+ bucket: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
170
+ accessKeyEnv: z.ZodEffects<z.ZodString, string, string>;
171
+ secretKeyEnv: z.ZodEffects<z.ZodString, string, string>;
172
+ region: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
173
+ keyPrefix: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
174
+ presignTtlSec: z.ZodOptional<z.ZodNumber>;
175
+ }, "strip", z.ZodTypeAny, {
176
+ endpoint: string;
177
+ accessKeyEnv: string;
178
+ secretKeyEnv: string;
179
+ bucket?: string | undefined;
180
+ region?: string | undefined;
181
+ keyPrefix?: string | undefined;
182
+ presignTtlSec?: number | undefined;
183
+ }, {
184
+ endpoint: string;
185
+ accessKeyEnv: string;
186
+ secretKeyEnv: string;
187
+ bucket?: string | undefined;
188
+ region?: string | undefined;
189
+ keyPrefix?: string | undefined;
190
+ presignTtlSec?: number | undefined;
191
+ }>>;
192
+ }, "strip", z.ZodTypeAny, {
193
+ provider: "k8s";
194
+ image: string;
195
+ cpu?: string | undefined;
196
+ memory?: string | undefined;
197
+ mountPath?: string | undefined;
198
+ timeoutMs?: number | undefined;
199
+ apiUrl?: string | undefined;
200
+ tokenEnv?: string | undefined;
201
+ caCertEnv?: string | undefined;
202
+ insecureTls?: boolean | undefined;
203
+ namespace?: string | undefined;
204
+ runtimeClass?: string | undefined;
205
+ s3Snapshot?: {
206
+ endpoint: string;
207
+ accessKeyEnv: string;
208
+ secretKeyEnv: string;
209
+ bucket?: string | undefined;
210
+ region?: string | undefined;
211
+ keyPrefix?: string | undefined;
212
+ presignTtlSec?: number | undefined;
213
+ } | undefined;
214
+ }, {
215
+ provider: "k8s";
216
+ image: string;
217
+ cpu?: string | undefined;
218
+ memory?: string | undefined;
219
+ mountPath?: string | undefined;
220
+ timeoutMs?: number | undefined;
221
+ apiUrl?: string | undefined;
222
+ tokenEnv?: string | undefined;
223
+ caCertEnv?: string | undefined;
224
+ insecureTls?: boolean | undefined;
225
+ namespace?: string | undefined;
226
+ runtimeClass?: string | undefined;
227
+ s3Snapshot?: {
228
+ endpoint: string;
229
+ accessKeyEnv: string;
230
+ secretKeyEnv: string;
231
+ bucket?: string | undefined;
232
+ region?: string | undefined;
233
+ keyPrefix?: string | undefined;
234
+ presignTtlSec?: number | undefined;
235
+ } | undefined;
236
+ }>, z.ZodObject<{
237
+ provider: z.ZodLiteral<"ssh">;
238
+ host: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
239
+ port: z.ZodOptional<z.ZodNumber>;
240
+ username: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
241
+ /** env-NAME holding the SSH private key (service: SSH_PRIVATE_KEY). NEVER the key value. */
242
+ privateKeyEnv: z.ZodEffects<z.ZodString, string, string>;
243
+ mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
244
+ }, "strip", z.ZodTypeAny, {
245
+ provider: "ssh";
246
+ host: string;
247
+ username: string;
248
+ privateKeyEnv: string;
249
+ mountPath?: string | undefined;
250
+ port?: number | undefined;
251
+ }, {
252
+ provider: "ssh";
253
+ host: string;
254
+ username: string;
255
+ privateKeyEnv: string;
256
+ mountPath?: string | undefined;
257
+ port?: number | undefined;
258
+ }>, z.ZodObject<{
259
+ provider: z.ZodLiteral<"adb">;
260
+ serial: z.ZodEffects<z.ZodString, string, string>;
261
+ adbPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
262
+ mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
263
+ }, "strip", z.ZodTypeAny, {
264
+ provider: "adb";
265
+ serial: string;
266
+ mountPath?: string | undefined;
267
+ adbPath?: string | undefined;
268
+ }, {
269
+ provider: "adb";
270
+ serial: string;
271
+ mountPath?: string | undefined;
272
+ adbPath?: string | undefined;
273
+ }>]>;
274
+ export type RemoteExecSpec = z.infer<typeof RemoteExecSpec>;
275
+ //# sourceMappingURL=remote-exec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-exec.d.ts","sourceRoot":"","sources":["../src/remote-exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqBxB,eAAO,MAAM,cAAc;;IAIvB;8FAC0F;;IAE1F,4GAA4G;;IAE5G,sDAAsD;;IAEtD;4GACwG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAaxG;;4EAEwE;;IAmCxE,mEAAmE;;;;IAInE,2FAA2F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAO3F,8EAA8E;;IAE9E,+CAA+C;;;IAG/C,uFAAuF;;;IAGvF,gHAAgH;;;;;;;;;;;;;;;;;;;;IAOhH,+FAA+F;;IAE/F,yGAAyG;;IAEzG,oFAAoF;;IAEpF,4FAA4F;;;;;;;IAO5F,iGAAiG;;;IAGjG;;gEAE4D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsB5D,4FAA4F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAY9F,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * `remoteExec` — the execution-substrate contract (NET-NEW; not part of DOMAIN_SCHEMAS today).
3
+ *
4
+ * Per Clay's decision the exec layer is user-configurable & pluggable. This SCHEMA is a FORWARD-COMPATIBLE
5
+ * SUPERSET: it carries both the backends sema-server already implements today AND the TOC single-machine
6
+ * backends Clay added (the service implements those in PHASES — host + e2b first, local/remote-docker
7
+ * fast-follow). The schema LEADS; runtime support is phased. The discriminator is `provider`:
8
+ * FLEET (implemented today, sema-server/src/config.ts):
9
+ * - `e2b` per-task E2B Firecracker VM (isolated, suspendable). The default code-agent worker.
10
+ * - `k8s` per-task Kata pod on our k3s (isolated, NOT suspendable). Self-hosted sandbox lane.
11
+ * - `ssh` a real host over SSH (NOT isolated/suspendable). AI-orchestrated batch deploy.
12
+ * - `adb` a real device over ADB (NOT isolated/suspendable). On-device APP testing.
13
+ * TOC single-machine (Clay; service implements in phases — macOS + Linux first):
14
+ * - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
15
+ * agent). No isolation. macOS/Linux.
16
+ * - `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).
18
+ *
19
+ * 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.
24
+ */
25
+ import { z } from "zod";
26
+ import { ENV_NAME, K8S_QUANTITY, NON_SECRET_URL, TCP_PORT, containsSecretToken, hasNul, urlQueryFragmentHasSecret } from "./types.js";
27
+ /** A non-empty exec-bound string (an image / path / shell / arg the adapter feeds to spawn()/fs). Rejects a NUL
28
+ * byte uniformly — a NUL validates here but makes Node throw at spawn/boot (fail-OPEN → runtime crash). R20. */
29
+ 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 DUAL-MODE-DESIGN.md). */
32
+ const DOCKER_FIELDS = {
33
+ /** Sandbox image (required — a run must be reproducible). */
34
+ image: execStr,
35
+ /** docker `--memory` (e.g. "2g"). */
36
+ memory: execStr.optional(),
37
+ /** docker `--cpus` (e.g. 1.5). */
38
+ cpus: z.number().finite().positive().optional(),
39
+ network: z.enum(["none", "bridge", "host"]).optional(),
40
+ workdir: execStr.optional(),
41
+ mountPath: execStr.optional(),
42
+ };
43
+ export const RemoteExecSpec = z.discriminatedUnion("provider", [
44
+ // ── host: run on THIS machine, no container (TOC default; macOS/Linux). No isolation, no secrets.
45
+ z.object({
46
+ provider: z.literal("host"),
47
+ /** Working ROOT for the agent's tools; absent = the workspace/cwd. (≡ sema-server's resolved
48
+ * `workspaceBase` — the adapter runs each task under `<workdir>/ai-agent-host-<id>`.) */
49
+ workdir: execStr.optional(),
50
+ /** Optional writable-path allowlist bounding what the host-exec tools may touch (defence for a TOC box). */
51
+ allowedPaths: z.array(execStr).optional(),
52
+ /** Shell to spawn (absent = the platform default). */
53
+ shell: execStr.optional(),
54
+ /** Per-COMMAND timeout (ms) for the host lane — host is long-lived so this bounds a single exec, not the
55
+ * task (mirrors e2b/k8s `timeoutMs`; = sema-server's `commandTimeoutMs`). Absent = adapter default. */
56
+ commandTimeoutMs: z.number().int().positive().optional(),
57
+ }),
58
+ // ── local-docker: containers on this machine's docker (isolated; enables parallel fan-out). No secrets
59
+ // (local docker.sock). Service implements this fast-follow after host.
60
+ 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 codex). 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 codex.
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
+ // ── e2b: the user's E2B account (service provider "e2b"; REMOTE_EXEC=e2b + E2B_API_KEY).
116
+ z.object({
117
+ provider: z.literal("e2b"),
118
+ /** env-NAME holding the E2B API key (service: E2B_API_KEY). NEVER the key. */
119
+ apiKeyEnv: ENV_NAME,
120
+ /** E2B template id (service: E2B_TEMPLATE). */
121
+ template: execStr.optional(),
122
+ timeoutMs: z.number().int().positive().optional(),
123
+ /** Idle/liveness bound for a command (service: E2B_LIVENESS_MS, the [49] hang-fix). */
124
+ livenessMs: z.number().int().positive().optional(),
125
+ allowInternetAccess: z.boolean().optional(),
126
+ /** Which of THIS process's env-NAMEs to forward into the sandbox (service: E2B_SANDBOX_ENV, a CSV of NAMEs). */
127
+ sandboxEnv: z.array(ENV_NAME).optional(),
128
+ }),
129
+ // ── k8s: per-task Kata pod (service provider "k8s"; REMOTE_EXEC=k8s + K8S_SANDBOX_IMAGE).
130
+ z.object({
131
+ provider: z.literal("k8s"),
132
+ /** Sandbox container image (service: K8S_SANDBOX_IMAGE). Required so a run is reproducible. */
133
+ image: execStr,
134
+ /** Kube API URL (service: K8S_API_URL); absent → in-cluster service account. NO embedded credentials. */
135
+ apiUrl: NON_SECRET_URL.optional(),
136
+ /** env-NAME holding the kube bearer token (service: K8S_TOKEN). NEVER the token. */
137
+ tokenEnv: ENV_NAME.optional(),
138
+ /** env-NAME holding the base64 CA cert (service: K8S_CA_CERT_B64). NEVER the cert bytes. */
139
+ caCertEnv: ENV_NAME.optional(),
140
+ insecureTls: z.boolean().optional(),
141
+ namespace: execStr.optional(),
142
+ runtimeClass: execStr.optional(),
143
+ mountPath: execStr.optional(),
144
+ timeoutMs: z.number().int().positive().optional(),
145
+ /** Sandbox pod resource limits (service: K8S_MEMORY / K8S_CPU; adapter defaults 2Gi / 1 cpu). */
146
+ memory: K8S_QUANTITY.optional(),
147
+ cpu: K8S_QUANTITY.optional(),
148
+ /** Workspace-durable suspend/resume via MinIO/S3 (service: MINIO_*). Creds are env-NAME refs; the sandbox
149
+ * only ever sees a presigned URL. accessKeyEnv + secretKeyEnv required to enable (mirrors the service's
150
+ * "all three of endpoint/access/secret required" gate). */
151
+ s3Snapshot: z
152
+ .object({
153
+ endpoint: NON_SECRET_URL, // S3/MinIO endpoint — creds go in accessKeyEnv/secretKeyEnv, never the URL
154
+ bucket: execStr.optional(),
155
+ accessKeyEnv: ENV_NAME,
156
+ secretKeyEnv: ENV_NAME,
157
+ region: execStr.optional(),
158
+ keyPrefix: execStr.optional(),
159
+ presignTtlSec: z.number().int().positive().optional(),
160
+ })
161
+ .optional(),
162
+ }),
163
+ // ── ssh: a real host over SSH (service provider "ssh"; REMOTE_EXEC=ssh + SSH_HOST/SSH_USER/SSH_PRIVATE_KEY).
164
+ z.object({
165
+ 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.
168
+ host: execStr.refine((s) => !containsSecretToken(s), "no secret literal in ssh host — credentials go in privateKeyEnv"),
169
+ port: TCP_PORT.optional(),
170
+ username: execStr.refine((s) => !containsSecretToken(s), "no secret literal in ssh username — credentials go in privateKeyEnv"),
171
+ /** env-NAME holding the SSH private key (service: SSH_PRIVATE_KEY). NEVER the key value. */
172
+ privateKeyEnv: ENV_NAME,
173
+ mountPath: execStr.optional(),
174
+ }),
175
+ // ── adb: a real device over ADB (service provider "adb"; REMOTE_EXEC=adb + ADB_SERIAL). No secrets.
176
+ z.object({
177
+ provider: z.literal("adb"),
178
+ serial: execStr,
179
+ adbPath: execStr.optional(),
180
+ mountPath: execStr.optional(),
181
+ }),
182
+ ]);
183
+ //# sourceMappingURL=remote-exec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-exec.js","sourceRoot":"","sources":["../src/remote-exec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Per-worker roster resolution (docs/MULTI-ROSTER.md). Turns the GLOBAL effective config + a worker name
3
+ * into THAT worker's effective config: its `models` domain is the roster's view (model subset + role-map +
4
+ * @-allowlist override), everything else stays global. The `rosters` domain is control-plane-only and is
5
+ * always stripped from the wire payload (no /effective consumer reads it).
6
+ *
7
+ * Pure (no I/O) → unit-testable. Tolerant of dangling refs (unknown worker / missing roster → global
8
+ * fallback); strict referential integrity is enforced at PUT time (the [domain] route), not here.
9
+ */
10
+ import type { EffectiveConfig, EffectiveWire, ModelRole, RoleTarget, RosterEntry } from "./types.js";
11
+ /** primaryModel 标记喂的角色族(§3/[546])。 */
12
+ export declare const ROSTER_PRIMARY_ROLES: readonly ["default", "team", "synthesize"];
13
+ /** cheapModel 标记喂的角色族(§3/[546])。 */
14
+ export declare const ROSTER_CHEAP_ROLES: readonly ["subagent", "summarize"];
15
+ /** 把 roster 的主/cheap 角色标记派生成 roles 片段(PURE)。显式 roster.roles 逐角色盖过它;它盖过全局
16
+ * models.roles(合并点见 {@link resolveEffectiveForWorker})。wire 形状不变——service 零改仍兼容,
17
+ * 收敛后直接读标记亦可(effective 的 rosters 域不上 wire,标记经此物化进 models.roles)。 */
18
+ export declare function rosterMarkerRoles(r: Pick<RosterEntry, "primaryModel" | "cheapModel">): Partial<Record<ModelRole, RoleTarget>>;
19
+ export interface ResolvedEffective {
20
+ /** The wire payload: `models` resolved for the worker, `rosters` stripped, `skills` shipped as a manifest. */
21
+ config: EffectiveWire;
22
+ /** Diagnostic: the roster name applied, or "global" / "global(fallback)". Surfaced as the X-Roster header. */
23
+ rosterLabel: string;
24
+ /** Quoted ETag. Global path = `"v<version>"` (today's behaviour). Scoped path = content hash (excludes
25
+ * version/updatedAt) so an unrelated roster change at a new version doesn't churn this worker's cache. */
26
+ etag: string;
27
+ }
28
+ export declare function resolveEffectiveForWorker(eff: EffectiveConfig, worker: string | null | undefined): ResolvedEffective;
29
+ //# sourceMappingURL=resolve-roster.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-roster.d.ts","sourceRoot":"","sources":["../src/resolve-roster.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,SAAS,EAAgB,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASnH,sCAAsC;AACtC,eAAO,MAAM,oBAAoB,4CAA4E,CAAC;AAC9G,oCAAoC;AACpC,eAAO,MAAM,kBAAkB,oCAAoE,CAAC;AAEpG;;qEAEqE;AACrE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,cAAc,GAAG,YAAY,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAK7H;AAED,MAAM,WAAW,iBAAiB;IAChC,8GAA8G;IAC9G,MAAM,EAAE,aAAa,CAAC;IACtB,8GAA8G;IAC9G,WAAW,EAAE,MAAM,CAAC;IACpB;+GAC2G;IAC3G,IAAI,EAAE,MAAM,CAAC;CACd;AA4BD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,CAuDpH"}
@@ -0,0 +1,105 @@
1
+ import { stableHash } from "./hash.js";
2
+ import { toSkillsManifest } from "./skills-manifest.js";
3
+ // ── 主/cheap 角色标记语义(0.6.3, E3/[546]/[548]②)────────────────────────────────
4
+ // service 现有 ModelRoles 口径:default/team/synthesize→主、subagent/summarize→cheap。`verifier` 刻意
5
+ // 不派生(unset→core 的 default 兜底链,与 service 语义一致)。常量导出=单一语义源,service 收敛小单与
6
+ // center 表单同源引用,别第二份。
7
+ /** primaryModel 标记喂的角色族(§3/[546])。 */
8
+ export const ROSTER_PRIMARY_ROLES = ["default", "team", "synthesize"];
9
+ /** cheapModel 标记喂的角色族(§3/[546])。 */
10
+ export const ROSTER_CHEAP_ROLES = ["subagent", "summarize"];
11
+ /** 把 roster 的主/cheap 角色标记派生成 roles 片段(PURE)。显式 roster.roles 逐角色盖过它;它盖过全局
12
+ * models.roles(合并点见 {@link resolveEffectiveForWorker})。wire 形状不变——service 零改仍兼容,
13
+ * 收敛后直接读标记亦可(effective 的 rosters 域不上 wire,标记经此物化进 models.roles)。 */
14
+ export function rosterMarkerRoles(r) {
15
+ const out = {};
16
+ if (r.primaryModel)
17
+ for (const role of ROSTER_PRIMARY_ROLES)
18
+ out[role] = { model: r.primaryModel };
19
+ if (r.cheapModel)
20
+ for (const role of ROSTER_CHEAP_ROLES)
21
+ out[role] = { model: r.cheapModel };
22
+ return out;
23
+ }
24
+ /** Per-roster targeting (v2): keep items whose `rosters` is empty (= all rosters) or includes `rosterName`.
25
+ * `rosterName: null` = the unscoped/global pull → no filtering (ship all). */
26
+ function forRoster(items, rosterName) {
27
+ if (!rosterName)
28
+ return items;
29
+ return items.filter((it) => !it.rosters?.length || it.rosters.includes(rosterName));
30
+ }
31
+ /** Shape the wire payload: swap in the resolved `models`, empty the control-plane-only `rosters` domain
32
+ * (never shipped on the wire), ship `skills` as a manifest (content → contentHash), and filter
33
+ * skills + mcp by the worker's roster (`rosterName: null` on the global path = no filter).
34
+ * (The `access` domain was deleted outright in 0.6.0 — its strip here went with it, EXPERT-REDESIGN §9.
35
+ * `runtime` rides the wire as {@link EffectiveConfig}'s RuntimeWire — limit residue + governance mirror
36
+ * (双写过渡, §5) — built upstream in buildEffective; nothing to reshape here.) */
37
+ function shipped(eff, models, rosterName) {
38
+ return {
39
+ ...eff,
40
+ models,
41
+ rosters: { rosters: [] },
42
+ // entitlement is per-PRINCIPAL governance (every subject's tier/budget/caps) — control-plane only. A worker
43
+ // pull must NEVER see it (privacy + size); the per-principal view is served separately via /effective?principal=.
44
+ entitlement: { tiers: [], subjects: [], groupBindings: [], budgetGroups: [] },
45
+ skills: toSkillsManifest({ skills: forRoster(eff.skills.skills, rosterName) }),
46
+ mcp: { servers: forRoster(eff.mcp.servers, rosterName) },
47
+ };
48
+ }
49
+ export function resolveEffectiveForWorker(eff, worker) {
50
+ if (!worker) {
51
+ return { config: shipped(eff, eff.models, null), rosterLabel: "global", etag: `"v${eff.version}"` };
52
+ }
53
+ const w = eff.workers.workers.find((x) => x.name === worker);
54
+ const roster = w?.roster ? eff.rosters.rosters.find((r) => r.name === w.roster) : undefined;
55
+ if (!roster) {
56
+ // unknown worker / worker without a roster / dangling roster ref → global fallback (diagnosable).
57
+ return { config: shipped(eff, eff.models, null), rosterLabel: "global(fallback)", etag: `"v${eff.version}"` };
58
+ }
59
+ const allow = roster.models.length ? new Set(roster.models) : null; // empty roster.models = full catalog
60
+ const resolvedModels = allow ? eff.models.models.filter((m) => allow.has(m.name)) : eff.models.models;
61
+ // Defence-in-depth: the shipped @-allowlist must only name models actually in this worker's
62
+ // set — otherwise a roster that restricts `models` but keeps a stale allowlist ships an inconsistent config.
63
+ // Validation also blocks this at PUT, but filter here so resolution is self-consistent regardless. Keep
64
+ // `undefined` (no override + no global) as-is = "all enabled models are @-mentionable".
65
+ const finalNames = new Set(resolvedModels.map((m) => m.name));
66
+ const srcAllowlist = roster.atModelAllowlist ?? eff.models.atModelAllowlist;
67
+ // Same defence for `roles` as for the @-allowlist (the MORE load-bearing one — roles drive real model
68
+ // selection, not just @-mention). A `{model}`-form role pointing at a model NOT in this worker's set (an
69
+ // inherited global default under a `models` subset, or a stale role after a catalog deletion — PUT does no
70
+ // dependent re-validation) would otherwise ship a DANGLING role on the wire → the worker resolves that role
71
+ // to a non-existent model. Drop it → the consumer's core fallback (unset role → default) kicks in.
72
+ // `{select}`-form (capability-based) roles name no model → kept.
73
+ // Per-role merge (roster overrides global PER ROLE) — NOT a whole-map replace. The types promise "unset
74
+ // roster roles fall back to global models.roles per role"; a whole-map `??` silently dropped the global
75
+ // `subagent`/`team`/… when a roster set only `default`. Round-3 fix (codex). Then drop any dangling {model}.
76
+ // 0.6.3: marker-derived roles (primaryModel/cheapModel, [546] semantics) sit BETWEEN global and explicit
77
+ // roster.roles — global < markers < explicit per-role. Markers are subset-validated at PUT; a stale marker
78
+ // after a catalog change still falls through the dangling-{model} drop below (defence-in-depth).
79
+ const srcRoles = { ...eff.models.roles, ...rosterMarkerRoles(roster), ...(roster.roles ?? {}) };
80
+ const roles = Object.fromEntries(Object.entries(srcRoles).filter(([, t]) => !(t && "model" in t) || finalNames.has(t.model)));
81
+ // @-allowlist: drop names not in this worker's model set (consistency) — BUT if a NON-EMPTY allowlist would
82
+ // filter to EMPTY (the worker exposes none of the allowlisted models), DON'T ship `[]`: empty means "ALL
83
+ // @-mentionable" (contract, types.ts), which would INVERT the admin's restriction into permissive. Keep the
84
+ // source instead — the consumer also gates on model membership, so nothing the worker lacks becomes
85
+ // mentionable; the restriction is preserved (a dangling allowlist name simply never matches). Round-5 (codex).
86
+ const filteredAllow = srcAllowlist?.filter((n) => finalNames.has(n));
87
+ // 档位组(0.9.0):直通 + 逐档悬空-drop(照上面 roles 的 dangling-{model} drop 先例)——roster 收窄后
88
+ // 组内绑到本 worker 模型集之外的档剔除,引擎侧「未绑档沿链降档 fail-open」接手([604]),与 roles
89
+ // drop→core fallback 同构。组名/active 选择不动(组是 center 侧抽象,per-worker 只影响绑定可见性)。
90
+ const tierGroups = eff.models.tierGroups.map((g) => ({
91
+ ...g,
92
+ tiers: Object.fromEntries(Object.entries(g.tiers).filter(([, m]) => typeof m !== "string" || finalNames.has(m))),
93
+ }));
94
+ const models = {
95
+ models: resolvedModels,
96
+ roles,
97
+ atModelAllowlist: srcAllowlist && srcAllowlist.length > 0 && filteredAllow && filteredAllow.length === 0 ? srcAllowlist : filteredAllow,
98
+ tierGroups,
99
+ activeTierGroup: eff.models.activeTierGroup,
100
+ };
101
+ const config = shipped(eff, models, roster.name);
102
+ const { version: _v, updatedAt: _u, ...content } = config;
103
+ return { config, rosterLabel: roster.name, etag: `"${stableHash(content)}"` };
104
+ }
105
+ //# sourceMappingURL=resolve-roster.js.map