@sealant/api-contracts-next 0.0.0-next.0 → 0.39.0-next.682

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 (40) hide show
  1. package/LICENSE +202 -0
  2. package/dist/core-api/access-tokens.d.ts +126 -0
  3. package/dist/core-api/access-tokens.js +72 -0
  4. package/dist/core-api/budgets.d.ts +15 -0
  5. package/dist/core-api/budgets.js +13 -0
  6. package/dist/core-api/connected-accounts.d.ts +226 -0
  7. package/dist/core-api/connected-accounts.js +128 -0
  8. package/dist/core-api/control-plane.d.ts +1318 -0
  9. package/dist/core-api/control-plane.js +31 -0
  10. package/dist/core-api/github.d.ts +261 -0
  11. package/dist/core-api/github.js +153 -0
  12. package/dist/core-api/inference.d.ts +223 -0
  13. package/dist/core-api/inference.js +139 -0
  14. package/dist/core-api/oci-names.d.ts +21 -0
  15. package/dist/core-api/oci-names.js +44 -0
  16. package/dist/core-api/packages.d.ts +179 -0
  17. package/dist/core-api/packages.js +60 -0
  18. package/dist/core-api/profiles.d.ts +188 -0
  19. package/dist/core-api/profiles.js +88 -0
  20. package/dist/core-api/record-events.d.ts +209 -0
  21. package/dist/core-api/record-events.js +193 -0
  22. package/dist/core-api/registries.d.ts +106 -0
  23. package/dist/core-api/registries.js +85 -0
  24. package/dist/core-api/runs.d.ts +516 -0
  25. package/dist/core-api/runs.js +284 -0
  26. package/dist/core-api/sessions.d.ts +390 -0
  27. package/dist/core-api/sessions.js +264 -0
  28. package/dist/core-api/ssh-keys.d.ts +120 -0
  29. package/dist/core-api/ssh-keys.js +108 -0
  30. package/dist/core-api/system.d.ts +51 -0
  31. package/dist/core-api/system.js +41 -0
  32. package/dist/core-api/users.d.ts +71 -0
  33. package/dist/core-api/users.js +48 -0
  34. package/dist/core-api/workspaces.d.ts +1880 -0
  35. package/dist/core-api/workspaces.js +980 -0
  36. package/dist/index.d.ts +18 -0
  37. package/dist/index.js +18 -0
  38. package/dist/workspace-environment.d.ts +124 -0
  39. package/dist/workspace-environment.js +259 -0
  40. package/package.json +31 -9
@@ -0,0 +1,18 @@
1
+ export * from "./core-api/connected-accounts.js";
2
+ export * from "./core-api/control-plane.js";
3
+ export * from "./core-api/github.js";
4
+ export * from "./core-api/inference.js";
5
+ export * from "./core-api/packages.js";
6
+ export * from "./core-api/profiles.js";
7
+ export * from "./core-api/record-events.js";
8
+ export * from "./core-api/budgets.js";
9
+ export * from "./core-api/oci-names.js";
10
+ export * from "./core-api/registries.js";
11
+ export * from "./core-api/runs.js";
12
+ export * from "./core-api/sessions.js";
13
+ export * from "./core-api/access-tokens.js";
14
+ export * from "./core-api/workspaces.js";
15
+ export * from "./core-api/ssh-keys.js";
16
+ export * from "./core-api/system.js";
17
+ export * from "./core-api/users.js";
18
+ export * from "./workspace-environment.js";
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export * from "./core-api/connected-accounts.js";
2
+ export * from "./core-api/control-plane.js";
3
+ export * from "./core-api/github.js";
4
+ export * from "./core-api/inference.js";
5
+ export * from "./core-api/packages.js";
6
+ export * from "./core-api/profiles.js";
7
+ export * from "./core-api/record-events.js";
8
+ export * from "./core-api/budgets.js";
9
+ export * from "./core-api/oci-names.js";
10
+ export * from "./core-api/registries.js";
11
+ export * from "./core-api/runs.js";
12
+ export * from "./core-api/sessions.js";
13
+ export * from "./core-api/access-tokens.js";
14
+ export * from "./core-api/workspaces.js";
15
+ export * from "./core-api/ssh-keys.js";
16
+ export * from "./core-api/system.js";
17
+ export * from "./core-api/users.js";
18
+ export * from "./workspace-environment.js";
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Workspace environment policy: the ONE definition of which caller-supplied environment variables
3
+ * a workspace may receive at creation, shared verbatim by the SDK (client-side rejection with the
4
+ * same messages the server would produce), the control-plane validators (`runtime.userEnv`), and
5
+ * any downstream product composing its own additions on top.
6
+ *
7
+ * Deliberately dependency-free (no schema library): consumers span an Effect-based contract
8
+ * package, zod-based validators, and browser bundles. Everything here is data plus pure functions.
9
+ *
10
+ * Two invariants shape the rules:
11
+ *
12
+ * 1. **Nothing that validates may silently vanish.** sealantd rebuilds the child environment for
13
+ * every managed process from a boot-time snapshot of the container environment, dropping names
14
+ * that match its secret markers (substring `TOKEN`/`SECRET`/`PASSWORD`/`PASSWD`/`CREDENTIAL`/
15
+ * `APIKEY`, suffix `_KEY`, exact `KEY`) and its consumed `SEALANT_*` keys. The reserved rules
16
+ * here are a strict superset of that filter, so an accepted name is guaranteed to reach the
17
+ * workspace's processes rather than being filtered on the way in.
18
+ * 2. **Diagnostics never carry values.** Accepted values are ordinary (non-secret) configuration
19
+ * by contract, but issue records still identify entries by name or index only — a name may
20
+ * appear in an issue only after it has passed the grammar check; otherwise a bounded escaped
21
+ * rendering is used.
22
+ */
23
+ export declare const WORKSPACE_ENV_NAME_PATTERN: RegExp;
24
+ /** Names are ASCII by grammar, so length in characters equals length in bytes. */
25
+ export declare const WORKSPACE_ENV_MAX_NAME_LENGTH = 128;
26
+ /** Maximum UTF-8 bytes per value. Values may be empty and may contain newlines, never NUL. */
27
+ export declare const WORKSPACE_ENV_MAX_VALUE_BYTES = 4096;
28
+ export declare const WORKSPACE_ENV_MAX_ENTRIES = 128;
29
+ /** Maximum total UTF-8 bytes across all names and values of one map. */
30
+ export declare const WORKSPACE_ENV_MAX_TOTAL_BYTES = 32768;
31
+ /**
32
+ * Why a name is reserved. `secret-marker` mirrors sealantd's filter exactly (see module doc);
33
+ * the rest prevent project-level settings from silently reconfiguring platform control,
34
+ * connected-account lookup, loaders, shells, or global tool runtimes. Legitimate uses of a
35
+ * blocked process-control variable belong in explicit command or workspace-image configuration
36
+ * where the effect is visible.
37
+ */
38
+ export type WorkspaceEnvReservedRule = "platform-prefix" | "process-identity" | "runtime-network" | "account-lookup" | "dynamic-loader" | "shell-startup" | "runtime-injection" | "git-ssh" | "secret-marker";
39
+ /** sealantd's secret markers, matched as substrings of the ASCII-uppercased name. */
40
+ export declare const WORKSPACE_ENV_SECRET_MARKERS: readonly ["TOKEN", "SECRET", "PASSWORD", "PASSWD", "CREDENTIAL", "APIKEY"];
41
+ /**
42
+ * The reserved rule a (grammar-valid) name violates, or undefined when the name is allowed.
43
+ * Matching is ASCII-uppercase so `http_proxy` cannot bypass `HTTP_PROXY`.
44
+ */
45
+ export declare const findWorkspaceEnvReservedRule: (name: string) => WorkspaceEnvReservedRule | undefined;
46
+ /**
47
+ * One rejected aspect of a workspace environment map. Name-bearing variants carry a name only when
48
+ * it passed the grammar check; grammar/length failures identify the entry by enumeration index and
49
+ * a bounded escaped rendering. No variant ever carries a value.
50
+ */
51
+ export type WorkspaceEnvIssue = {
52
+ readonly rule: "name-grammar";
53
+ readonly index: number;
54
+ readonly nameDisplay: string;
55
+ } | {
56
+ readonly rule: "name-length";
57
+ readonly index: number;
58
+ readonly nameDisplay: string;
59
+ } | {
60
+ readonly rule: "name-reserved";
61
+ readonly name: string;
62
+ readonly reservedRule: WorkspaceEnvReservedRule;
63
+ } | {
64
+ readonly rule: "value-nul";
65
+ readonly name: string;
66
+ } | {
67
+ readonly rule: "value-size";
68
+ readonly name: string;
69
+ readonly valueBytes: number;
70
+ } | {
71
+ readonly rule: "entry-count";
72
+ readonly entryCount: number;
73
+ } | {
74
+ readonly rule: "total-size";
75
+ readonly totalBytes: number;
76
+ };
77
+ export type WorkspaceEnvParseResult = {
78
+ readonly ok: true;
79
+ readonly env: Readonly<Record<string, string>>;
80
+ } | {
81
+ readonly ok: false;
82
+ readonly issues: ReadonlyArray<WorkspaceEnvIssue>;
83
+ };
84
+ /**
85
+ * Validate a caller-supplied environment map against the full policy. On success the returned map
86
+ * is re-serialized in name order (deterministic for tests and stored specs). Collects every issue
87
+ * rather than stopping at the first, so a settings UI can annotate all rows in one round trip.
88
+ * Duplicate names cannot be detected here — a parsed JS object has already collapsed them; owners
89
+ * of the pre-parse representation (databases, forms) must enforce uniqueness themselves.
90
+ */
91
+ export declare const parseWorkspaceEnv: (input: Readonly<Record<string, string>>) => WorkspaceEnvParseResult;
92
+ /**
93
+ * Validate a caller-supplied SECRET environment map — the transient secret channel
94
+ * (`CreateOptions.secretEnv`): same grammar and size bounds as `env`, and the same platform-owned
95
+ * name classes are reserved, but secret-shaped names are exactly what belongs here, so the
96
+ * `secret-marker` rule does not apply. Account-lookup names (`GITHUB_TOKEN`,
97
+ * `CLAUDE_CODE_OAUTH_TOKEN`, …) stay reserved: connected accounts own those.
98
+ */
99
+ export declare const parseWorkspaceSecretEnv: (input: Readonly<Record<string, string>>) => WorkspaceEnvParseResult;
100
+ /** Reserved-rule lookup for the secret lane: everything but `secret-marker`. */
101
+ export declare const findWorkspaceSecretEnvReservedRule: (name: string) => WorkspaceEnvReservedRule | undefined;
102
+ /** Human-readable, value-free rendering of one issue; identical client- and server-side. */
103
+ export declare const formatWorkspaceEnvIssue: (issue: WorkspaceEnvIssue) => string;
104
+ /**
105
+ * Secret names the CONTROL PLANE itself adds to the sealed secret channel beside the caller's map.
106
+ * They carry the `SEALANT_` prefix — platform-owned, so no caller lane can supply them — and are
107
+ * consumed by `sealantd boot` rather than handed to workspace processes. Today: the capture
108
+ * source's session credential (sealantd ADR-0015), which the create request carries as
109
+ * `captureToken` and the daemon reads from its boot secret file as `SEALANT_CAPTURE_TOKEN`.
110
+ */
111
+ export declare const CAPTURE_TOKEN_SECRET_ENV_NAME = "SEALANT_CAPTURE_TOKEN";
112
+ export declare const PLATFORM_SECRET_ENV_NAMES: ReadonlySet<string>;
113
+ export interface SplitSecretEnv {
114
+ /** The caller's entries: everything `parseWorkspaceSecretEnv` must re-validate. */
115
+ readonly callerEnv: Readonly<Record<string, string>>;
116
+ /** The platform-owned entries, matched by exact name; never subject to the caller policy. */
117
+ readonly platformEnv: Readonly<Record<string, string>>;
118
+ }
119
+ /**
120
+ * Separate the platform-owned entries of a sealed secret map from the caller's so the last-hop
121
+ * re-validation applies the caller policy to the caller's lane only. Any other `SEALANT_`-prefixed
122
+ * name stays in the caller lane and is rejected there, as before.
123
+ */
124
+ export declare const splitPlatformSecretEnv: (input: Readonly<Record<string, string>>) => SplitSecretEnv;
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Workspace environment policy: the ONE definition of which caller-supplied environment variables
3
+ * a workspace may receive at creation, shared verbatim by the SDK (client-side rejection with the
4
+ * same messages the server would produce), the control-plane validators (`runtime.userEnv`), and
5
+ * any downstream product composing its own additions on top.
6
+ *
7
+ * Deliberately dependency-free (no schema library): consumers span an Effect-based contract
8
+ * package, zod-based validators, and browser bundles. Everything here is data plus pure functions.
9
+ *
10
+ * Two invariants shape the rules:
11
+ *
12
+ * 1. **Nothing that validates may silently vanish.** sealantd rebuilds the child environment for
13
+ * every managed process from a boot-time snapshot of the container environment, dropping names
14
+ * that match its secret markers (substring `TOKEN`/`SECRET`/`PASSWORD`/`PASSWD`/`CREDENTIAL`/
15
+ * `APIKEY`, suffix `_KEY`, exact `KEY`) and its consumed `SEALANT_*` keys. The reserved rules
16
+ * here are a strict superset of that filter, so an accepted name is guaranteed to reach the
17
+ * workspace's processes rather than being filtered on the way in.
18
+ * 2. **Diagnostics never carry values.** Accepted values are ordinary (non-secret) configuration
19
+ * by contract, but issue records still identify entries by name or index only — a name may
20
+ * appear in an issue only after it has passed the grammar check; otherwise a bounded escaped
21
+ * rendering is used.
22
+ */
23
+ export const WORKSPACE_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
24
+ /** Names are ASCII by grammar, so length in characters equals length in bytes. */
25
+ export const WORKSPACE_ENV_MAX_NAME_LENGTH = 128;
26
+ /** Maximum UTF-8 bytes per value. Values may be empty and may contain newlines, never NUL. */
27
+ export const WORKSPACE_ENV_MAX_VALUE_BYTES = 4096;
28
+ export const WORKSPACE_ENV_MAX_ENTRIES = 128;
29
+ /** Maximum total UTF-8 bytes across all names and values of one map. */
30
+ export const WORKSPACE_ENV_MAX_TOTAL_BYTES = 32768;
31
+ const RESERVED_PREFIXES = [
32
+ ["SEALANT_", "platform-prefix"],
33
+ ["LD_", "dynamic-loader"],
34
+ ["DYLD_", "dynamic-loader"],
35
+ ["GIT_CONFIG_KEY_", "git-ssh"],
36
+ ["GIT_CONFIG_VALUE_", "git-ssh"],
37
+ ];
38
+ const RESERVED_NAMES = new Map([
39
+ // Identity/process roots the daemon and shells own.
40
+ ...[
41
+ "HOME",
42
+ "PATH",
43
+ "USER",
44
+ "LOGNAME",
45
+ "SHELL",
46
+ "PWD",
47
+ "OLDPWD",
48
+ "SHLVL",
49
+ "TERM",
50
+ "COLORTERM",
51
+ "_",
52
+ ].map((name) => [name, "process-identity"]),
53
+ // Runtime/network ownership (lowercase variants are caught by the uppercase comparison).
54
+ ...[
55
+ "DOCKER_HOST",
56
+ "DOCKER_TLS_CERTDIR",
57
+ "HTTP_PROXY",
58
+ "HTTPS_PROXY",
59
+ "ALL_PROXY",
60
+ "NO_PROXY",
61
+ ].map((name) => [name, "runtime-network"]),
62
+ // Connected-account identity and config-lookup roots.
63
+ ...[
64
+ "CLAUDE_CODE_OAUTH_TOKEN",
65
+ "GITHUB_TOKEN",
66
+ "GH_TOKEN",
67
+ "XDG_CONFIG_HOME",
68
+ "CLAUDE_CONFIG_DIR",
69
+ "CODEX_HOME",
70
+ ].map((name) => [name, "account-lookup"]),
71
+ ["GLIBC_TUNABLES", "dynamic-loader"],
72
+ // Shell startup/control.
73
+ ...["BASH_ENV", "ENV", "ZDOTDIR", "PROMPT_COMMAND", "PS4", "SHELLOPTS"].map((name) => [name, "shell-startup"]),
74
+ // Runtime-wide code injection.
75
+ ...[
76
+ "NODE_OPTIONS",
77
+ "PYTHONPATH",
78
+ "PYTHONSTARTUP",
79
+ "RUBYOPT",
80
+ "PERL5OPT",
81
+ "JAVA_TOOL_OPTIONS",
82
+ ].map((name) => [name, "runtime-injection"]),
83
+ // Git/SSH identity and config controls.
84
+ ...[
85
+ "SSH_AUTH_SOCK",
86
+ "GIT_SSH",
87
+ "GIT_SSH_COMMAND",
88
+ "GIT_CONFIG_GLOBAL",
89
+ "GIT_CONFIG_SYSTEM",
90
+ "GIT_CONFIG_NOSYSTEM",
91
+ "GIT_CONFIG_COUNT",
92
+ ].map((name) => [name, "git-ssh"]),
93
+ ]);
94
+ /** sealantd's secret markers, matched as substrings of the ASCII-uppercased name. */
95
+ export const WORKSPACE_ENV_SECRET_MARKERS = [
96
+ "TOKEN",
97
+ "SECRET",
98
+ "PASSWORD",
99
+ "PASSWD",
100
+ "CREDENTIAL",
101
+ "APIKEY",
102
+ ];
103
+ const toAsciiUpper = (name) => name.replace(/[a-z]/g, (char) => String.fromCharCode(char.charCodeAt(0) - 32));
104
+ /**
105
+ * The reserved rule a (grammar-valid) name violates, or undefined when the name is allowed.
106
+ * Matching is ASCII-uppercase so `http_proxy` cannot bypass `HTTP_PROXY`.
107
+ */
108
+ export const findWorkspaceEnvReservedRule = (name) => {
109
+ const upper = toAsciiUpper(name);
110
+ for (const [prefix, rule] of RESERVED_PREFIXES) {
111
+ if (upper.startsWith(prefix)) {
112
+ return rule;
113
+ }
114
+ }
115
+ const exact = RESERVED_NAMES.get(upper);
116
+ if (exact !== undefined) {
117
+ return exact;
118
+ }
119
+ // Mirror of sealantd's is_secret_key: substring markers, `_KEY` suffix, bare `KEY`. Broader than
120
+ // it looks on purpose — `TOKENIZER_PATH` matches `TOKEN` — because the daemon's filter would
121
+ // silently drop those names, and a loud rejection here beats a variable that never appears.
122
+ if (WORKSPACE_ENV_SECRET_MARKERS.some((marker) => upper.includes(marker))) {
123
+ return "secret-marker";
124
+ }
125
+ if (upper === "KEY" || upper.endsWith("_KEY")) {
126
+ return "secret-marker";
127
+ }
128
+ return undefined;
129
+ };
130
+ const boundedNameDisplay = (name) => {
131
+ const head = [...name].slice(0, 32).join("");
132
+ return JSON.stringify(head.length < name.length ? `${head}…` : head);
133
+ };
134
+ const utf8Bytes = (value) => new TextEncoder().encode(value).length;
135
+ /**
136
+ * Validate a caller-supplied environment map against the full policy. On success the returned map
137
+ * is re-serialized in name order (deterministic for tests and stored specs). Collects every issue
138
+ * rather than stopping at the first, so a settings UI can annotate all rows in one round trip.
139
+ * Duplicate names cannot be detected here — a parsed JS object has already collapsed them; owners
140
+ * of the pre-parse representation (databases, forms) must enforce uniqueness themselves.
141
+ */
142
+ export const parseWorkspaceEnv = (input) => parseEnvMap(input, "env");
143
+ /**
144
+ * Validate a caller-supplied SECRET environment map — the transient secret channel
145
+ * (`CreateOptions.secretEnv`): same grammar and size bounds as `env`, and the same platform-owned
146
+ * name classes are reserved, but secret-shaped names are exactly what belongs here, so the
147
+ * `secret-marker` rule does not apply. Account-lookup names (`GITHUB_TOKEN`,
148
+ * `CLAUDE_CODE_OAUTH_TOKEN`, …) stay reserved: connected accounts own those.
149
+ */
150
+ export const parseWorkspaceSecretEnv = (input) => parseEnvMap(input, "secretEnv");
151
+ /** Reserved-rule lookup for the secret lane: everything but `secret-marker`. */
152
+ export const findWorkspaceSecretEnvReservedRule = (name) => {
153
+ const rule = findWorkspaceEnvReservedRule(name);
154
+ return rule === "secret-marker" ? undefined : rule;
155
+ };
156
+ const parseEnvMap = (input, lane) => {
157
+ const findReserved = lane === "env" ? findWorkspaceEnvReservedRule : findWorkspaceSecretEnvReservedRule;
158
+ const issues = [];
159
+ const entries = Object.entries(input);
160
+ if (entries.length > WORKSPACE_ENV_MAX_ENTRIES) {
161
+ issues.push({ rule: "entry-count", entryCount: entries.length });
162
+ }
163
+ let totalBytes = 0;
164
+ for (const [index, [name, value]] of entries.entries()) {
165
+ if (!WORKSPACE_ENV_NAME_PATTERN.test(name)) {
166
+ issues.push({ rule: "name-grammar", index, nameDisplay: boundedNameDisplay(name) });
167
+ continue;
168
+ }
169
+ if (name.length > WORKSPACE_ENV_MAX_NAME_LENGTH) {
170
+ issues.push({ rule: "name-length", index, nameDisplay: boundedNameDisplay(name) });
171
+ continue;
172
+ }
173
+ const reservedRule = findReserved(name);
174
+ if (reservedRule !== undefined) {
175
+ issues.push({ rule: "name-reserved", name, reservedRule });
176
+ continue;
177
+ }
178
+ if (value.includes("\u0000")) {
179
+ issues.push({ rule: "value-nul", name });
180
+ continue;
181
+ }
182
+ const valueBytes = utf8Bytes(value);
183
+ if (valueBytes > WORKSPACE_ENV_MAX_VALUE_BYTES) {
184
+ issues.push({ rule: "value-size", name, valueBytes });
185
+ continue;
186
+ }
187
+ totalBytes += name.length + valueBytes;
188
+ }
189
+ if (totalBytes > WORKSPACE_ENV_MAX_TOTAL_BYTES) {
190
+ issues.push({ rule: "total-size", totalBytes });
191
+ }
192
+ if (issues.length > 0) {
193
+ return { ok: false, issues };
194
+ }
195
+ const env = {};
196
+ for (const name of Object.keys(input).toSorted()) {
197
+ env[name] = input[name] ?? "";
198
+ }
199
+ return { ok: true, env };
200
+ };
201
+ const RESERVED_RULE_MESSAGES = {
202
+ "platform-prefix": "the SEALANT_ prefix is platform-owned",
203
+ "process-identity": "it is a process-identity variable owned by the workspace runtime",
204
+ "runtime-network": "it controls runtime or network routing",
205
+ "account-lookup": "it controls connected-account identity or configuration lookup",
206
+ "dynamic-loader": "it controls the dynamic loader",
207
+ "shell-startup": "it controls shell startup",
208
+ "runtime-injection": "it injects code into a language runtime",
209
+ "git-ssh": "it controls Git or SSH identity and configuration",
210
+ "secret-marker": "secret-looking names (containing TOKEN, SECRET, PASSWORD, PASSWD, CREDENTIAL, or APIKEY, ending in _KEY, or exactly KEY) are filtered by the workspace runtime and would never reach any process; environment variables are for ordinary non-secret configuration",
211
+ };
212
+ /** Human-readable, value-free rendering of one issue; identical client- and server-side. */
213
+ export const formatWorkspaceEnvIssue = (issue) => {
214
+ switch (issue.rule) {
215
+ case "name-grammar":
216
+ return `env entry ${issue.index} (${issue.nameDisplay}): names must match [A-Za-z_][A-Za-z0-9_]*`;
217
+ case "name-length":
218
+ return `env entry ${issue.index} (${issue.nameDisplay}): names must be at most ${WORKSPACE_ENV_MAX_NAME_LENGTH} characters`;
219
+ case "name-reserved":
220
+ return `env name ${issue.name} is reserved: ${RESERVED_RULE_MESSAGES[issue.reservedRule]}`;
221
+ case "value-nul":
222
+ return `env value for ${issue.name} must not contain NUL`;
223
+ case "value-size":
224
+ return `env value for ${issue.name} is ${issue.valueBytes} bytes; the maximum is ${WORKSPACE_ENV_MAX_VALUE_BYTES}`;
225
+ case "entry-count":
226
+ return `env has ${issue.entryCount} entries; the maximum is ${WORKSPACE_ENV_MAX_ENTRIES}`;
227
+ case "total-size":
228
+ return `env totals ${issue.totalBytes} bytes across names and values; the maximum is ${WORKSPACE_ENV_MAX_TOTAL_BYTES}`;
229
+ }
230
+ };
231
+ /**
232
+ * Secret names the CONTROL PLANE itself adds to the sealed secret channel beside the caller's map.
233
+ * They carry the `SEALANT_` prefix — platform-owned, so no caller lane can supply them — and are
234
+ * consumed by `sealantd boot` rather than handed to workspace processes. Today: the capture
235
+ * source's session credential (sealantd ADR-0015), which the create request carries as
236
+ * `captureToken` and the daemon reads from its boot secret file as `SEALANT_CAPTURE_TOKEN`.
237
+ */
238
+ export const CAPTURE_TOKEN_SECRET_ENV_NAME = "SEALANT_CAPTURE_TOKEN";
239
+ export const PLATFORM_SECRET_ENV_NAMES = new Set([
240
+ CAPTURE_TOKEN_SECRET_ENV_NAME,
241
+ ]);
242
+ /**
243
+ * Separate the platform-owned entries of a sealed secret map from the caller's so the last-hop
244
+ * re-validation applies the caller policy to the caller's lane only. Any other `SEALANT_`-prefixed
245
+ * name stays in the caller lane and is rejected there, as before.
246
+ */
247
+ export const splitPlatformSecretEnv = (input) => {
248
+ const callerEnv = {};
249
+ const platformEnv = {};
250
+ for (const [name, value] of Object.entries(input)) {
251
+ if (PLATFORM_SECRET_ENV_NAMES.has(name)) {
252
+ platformEnv[name] = value;
253
+ }
254
+ else {
255
+ callerEnv[name] = value;
256
+ }
257
+ }
258
+ return { callerEnv, platformEnv };
259
+ };
package/package.json CHANGED
@@ -1,13 +1,35 @@
1
1
  {
2
2
  "name": "@sealant/api-contracts-next",
3
- "version": "0.0.0-next.0",
4
- "description": "Prereleases of @sealant/api-contracts from main, for exact pins and @next only. Install @sealant/api-contracts instead.",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "keywords": [],
10
- "author": "",
3
+ "version": "0.39.0-next.682",
4
+ "description": "Contract-first HTTP API definitions for Sealant services",
11
5
  "license": "Apache-2.0",
12
- "type": "commonjs"
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/sealant-sh/sealant.git",
9
+ "directory": "packages/api-contracts"
10
+ },
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./workspace-environment": {
18
+ "types": "./dist/workspace-environment.d.ts",
19
+ "import": "./dist/workspace-environment.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "peerDependencies": {
29
+ "effect": ">=4.0.0-beta.85 <5"
30
+ },
31
+ "gitHead": "ef9e5377ecec37e1257a7a4d5048e79c5322a500",
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
13
35
  }