@sema-agent/settings-schema 17.2.0 → 19.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +216 -0
- package/README.md +53 -6
- package/conformance/VERSION +1 -1
- package/conformance/baseline.json +3 -0
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +4 -1
- package/conformance/cases/c02-hosts-clamp-stacked/expected.json +4 -1
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +4 -1
- package/conformance/cases/c04-all-bad-collection-not-salvaged/expected.json +4 -1
- package/conformance/cases/c07-hooks-is-not-a-config-domain/expected.json +4 -1
- package/conformance/cases/c08-limits-unknown-carried-64/expected.json +4 -1
- package/conformance/cases/c09-limits-unknown-carried-65/expected.json +4 -1
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +4 -1
- package/conformance/cases/c11-unicode-unknown-keys/expected.json +4 -1
- package/conformance/cases/c12-prototype-name-top-keys/expected.json +4 -1
- package/conformance/cases/c13-models-field-null/expected.json +4 -1
- package/conformance/cases/c14-models-field-empty-array/expected.json +4 -1
- package/conformance/cases/c15-models-field-number/expected.json +4 -1
- package/conformance/cases/c16-deep-nesting-205/expected.json +3 -0
- package/conformance/cases/c17-empty-domain-file/expected.json +3 -0
- package/conformance/cases/c18-wire-envelope-not-an-object/expected.json +4 -1
- package/conformance/cases/c19-wire-envelope-no-version/expected.json +4 -1
- package/conformance/cases/c21-governance-empty-object-wins/expected.json +4 -1
- package/conformance/cases/c23-mixed-validity-models/expected.json +4 -1
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +4 -1
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +4 -1
- package/conformance/cases/c26-hosts-array-1024/expected.json +4 -1
- package/conformance/cases/c27-hosts-array-1025/expected.json +4 -1
- package/conformance/cases/c28-large-document-over-1mb/expected.json +4 -1
- package/conformance/cases/c29-salvage-then-domain-refine-fails/expected.json +4 -1
- package/conformance/cases/c30-version-triple-a-first/expected.json +4 -1
- package/conformance/cases/c31-version-triple-b-changed/expected.json +4 -1
- package/conformance/cases/c32-version-triple-a-restored/expected.json +4 -1
- package/conformance/cases/c33-limits-nested-carried-key/expected.json +4 -1
- package/conformance/cases/c34-skills-content-vs-manifest/expected.json +7 -1
- package/conformance/cases/c35-portable-domain-file-is-null/expected.json +3 -0
- package/conformance/cases/c36-skills-entry-without-content/expected.json +7 -1
- package/conformance/cases/c37-skills-field-null/expected.json +6 -0
- package/conformance/cases/c38-remote-exec-is-a-non-domain-file/expected.json +4 -1
- package/conformance/cases/c39-hidden-file-in-config-d/expected.json +4 -1
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +4 -1
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +4 -1
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +4 -1
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +4 -1
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +4 -1
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +4 -1
- package/conformance/cases/c46-s811-host-name-url-userinfo-withheld/expected.json +4 -1
- package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/expected.json +4 -1
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +4 -1
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +4 -1
- package/conformance/cases/c50-s784-lane-host-legal/expected.json +4 -1
- package/conformance/cases/c51-s784-lane-e2b-legal/expected.json +4 -1
- package/conformance/cases/c52-s784-lane-k8s-legal/expected.json +4 -1
- package/conformance/cases/c53-s784-lane-ssh-legal/expected.json +4 -1
- package/conformance/cases/c54-s784-lane-adb-legal/expected.json +4 -1
- package/conformance/cases/c55-s784-lane-local-docker-legal/expected.json +4 -1
- package/conformance/cases/c56-s784-lane-device-legal/expected.json +4 -1
- package/conformance/cases/c57-s784-g1-enabled-no-lane/expected.json +4 -1
- package/conformance/cases/c58-s784-g2-multitenant-host/expected.json +4 -1
- package/conformance/cases/c59-s784-g3-k8s-missing-image/expected.json +4 -1
- package/conformance/cases/c60-s784-g3-e2b-missing-key/expected.json +4 -1
- package/conformance/cases/c61-s784-g3-other-lane-key/expected.json +4 -1
- package/conformance/cases/c62-s784-g7a-session-tidb/expected.json +4 -1
- package/conformance/cases/c63-s784-host-two-replicas/expected.json +4 -1
- package/conformance/cases/c64-s784-multitenant-local-store/expected.json +4 -1
- package/conformance/cases/c65-s784-pg-without-db/expected.json +4 -1
- package/conformance/cases/c66-s784-memory-backend-mismatch/expected.json +4 -1
- package/conformance/cases/c67-s784-retired-field-loud/expected.json +4 -1
- package/conformance/cases/c68-s784-device-local-store/expected.json +4 -1
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +4 -1
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +4 -1
- package/conformance/cases/c71-one-scanner-write-reads-display-forms/expected.json +4 -1
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +4 -1
- package/conformance/cases/c73-projects-extended-registration-reads/expected.json +4 -1
- package/conformance/cases/c74-projects-over-bound-refused/expected.json +4 -1
- package/conformance/cases/c75-projects-patch-remote-uniqueness/expected.json +4 -1
- package/conformance/cases/c76-projects-entry-salvage-nested/expected.json +4 -1
- package/conformance/cases/c77-prompts-center-artifact-carried/expected.json +4 -1
- package/conformance/cases/c78-prompts-empty-bindings-refused/expected.json +4 -1
- package/conformance/cases/c79-prompts-bad-worker-artifact-not-salvaged/expected.json +4 -1
- package/conformance/cases/c80-prompts-wire-v2-verbatim/expected.json +7 -1
- package/conformance/cases/c81-prompts-declaration-bindings-refused/expected.json +4 -1
- package/conformance/cases/c82-prompts-v1-catalog-refused/expected.json +4 -1
- package/conformance/cases/c83-prompts-bad-overlay-not-salvaged/expected.json +4 -1
- package/conformance/cases/c84-sandbox-git-block-carried-verbatim/expected.json +7 -1
- package/conformance/cases/c85-sandbox-git-refused-block-still-carried/expected.json +7 -1
- package/conformance/cases/c86-a2a-peers-legal/expected.json +135 -0
- package/conformance/cases/c86-a2a-peers-legal/input.json +32 -0
- package/conformance/cases/c86-a2a-peers-legal/meta.json +5 -0
- package/conformance/cases/c87-a2a-credential-refs-not-partially-salvaged/expected.json +123 -0
- package/conformance/cases/c87-a2a-credential-refs-not-partially-salvaged/input.json +39 -0
- package/conformance/cases/c87-a2a-credential-refs-not-partially-salvaged/meta.json +5 -0
- package/conformance/cases/cc01-mcp-toolfaces-non-object/expected.json +4 -1
- package/conformance/cases/cc02-mcp-source-key-is-transported/expected.json +4 -1
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +4 -1
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +4 -1
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +4 -1
- package/conformance/cases/cli03-credential-pasted-into-unknown-key/expected.json +4 -1
- package/conformance/cases/cli04-env-value-with-newline/expected.json +4 -1
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +4 -1
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +4 -1
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +4 -1
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +4 -1
- package/conformance/cases/z01-every-domain-empty-object/expected.json +4 -1
- package/conformance/cases/z02-every-domain-unknown-key/expected.json +4 -1
- package/dist/center-refs.d.ts +4 -3
- package/dist/center-refs.js +8 -6
- package/dist/cli.d.ts +1 -1
- package/dist/config-fns.d.ts +6 -1
- package/dist/config-fns.js +16 -2
- package/dist/execution-lanes.d.ts +158 -11
- package/dist/execution-lanes.js +76 -11
- package/dist/file-edit.d.ts +1 -1
- package/dist/file-edit.js +2 -1
- package/dist/hooks-config.d.ts +23 -5
- package/dist/hooks-config.js +33 -5
- package/dist/hooks.d.ts +6 -4
- package/dist/hooks.js +3 -1
- package/dist/mcp-request-entry.d.ts +11 -21
- package/dist/mcp-request-entry.js +21 -38
- package/dist/one-time-migrations.js +1 -1
- package/dist/portable-domains.d.ts +1 -1
- package/dist/portable-domains.js +1 -1
- package/dist/remote-exec.d.ts +5 -0
- package/dist/remote-exec.js +5 -0
- package/dist/reserved-member-name.d.ts +13 -0
- package/dist/reserved-member-name.js +14 -0
- package/dist/retired-envs.js +2 -0
- package/dist/secret-refs.d.ts +1 -1
- package/dist/secret-refs.js +5 -1
- package/dist/types.d.ts +70 -41
- package/dist/types.js +179 -32
- package/dist/url-credential.d.ts +12 -2
- package/dist/url-credential.js +164 -9
- package/dist/wire-name.d.ts +19 -0
- package/dist/wire-name.js +21 -0
- package/docs/API-SURFACE.md +27 -6
- package/docs/CC-DIVERGENCE.md +2 -2
- package/docs/CONSUMER-INTEGRATION.md +72 -7
- package/llms-full.txt +38 -18
- package/llms.txt +10 -10
- package/package.json +1 -1
package/dist/hooks-config.js
CHANGED
|
@@ -99,14 +99,26 @@ export const HOOK_EVENTS = [...CC_198_HOOK_EVENTS, ...POST_198_HOOK_EVENTS];
|
|
|
99
99
|
const HOOK_GUARD_EVENTS = ["PreToolUse", "PermissionRequest"];
|
|
100
100
|
// ── entry vocabulary and the one field table ───────────────────────────────────────────────────────────────
|
|
101
101
|
/**
|
|
102
|
-
* The hook entry `type` closed set — CC 2.1.296's five (`cli296.js:237454`): `command` (
|
|
102
|
+
* The hook entry `type` closed set — CC 2.1.296's five (`cli296.js:237454`): `command` (a command on the host that runs the hook: a shell command line, or — with `args` — an executable spawned directly, no shell),
|
|
103
103
|
* `prompt` (one-shot LLM evaluation of the hook input), `agent` (agentic verifier loop), `http` (POST the hook input to a URL),
|
|
104
104
|
* `mcp_tool` (call a tool on an already-configured MCP server). An entry of another type is a bad piece — and in a guard event that rejects
|
|
105
105
|
* the whole document, which is why `test/upstream-drift.test.ts` ①b holds this set against the CC anchor snapshot.
|
|
106
106
|
*/
|
|
107
107
|
export const HOOK_ENTRY_TYPES = ["command", "prompt", "agent", "http", "mcp_tool"];
|
|
108
|
+
/**
|
|
109
|
+
* The `onFailure` closed set (CC `cli309.js:252382`, `onFailure:At()`): what a FAILURE of the hook does — it could not start (a missing script), timed out, exited with a code other
|
|
110
|
+
* than 0 or 2, or printed JSON that is invalid or fails validation. `continue` (CC's default when the key is absent; this package does not fill it in — an absent key stays absent): the
|
|
111
|
+
* failure is reported and the action goes ahead. `block`: the failure counts as exit code 2, so the action the event guards is blocked. Carried by `command` and `http` entries only.
|
|
112
|
+
* CARRIED, not executed here: a consumer that reads `block` executes it or refuses it — ignoring it makes a guard that cannot start let the action through.
|
|
113
|
+
* The classifier reads this set through a closure-private projection that the compiler ties to it in both directions.
|
|
114
|
+
*/
|
|
115
|
+
export const HOOK_ON_FAILURE_WORDS = ["continue", "block"];
|
|
108
116
|
/** `shell` values: 'bash' = the user's $SHELL family; 'powershell' = pwsh. Default bash. */
|
|
109
117
|
const HOOK_SHELLS = ["bash", "powershell"];
|
|
118
|
+
/** The classifier's reading of {@link HOOK_ON_FAILURE_WORDS}. The classifier is copied as source by consumers' generators, so its closure cannot read an exported name (its one free name
|
|
119
|
+
* is {@link HOOK_EVENTS}): this record is the closure-private projection of the exported set, and the compiler ties the two in both directions — a word added to the set without a row
|
|
120
|
+
* here, or a row here that the set does not hold, is a compile error. */
|
|
121
|
+
const HOOK_ON_FAILURE_ACCEPTED = { continue: true, block: true };
|
|
110
122
|
/** Fields every entry type takes: `if` = permission-rule syntax (e.g. "Bash(git *)") evaluated against tool_name/tool_input before the hook
|
|
111
123
|
* runs (tool events only; shell superset field, CC-compatible); `timeout` = seconds for this entry (default `DEFAULT_HOOK_TIMEOUT_SECONDS`);
|
|
112
124
|
* `statusMessage` = spinner status message; `once` = run once, then remove. */
|
|
@@ -114,18 +126,23 @@ const HOOK_ENTRY_COMMON_FIELDS = { if: "string?", timeout: "seconds?", statusMes
|
|
|
114
126
|
/**
|
|
115
127
|
* THE field table of a hook entry, per type — read by {@link classifyHooksConfig} (which field is bad, what is carried) and by the entry
|
|
116
128
|
* types below (derived from it). Field order = the order a loaded entry's keys come out in.
|
|
117
|
-
* · command: `
|
|
129
|
+
* · command: `args` = exec form (CC `cli309.js:252382`: when present — `[]` included — `command` is an executable spawned directly with these arguments, no shell; the mere
|
|
130
|
+
* presence of `args` is the switch), `shell` (see above), `async` = run in background without blocking, `asyncRewake` = background + wake the model on exit 2,
|
|
131
|
+
* `onFailure` (see {@link HOOK_ON_FAILURE_WORDS});
|
|
118
132
|
* · prompt / agent: `prompt` ($ARGUMENTS = the hook input JSON), `model`;
|
|
119
133
|
* · http: `url`, `headers` (values may reference $NAMEs listed in `allowedEnvVars` — env-NAME interpolation only, the config never carries a
|
|
120
|
-
* secret value; `interpolateHookEnvVars` is the one reading), `allowedEnvVars
|
|
134
|
+
* secret value; `interpolateHookEnvVars` is the one reading), `allowedEnvVars`, `onFailure` (the other schema CC gives it to);
|
|
121
135
|
* · mcp_tool: `server` (an already-configured MCP server's name), `tool`, `input` (the tool's arguments; string values may interpolate
|
|
122
136
|
* `${path}` from the hook input JSON — CC 2.1.296 semantics, the runner's business).
|
|
137
|
+
* NOT in the table, on purpose: CC's `@internal` command / http keys `rewakeMessage` / `rewakeSummary` / `cloud` (not public settings keys; `cloud` even reads a bad value
|
|
138
|
+
* as 'skip' and loads the file) and the prompt entry's `continueOnBlock`. A key outside a type's row is not carried and not a bad piece (`docs/CC-DIVERGENCE.md` H7) —
|
|
139
|
+
* which is also why `args` on a non-command entry and `onFailure` on a prompt / agent / mcp_tool entry are such keys.
|
|
123
140
|
*/
|
|
124
141
|
const HOOK_ENTRY_FIELDS = {
|
|
125
|
-
command: { command: "text", shell: "shell?", async: "boolean?", asyncRewake: "boolean?", ...HOOK_ENTRY_COMMON_FIELDS },
|
|
142
|
+
command: { command: "text", args: "argv?", shell: "shell?", async: "boolean?", asyncRewake: "boolean?", onFailure: "onFailure?", ...HOOK_ENTRY_COMMON_FIELDS },
|
|
126
143
|
prompt: { prompt: "text", model: "string?", ...HOOK_ENTRY_COMMON_FIELDS },
|
|
127
144
|
agent: { prompt: "text", model: "string?", ...HOOK_ENTRY_COMMON_FIELDS },
|
|
128
|
-
http: { url: "text", headers: "stringRecord?", allowedEnvVars: "stringList?", ...HOOK_ENTRY_COMMON_FIELDS },
|
|
145
|
+
http: { url: "text", headers: "stringRecord?", allowedEnvVars: "stringList?", onFailure: "onFailure?", ...HOOK_ENTRY_COMMON_FIELDS },
|
|
129
146
|
mcp_tool: { server: "string", tool: "string", input: "record?", ...HOOK_ENTRY_COMMON_FIELDS },
|
|
130
147
|
};
|
|
131
148
|
// ── classification vocabulary ──────────────────────────────────────────────────────────────────────────────
|
|
@@ -338,6 +355,17 @@ function hookFieldFits(base, value) {
|
|
|
338
355
|
if (typeof value[i] !== "string")
|
|
339
356
|
return false;
|
|
340
357
|
return true;
|
|
358
|
+
case "argv":
|
|
359
|
+
if (!hookIsArray(value))
|
|
360
|
+
return false;
|
|
361
|
+
for (let i = 0; i < value.length; i++) {
|
|
362
|
+
const arg = value[i];
|
|
363
|
+
if (typeof arg !== "string" || arg.includes("\0"))
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
case "onFailure":
|
|
368
|
+
return typeof value === "string" && Object.hasOwn(HOOK_ON_FAILURE_ACCEPTED, value);
|
|
341
369
|
case "record":
|
|
342
370
|
return hookIsRecord(value);
|
|
343
371
|
default: {
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1442,8 +1442,8 @@ export declare const HookInput: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1442
1442
|
export type HookInput = z.infer<typeof HookInput>;
|
|
1443
1443
|
/** Legacy top-level decision (CC): "approve" ≈ allow, "block" feeds `reason` back to the model. */
|
|
1444
1444
|
export declare const HookDecision: z.ZodEnum<{
|
|
1445
|
-
approve: "approve";
|
|
1446
1445
|
block: "block";
|
|
1446
|
+
approve: "approve";
|
|
1447
1447
|
}>;
|
|
1448
1448
|
export type HookDecision = z.infer<typeof HookDecision>;
|
|
1449
1449
|
/** PermissionRequest hook decision — mirrors CC PermissionResult (allow may rewrite input/permissions). */
|
|
@@ -1728,19 +1728,21 @@ export type HookSpecificOutput = z.infer<typeof HookSpecificOutput>;
|
|
|
1728
1728
|
* Synchronous hook JSON output (stdout of a command hook, CC-verbatim). Exit-code semantics ride
|
|
1729
1729
|
* alongside: exit 0 = success (stdout parsed as this shape when JSON); exit 2 = BLOCKING error (stderr is fed
|
|
1730
1730
|
* back to the model as the block reason / additionalContext, per event); any other exit, a timeout, a spawn
|
|
1731
|
-
* failure or malformed JSON = NON-BLOCKING failure: the run continues, and the consumer's hook-runner forwards
|
|
1731
|
+
* failure or malformed JSON = NON-BLOCKING failure by default (the entry's `onFailure` is absent or `continue`): the run continues, and the consumer's hook-runner forwards
|
|
1732
1732
|
* it as a `hook_non_blocking_failure` notice (a `hook_notice` frame on the task/fleet stream carrying hook name,
|
|
1733
1733
|
* event, reason, exit code and the stderr text redacted-then-bounded to 512 chars), so the failure is visible
|
|
1734
1734
|
* from the task's point of view instead of only in server logs. There is no separate "shown to the user"
|
|
1735
1735
|
* surface: what a user sees is whatever their client renders from that notice frame.
|
|
1736
|
+
* An entry that carries `onFailure: "block"` (a contract field of `command` and `http` entries, see `./hooks-config`) counts exactly those failures as exit 2 instead;
|
|
1737
|
+
* executing that is the consumer's business, and a consumer that ignores it lets through a guarded action whose guard could not run.
|
|
1736
1738
|
*/
|
|
1737
1739
|
export declare const SyncHookOutput: z.ZodObject<{
|
|
1738
1740
|
continue: z.ZodOptional<z.ZodBoolean>;
|
|
1739
1741
|
suppressOutput: z.ZodOptional<z.ZodBoolean>;
|
|
1740
1742
|
stopReason: z.ZodOptional<z.ZodString>;
|
|
1741
1743
|
decision: z.ZodOptional<z.ZodEnum<{
|
|
1742
|
-
approve: "approve";
|
|
1743
1744
|
block: "block";
|
|
1745
|
+
approve: "approve";
|
|
1744
1746
|
}>>;
|
|
1745
1747
|
reason: z.ZodOptional<z.ZodString>;
|
|
1746
1748
|
systemMessage: z.ZodOptional<z.ZodString>;
|
|
@@ -1935,8 +1937,8 @@ export declare const HookOutput: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
1935
1937
|
suppressOutput: z.ZodOptional<z.ZodBoolean>;
|
|
1936
1938
|
stopReason: z.ZodOptional<z.ZodString>;
|
|
1937
1939
|
decision: z.ZodOptional<z.ZodEnum<{
|
|
1938
|
-
approve: "approve";
|
|
1939
1940
|
block: "block";
|
|
1941
|
+
approve: "approve";
|
|
1940
1942
|
}>>;
|
|
1941
1943
|
reason: z.ZodOptional<z.ZodString>;
|
|
1942
1944
|
systemMessage: z.ZodOptional<z.ZodString>;
|
package/dist/hooks.js
CHANGED
|
@@ -546,11 +546,13 @@ export const HookSpecificOutput = z.union([
|
|
|
546
546
|
* Synchronous hook JSON output (stdout of a command hook, CC-verbatim). Exit-code semantics ride
|
|
547
547
|
* alongside: exit 0 = success (stdout parsed as this shape when JSON); exit 2 = BLOCKING error (stderr is fed
|
|
548
548
|
* back to the model as the block reason / additionalContext, per event); any other exit, a timeout, a spawn
|
|
549
|
-
* failure or malformed JSON = NON-BLOCKING failure: the run continues, and the consumer's hook-runner forwards
|
|
549
|
+
* failure or malformed JSON = NON-BLOCKING failure by default (the entry's `onFailure` is absent or `continue`): the run continues, and the consumer's hook-runner forwards
|
|
550
550
|
* it as a `hook_non_blocking_failure` notice (a `hook_notice` frame on the task/fleet stream carrying hook name,
|
|
551
551
|
* event, reason, exit code and the stderr text redacted-then-bounded to 512 chars), so the failure is visible
|
|
552
552
|
* from the task's point of view instead of only in server logs. There is no separate "shown to the user"
|
|
553
553
|
* surface: what a user sees is whatever their client renders from that notice frame.
|
|
554
|
+
* An entry that carries `onFailure: "block"` (a contract field of `command` and `http` entries, see `./hooks-config`) counts exactly those failures as exit 2 instead;
|
|
555
|
+
* executing that is the consumer's business, and a consumer that ignores it lets through a guarded action whose guard could not run.
|
|
554
556
|
*/
|
|
555
557
|
export const SyncHookOutput = z.object({
|
|
556
558
|
/** Whether the run should continue after the hook (default true). */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type McpContentOrigin } from "./types.js";
|
|
2
|
+
import { type ReservedMemberName } from "./reserved-member-name.js";
|
|
2
3
|
/** 值的**种类**(不回显值本身)。总(不抛):形状只问座位,读不出的对象说全包唯一的「检视不了」那一句。 */
|
|
3
4
|
export declare function describeValueShape(v: unknown): string;
|
|
4
5
|
/** 「不是字符串数组」的种类:数组里混了非串元素说那一句,否则回落到 {@link describeValueShape}。总(不抛):`isJsonArray` 是座位的总版。 */
|
|
@@ -12,19 +13,13 @@ export declare function describeNotStringRecord(v: unknown): string;
|
|
|
12
13
|
*/
|
|
13
14
|
export declare function isStringArray(v: unknown): v is string[];
|
|
14
15
|
/**
|
|
15
|
-
* 纯数据的「名 → 串」记录(空对象合法;数组、`Map
|
|
16
|
+
* 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、有保留名成员({@link isReservedMemberName})、有非串成员的对象都不是)—— 类型守卫,所以是**严归类**:成员经内核的
|
|
16
17
|
* `ownEntriesOf` 读(整容器读的严归类:每个自有串键一份描述符;访问器成员或不可枚举的成员 ⇒ 不是,getter 一次都不调 —— 守卫为真,调用方随后的普通读读到的就是判过的那一份)。
|
|
17
|
-
* 传输读器读 `env` / `headers`({@link readPlainStringRecord})交出去的是**副本**,按宽归类读(不可枚举的成员照读、访问器调一次);值与成员名的判据(
|
|
18
|
+
* 传输读器读 `env` / `headers`({@link readPlainStringRecord})交出去的是**副本**,按宽归类读(不可枚举的成员照读、访问器调一次);值与成员名的判据(是串、不是保留名)两边同一条。
|
|
18
19
|
*/
|
|
19
20
|
export declare function isStringRecord(v: unknown): v is Record<string, string>;
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
-
/**
|
|
23
|
-
* 这个申报名能用吗。严判:非串、空串、超 {@link TOOL_WIRE_NAME_MAX_CHARS}、**首尾带空白**一律不可用。不截断、不 trim、不规范化 —— 截短或剥掉一个名字就是**换一个身份**,
|
|
24
|
-
* 而被换掉身份的那台 server 上挂的 allowTools / toolFaces 收窄声明会一起对不上号。首尾空白 = 坏名(它不是另一个名字:名字是 `mcp__<server>__<tool>` 的身份片,
|
|
25
|
-
* 带着空白的一端无从与不带的那个对上号);内部空白(`a b`)照旧是合法名,本判官不扩判。
|
|
26
|
-
*/
|
|
27
|
-
export declare function isUsableWireName(v: unknown): v is string;
|
|
21
|
+
export { TOOL_WIRE_NAME_MAX_CHARS, isUsableWireName } from "./wire-name.js";
|
|
22
|
+
export { RESERVED_MEMBER_NAMES, isReservedMemberName, type ReservedMemberName } from "./reserved-member-name.js";
|
|
28
23
|
/**
|
|
29
24
|
* 坏名的**可打印**摘要(进拒句 / 告警元数据):种类、`empty`,或引号形的回显加原因 —— 绝不把任意长度的申报串原样回显。回显口径见 {@link quotedCallerText}
|
|
30
25
|
* (剥控制与双向字符、截 64 字)。串的原因是:超界 ⇒ `N chars > 128`(N 是送来的原长度,不是剥完的);首尾带空白 ⇒ `leading or trailing whitespace`
|
|
@@ -82,7 +77,7 @@ export type RequestMcpTransport = {
|
|
|
82
77
|
* `env` / `headers` 这两张「名 → 串」记录上的坏形另带两格机读位,调用方分流不必解析句子:`fault` = 闭集词({@link PLAIN_STRING_RECORD_FAULTS},与公开的记录读器 {@link readPlainStringRecord} 同一张表)——
|
|
83
78
|
* · `not_plain_record`:这个键的值不是纯数据记录(不是对象 / 数组 / `null`,或不是朴素 JSON 对象 —— 原型链比座位的判据长:`Map`、`Set`、类实例;读形状就抛的对象同);整张记录不交;
|
|
84
79
|
* · `member_not_string`:有一个成员的值不是串,或它读不出(它的访问器抛了,或读它的描述符时代理陷阱抛了);整张记录不交,`member` 点名它,两种情形句子分说;
|
|
85
|
-
* · `member_name_reserved
|
|
80
|
+
* · `member_name_reserved`:有成员叫保留名({@link RESERVED_MEMBER_NAMES}:`__proto__` / `constructor` / `prototype`)。值是串 ⇒ 只剔它、其余照交,每个一条、`path` 是成员级(`transport.env.__proto__`);
|
|
86
81
|
* 成员的值也不是串(或读不出)⇒ 整张记录不交,`path` 是键级,句子一次说全两件事 —— 名坏优先、值坏并列,所以词仍是 `member_name_reserved`。
|
|
87
82
|
* `member` 是成员名的**回显形**(剥控制与双向字符、截 64 字加 `…`),可直接进日志与句子,不是回查原对象的键。其余的坏形(`args` / `url` / `principalHeader` / ……)两格都不带。
|
|
88
83
|
*/
|
|
@@ -107,16 +102,11 @@ export type RequestMcpTransportRead = {
|
|
|
107
102
|
* · 软坏形(`args` 不是串数组 / `env` `headers` 不是纯数据的串记录 / `principalHeader` 非串)⇒ 读器**丢这个键**(重放存量提交体时的升级前读法),并把它记进 `lost`
|
|
108
103
|
* (受理新提交的门据它响亮拒)。`assertionHeader` 不在 `lost` 里:两个身份头的键组合与 `assertionHeader` 的值形由调用方在读之前对原始传输判。
|
|
109
104
|
* · `env` / `headers` 只收**纯数据**的记录({@link readPlainStringRecord}):必须是朴素 JSON 对象(`./plain-json-object` 的判据;`Map`、`Set`、类实例整张不收),每个自有成员的值都是串;
|
|
110
|
-
* 交出的是**副本**(不是调用方的对象,改原对象不影响读后的形)
|
|
105
|
+
* 交出的是**副本**(不是调用方的对象,改原对象不影响读后的形)。名叫保留名({@link RESERVED_MEMBER_NAMES})的成员不进副本、每个单独记进 `lost`。
|
|
111
106
|
* 坏形的 `lost` 条目带机读位(见 {@link McpEntryKeyIssue})。
|
|
112
107
|
* url 的拒句不回显值:它常规地带凭据(userinfo)。
|
|
113
108
|
*/
|
|
114
109
|
export declare function readRequestMcpTransport(t: unknown): RequestMcpTransportRead;
|
|
115
|
-
/**
|
|
116
|
-
* 引擎的声明收据不收的成员名(core `mcp-server-spec`:按赋值拷贝它会变成原型):JSON 文本铸得出这个**自有**键,对象字面量写不出。
|
|
117
|
-
* 公开,因为读器交出 `reservedMember: true` 时消费方要点名它 —— 名字只在这里定一次。
|
|
118
|
-
*/
|
|
119
|
-
export declare const PLAIN_STRING_RECORD_RESERVED_MEMBER = "__proto__";
|
|
120
110
|
/**
|
|
121
111
|
* 「名 → 串」记录读不交(或交时点名保留名)的**原因词**,闭集,三个词、一张表:
|
|
122
112
|
* 整张一词(`not_plain_record`)· 成员级一词(`member_not_string`:值读成了别的种类,或读成员时抛了 —— 句子分说)· 名字级一词(`member_name_reserved`)。
|
|
@@ -127,12 +117,12 @@ export declare const PLAIN_STRING_RECORD_FAULTS: readonly ["not_plain_record", "
|
|
|
127
117
|
export type PlainStringRecordFault = (typeof PLAIN_STRING_RECORD_FAULTS)[number];
|
|
128
118
|
/**
|
|
129
119
|
* {@link readPlainStringRecord} 的读数:`ok:false` = 记录不交(`fault` = 原因词;`detail` 是接在 `must be a plain object of string values — ` 后面的句子;坏在某个成员时 `member` 点名它的回显形);
|
|
130
|
-
* `ok:true` = 交出去的纯数据副本,`
|
|
120
|
+
* `ok:true` = 交出去的纯数据副本,`reservedMembers` = 记录里名叫保留名({@link RESERVED_MEMBER_NAMES})的成员,按自有键序(它们没进副本)。
|
|
131
121
|
*/
|
|
132
122
|
export type PlainStringRecordRead = {
|
|
133
123
|
readonly ok: true;
|
|
134
124
|
readonly record: Record<string, string>;
|
|
135
|
-
readonly
|
|
125
|
+
readonly reservedMembers: readonly ReservedMemberName[];
|
|
136
126
|
} | {
|
|
137
127
|
readonly ok: false;
|
|
138
128
|
readonly detail: string;
|
|
@@ -143,7 +133,7 @@ export type PlainStringRecordRead = {
|
|
|
143
133
|
* 读一份「名 → 串」记录 —— **公开读口**:传输读器读 `env` / `headers` 用的就是它,消费方读自己的「名 → 串」记录(中心名册的引用表)调同一只,不抄镜像。从不抛。记录交不交:是朴素 JSON 对象(座位的判据,见 `./plain-json-object`:原型是 `null`,或原型自己的原型是 `null`;纯数据 —— `Map`、`Set`、类实例、建在原型上的记录都不是),
|
|
144
134
|
* 且每个成员的值都是串。成员 = `Object.getOwnPropertyNames` 的每一个名字(不可枚举的也算 —— 引擎的收据按全部自有属性名读),值经内核 `readKey` 读(一份描述符:数据属性取值,访问器调一次,不问 `get` 陷阱;访问器或描述符 trap 抛了同判为坏 —— 词与「值不是串」同是 `member_not_string`,句子说读不出)。
|
|
145
135
|
* 读原型与成员名时代理 trap 抛了 ⇒ 整张不交(读器对调用方递来的任何对象都不抛;`JSON` 存体里不会有代理,这条只落在直接调读器的嵌入方身上)。
|
|
146
|
-
* 交出去的是副本(原型 `Object.prototype`、成员全可枚举)
|
|
147
|
-
*
|
|
136
|
+
* 交出去的是副本(原型 `Object.prototype`、成员全可枚举),调用方的对象不被交出去、也不被改;名叫保留名({@link isReservedMemberName})的成员不进副本,`reservedMembers` 记下它们。
|
|
137
|
+
* 名叫保留名而值也坏的成员,句子一次说全两件事(名坏在前、值坏并列):调用方照值改完再撞第二条是两步才改对。
|
|
148
138
|
*/
|
|
149
139
|
export declare function readPlainStringRecord(v: unknown): PlainStringRecordRead;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 判据与原因句逐字等于消费方请求腿的那一份(钉:`test/mcp-request-entry-14.0.0.test.ts` 逐例搬了消费方既有钉的夹具与期望)。
|
|
11
11
|
* 本模块只答「这一格的形」;拒绝的整句(条目下标、条目名的回显清洗、收尾句)与「拒还是降级」的后果仍是消费方按自己的腿定的 —— 判据一份,后果按腿分。
|
|
12
12
|
* 与最初搬来时相比有三处收严,都是收到与消费方请求腿同严、不是新语义,消费方改 import 这一份之后不再各留一份旧判据:申报名判官另判**首尾空白**;
|
|
13
|
-
* `env` / `headers` 只收**纯数据**的「名 → 串」记录(Map /
|
|
13
|
+
* `env` / `headers` 只收**纯数据**的「名 → 串」记录(Map / 类实例不收;保留成员名 {@link RESERVED_MEMBER_NAMES} 不进副本,交出的是副本);两只回显描述器用消费方的回显口径(剥控制与双向字符、截 64 字、引号形)。
|
|
14
14
|
*
|
|
15
15
|
* ## 依赖的引擎事实(镜像 + 漂移门)
|
|
16
16
|
* · 申报名的线长界 {@link TOOL_WIRE_NAME_MAX_CHARS} = 引擎同名常量(值级对拍:上游漂移门 ⑧);
|
|
@@ -31,6 +31,8 @@ import { isJsonArray, isPlainJsonObject, jsonShapeOf } from "./plain-json-object
|
|
|
31
31
|
import { MCP_CONTENT_ORIGINS } from "./types.js";
|
|
32
32
|
import { RESERVED_MCP_SERVER_NAME_REASON, isReservedMcpServerName } from "./mcp-server-name.js";
|
|
33
33
|
import { startsWithHttpScheme } from "./url-shape.js";
|
|
34
|
+
import { TOOL_WIRE_NAME_MAX_CHARS, hasEdgeWhitespace, isUsableWireName } from "./wire-name.js";
|
|
35
|
+
import { isReservedMemberName } from "./reserved-member-name.js";
|
|
34
36
|
// ── 值的种类描述(拒句只说「你写的是什么形」,不回显值:值可能带凭据、可能任意长)──────────────────────────────────────
|
|
35
37
|
/** 值的**种类词**(不回显值本身):`null` / `array` / `typeof` 词(`object` / `function` / `string` …);读不出的对象 ⇒ 全包唯一的那一句。形状只问座位(总,不抛):两只坏值描述器共用这一只。 */
|
|
36
38
|
function describeKindWord(v) {
|
|
@@ -92,15 +94,15 @@ export function isStringArray(v) {
|
|
|
92
94
|
return captureStringArray(v, "strict") !== undefined;
|
|
93
95
|
}
|
|
94
96
|
/**
|
|
95
|
-
* 纯数据的「名 → 串」记录(空对象合法;数组、`Map
|
|
97
|
+
* 纯数据的「名 → 串」记录(空对象合法;数组、`Map`、类实例、有保留名成员({@link isReservedMemberName})、有非串成员的对象都不是)—— 类型守卫,所以是**严归类**:成员经内核的
|
|
96
98
|
* `ownEntriesOf` 读(整容器读的严归类:每个自有串键一份描述符;访问器成员或不可枚举的成员 ⇒ 不是,getter 一次都不调 —— 守卫为真,调用方随后的普通读读到的就是判过的那一份)。
|
|
97
|
-
* 传输读器读 `env` / `headers`({@link readPlainStringRecord})交出去的是**副本**,按宽归类读(不可枚举的成员照读、访问器调一次);值与成员名的判据(
|
|
99
|
+
* 传输读器读 `env` / `headers`({@link readPlainStringRecord})交出去的是**副本**,按宽归类读(不可枚举的成员照读、访问器调一次);值与成员名的判据(是串、不是保留名)两边同一条。
|
|
98
100
|
*/
|
|
99
101
|
export function isStringRecord(v) {
|
|
100
102
|
if (!isPlainJsonObject(v))
|
|
101
103
|
return false;
|
|
102
104
|
const entries = ownEntriesOf(v);
|
|
103
|
-
return entries.ok && entries.value.every(([name, value]) => name
|
|
105
|
+
return entries.ok && entries.value.every(([name, value]) => !isReservedMemberName(name) && typeof value === "string");
|
|
104
106
|
}
|
|
105
107
|
// ── 调用方写的串的回显口径 ──────────────────────────────────────────────────────────────────────────
|
|
106
108
|
/** 句子里点名一个值的写法:{@link echoCallerText} 外加一对双引号;内部的引号与反斜杠逐字(不转义)。 */
|
|
@@ -108,21 +110,9 @@ function quotedCallerText(text) {
|
|
|
108
110
|
return `"${echoCallerText(text)}"`;
|
|
109
111
|
}
|
|
110
112
|
// ── 申报名的线长界 ──────────────────────────────────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
export
|
|
113
|
-
|
|
114
|
-
* 首尾带空白吗 —— 「空白」就是 `String.prototype.trim` 剥的那一组(ECMAScript 的 WhiteSpace + LineTerminator:空格 / 制表 / 换行 / NBSP / BOM / 全角空格 / 行段分隔符等)。
|
|
115
|
-
* {@link isUsableWireName} 与 {@link describeBadWireName} 共读这一个定义,判与说不会各按各的口径。
|
|
116
|
-
*/
|
|
117
|
-
const hasEdgeWhitespace = (v) => v.trim() !== v;
|
|
118
|
-
/**
|
|
119
|
-
* 这个申报名能用吗。严判:非串、空串、超 {@link TOOL_WIRE_NAME_MAX_CHARS}、**首尾带空白**一律不可用。不截断、不 trim、不规范化 —— 截短或剥掉一个名字就是**换一个身份**,
|
|
120
|
-
* 而被换掉身份的那台 server 上挂的 allowTools / toolFaces 收窄声明会一起对不上号。首尾空白 = 坏名(它不是另一个名字:名字是 `mcp__<server>__<tool>` 的身份片,
|
|
121
|
-
* 带着空白的一端无从与不带的那个对上号);内部空白(`a b`)照旧是合法名,本判官不扩判。
|
|
122
|
-
*/
|
|
123
|
-
export function isUsableWireName(v) {
|
|
124
|
-
return typeof v === "string" && v.length > 0 && v.length <= TOOL_WIRE_NAME_MAX_CHARS && !hasEdgeWhitespace(v);
|
|
125
|
-
}
|
|
113
|
+
// 判据住在零依赖叶子 `./wire-name`(18.0.0 搬过去:`./types` 的 `a2a` 对端名也问它,而本模块引 `./types` —— 留在这里会成环);公开名照旧经本模块转出,同一个函数对象。
|
|
114
|
+
export { TOOL_WIRE_NAME_MAX_CHARS, isUsableWireName } from "./wire-name.js";
|
|
115
|
+
export { RESERVED_MEMBER_NAMES, isReservedMemberName } from "./reserved-member-name.js"; // 18.0.0: the one reserved-member-name table (schema guard · this reader · the center-refs resolver)
|
|
126
116
|
/**
|
|
127
117
|
* 坏名的**可打印**摘要(进拒句 / 告警元数据):种类、`empty`,或引号形的回显加原因 —— 绝不把任意长度的申报串原样回显。回显口径见 {@link quotedCallerText}
|
|
128
118
|
* (剥控制与双向字符、截 64 字)。串的原因是:超界 ⇒ `N chars > 128`(N 是送来的原长度,不是剥完的);首尾带空白 ⇒ `leading or trailing whitespace`
|
|
@@ -222,7 +212,7 @@ const inheritedIdentityIssue = (key) => ({
|
|
|
222
212
|
* · 软坏形(`args` 不是串数组 / `env` `headers` 不是纯数据的串记录 / `principalHeader` 非串)⇒ 读器**丢这个键**(重放存量提交体时的升级前读法),并把它记进 `lost`
|
|
223
213
|
* (受理新提交的门据它响亮拒)。`assertionHeader` 不在 `lost` 里:两个身份头的键组合与 `assertionHeader` 的值形由调用方在读之前对原始传输判。
|
|
224
214
|
* · `env` / `headers` 只收**纯数据**的记录({@link readPlainStringRecord}):必须是朴素 JSON 对象(`./plain-json-object` 的判据;`Map`、`Set`、类实例整张不收),每个自有成员的值都是串;
|
|
225
|
-
* 交出的是**副本**(不是调用方的对象,改原对象不影响读后的形)
|
|
215
|
+
* 交出的是**副本**(不是调用方的对象,改原对象不影响读后的形)。名叫保留名({@link RESERVED_MEMBER_NAMES})的成员不进副本、每个单独记进 `lost`。
|
|
226
216
|
* 坏形的 `lost` 条目带机读位(见 {@link McpEntryKeyIssue})。
|
|
227
217
|
* url 的拒句不回显值:它常规地带凭据(userinfo)。
|
|
228
218
|
*/
|
|
@@ -323,12 +313,8 @@ function snapshotTransport(tr) {
|
|
|
323
313
|
return { ok: true, value: { kind: "other", value: kind.value } };
|
|
324
314
|
}
|
|
325
315
|
// ── 「名 → 串」记录的读法(`env` / `headers`)────────────────────────────────────────────────────────
|
|
326
|
-
/**
|
|
327
|
-
|
|
328
|
-
* 公开,因为读器交出 `reservedMember: true` 时消费方要点名它 —— 名字只在这里定一次。
|
|
329
|
-
*/
|
|
330
|
-
export const PLAIN_STRING_RECORD_RESERVED_MEMBER = "__proto__";
|
|
331
|
-
const RESERVED_MEMBER_FAULT = `no member of a declaration may be named "${PLAIN_STRING_RECORD_RESERVED_MEMBER}"`;
|
|
316
|
+
/** 保留成员名的原因句(一个名一句;名字取自那一张表 {@link RESERVED_MEMBER_NAMES})。 */
|
|
317
|
+
const reservedMemberFault = (name) => `no member of a declaration may be named "${name}"`;
|
|
332
318
|
/**
|
|
333
319
|
* 「名 → 串」记录读不交(或交时点名保留名)的**原因词**,闭集,三个词、一张表:
|
|
334
320
|
* 整张一词(`not_plain_record`)· 成员级一词(`member_not_string`:值读成了别的种类,或读成员时抛了 —— 句子分说)· 名字级一词(`member_name_reserved`)。
|
|
@@ -361,8 +347,8 @@ function notPlainDetail(shape, v) {
|
|
|
361
347
|
* 读一份「名 → 串」记录 —— **公开读口**:传输读器读 `env` / `headers` 用的就是它,消费方读自己的「名 → 串」记录(中心名册的引用表)调同一只,不抄镜像。从不抛。记录交不交:是朴素 JSON 对象(座位的判据,见 `./plain-json-object`:原型是 `null`,或原型自己的原型是 `null`;纯数据 —— `Map`、`Set`、类实例、建在原型上的记录都不是),
|
|
362
348
|
* 且每个成员的值都是串。成员 = `Object.getOwnPropertyNames` 的每一个名字(不可枚举的也算 —— 引擎的收据按全部自有属性名读),值经内核 `readKey` 读(一份描述符:数据属性取值,访问器调一次,不问 `get` 陷阱;访问器或描述符 trap 抛了同判为坏 —— 词与「值不是串」同是 `member_not_string`,句子说读不出)。
|
|
363
349
|
* 读原型与成员名时代理 trap 抛了 ⇒ 整张不交(读器对调用方递来的任何对象都不抛;`JSON` 存体里不会有代理,这条只落在直接调读器的嵌入方身上)。
|
|
364
|
-
* 交出去的是副本(原型 `Object.prototype`、成员全可枚举)
|
|
365
|
-
*
|
|
350
|
+
* 交出去的是副本(原型 `Object.prototype`、成员全可枚举),调用方的对象不被交出去、也不被改;名叫保留名({@link isReservedMemberName})的成员不进副本,`reservedMembers` 记下它们。
|
|
351
|
+
* 名叫保留名而值也坏的成员,句子一次说全两件事(名坏在前、值坏并列):调用方照值改完再撞第二条是两步才改对。
|
|
366
352
|
*/
|
|
367
353
|
export function readPlainStringRecord(v) {
|
|
368
354
|
// 形状归座位旁的分类(不在这里再推一遍;座位总 —— 读形状就抛的对象答 `uninspectable`)。成员名的读取(`ownKeys` 陷阱抛)读不出的对象也不是纯数据。
|
|
@@ -377,7 +363,7 @@ export function readPlainStringRecord(v) {
|
|
|
377
363
|
// 累加器是零原型的袋、成员用 `Object.defineProperty` + 零原型描述符写(定义语义,不走 [[Set]]),最后用对象展开(同为定义语义)铸成普通对象:原型上被污染成只读的同名键不让读器抛,
|
|
378
364
|
// 原型上的数字键 setter 吞不掉成员(此前 `entries.push` + `Object.fromEntries` 被继承的数字键 getter / setter 弄抛)
|
|
379
365
|
const members = Object.create(null);
|
|
380
|
-
|
|
366
|
+
const reservedMembers = names.filter(isReservedMemberName); // `filter` 走 CreateDataProperty,不走 [[Set]](继承的数字键 setter 吞不掉)
|
|
381
367
|
for (const name of names) {
|
|
382
368
|
// 成员经内核读(契约见函数头注):一份描述符里取值 —— 数据属性取值、访问器调一次、从不问 Proxy 的 `get` 陷阱;成员在两次读之间被删掉 ⇒ 不是串(不读继承来的值)
|
|
383
369
|
const read = readKey(v, name);
|
|
@@ -385,28 +371,25 @@ export function readPlainStringRecord(v) {
|
|
|
385
371
|
const value = read.presence === "own" ? read.value : undefined;
|
|
386
372
|
if (unreadable || typeof value !== "string") {
|
|
387
373
|
const valueFault = unreadable ? "could not be read (its getter threw)" : `must be a string — got ${describeValueShape(value)}`;
|
|
388
|
-
const reserved = name === PLAIN_STRING_RECORD_RESERVED_MEMBER;
|
|
389
374
|
// 词:名坏优先(句子里值坏并列说全);名没问题时,读不出与值不是串同词,句子分说
|
|
390
|
-
const fault =
|
|
375
|
+
const fault = isReservedMemberName(name) ? "member_name_reserved" : "member_not_string";
|
|
391
376
|
return {
|
|
392
377
|
ok: false,
|
|
393
378
|
fault,
|
|
394
379
|
member: echoCallerText(name),
|
|
395
|
-
detail: `its member "${echoCallerText(name)}" ${
|
|
380
|
+
detail: `its member "${echoCallerText(name)}" ${isReservedMemberName(name) ? `is not allowed (${reservedMemberFault(name)}) and its value ${valueFault}` : valueFault}`,
|
|
396
381
|
};
|
|
397
382
|
}
|
|
398
|
-
if (name
|
|
399
|
-
reservedMember = true;
|
|
400
|
-
else
|
|
383
|
+
if (!isReservedMemberName(name))
|
|
401
384
|
Object.defineProperty(members, name, dataDescriptor(value));
|
|
402
385
|
}
|
|
403
|
-
return { ok: true, record: { ...members },
|
|
386
|
+
return { ok: true, record: { ...members }, reservedMembers };
|
|
404
387
|
}
|
|
405
388
|
/** 把诊断条目铸成数组:`undefined`(这一处没有诊断)略去。`filter` 的结果走 `CreateDataProperty`,不走 `[[Set]]` —— `lost` 是「门拒什么」的读数,不能被继承的数字键 setter 吞掉。 */
|
|
406
389
|
const issuesOf = (...candidates) => candidates.filter((c) => c !== undefined);
|
|
407
390
|
/**
|
|
408
391
|
* `transport.env` / `transport.headers` 的读法(两键同一只):缺席 ⇒ 缺席;记录不交 ⇒ 一处诊断点名**这个键**(不交的是整张记录;坏在某个成员时句子与 `member` 点名它);
|
|
409
|
-
* 交 ⇒
|
|
392
|
+
* 交 ⇒ 纯数据副本,名叫保留名的成员每个单记一处诊断、路径 `<键>.<保留名>`。
|
|
410
393
|
*/
|
|
411
394
|
function readTransportRecord(path, raw) {
|
|
412
395
|
if (raw === undefined)
|
|
@@ -420,6 +403,6 @@ function readTransportRecord(path, raw) {
|
|
|
420
403
|
}
|
|
421
404
|
return {
|
|
422
405
|
record: read.record,
|
|
423
|
-
issues: read.
|
|
406
|
+
issues: read.reservedMembers.map((name) => ({ path: `${path}.${name}`, reason: reservedMemberFault(name), member: name, fault: "member_name_reserved" })),
|
|
424
407
|
};
|
|
425
408
|
}
|
|
@@ -138,7 +138,7 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
138
138
|
releasedOn: "2026-10-10",
|
|
139
139
|
removableAfter: "2026-11-09",
|
|
140
140
|
rewrites: "every NON_SECRET_URL seat in a domain document (derived from the domain schemas, not listed by hand — today models.models[].baseUrl, " +
|
|
141
|
-
"mcp.servers[].transport.url, execution.sessionMirror.engineUrl, workers.workers[].coords.gitApiBaseUrl): `#` and everything after it are removed " +
|
|
141
|
+
"mcp.servers[].transport.url, a2a.peers[].url, a2a.peers[].cardUrl, execution.sessionMirror.engineUrl, workers.workers[].coords.gitApiBaseUrl): `#` and everything after it are removed " +
|
|
142
142
|
"(a fragment never goes on the wire); the same walk covers config.d/remote-exec.json, derived from its schema the same way " +
|
|
143
143
|
"(today the k8s apiUrl and s3Snapshot.endpoint) — the command-line migrate reads and rewrites it as one more document",
|
|
144
144
|
removal: "the next major after the window: delete the SS-M7 step and its schema walk; the NON_SECRET_URL fragment rule loses its SS-M7 clause",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 同形**。当年 `limits` 域就是这样在单机腿静默丢了一整片限额发布(远端腿同批可用,所以更难看出来)。
|
|
13
13
|
* 今日的兜底:漏登记的文件会被 `FileConfigStore`(`./node`)的 `"unread-config-file"` 警告点名(fail-loud,
|
|
14
14
|
* 见 getEffective),但那只在消费方接了 `onWarning` 时才响 —— **登记仍是第一现场的义务**。 */
|
|
15
|
-
export declare const PORTABLE_DOMAINS: readonly ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits"];
|
|
15
|
+
export declare const PORTABLE_DOMAINS: readonly ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits", "a2a"];
|
|
16
16
|
export type PortableDomain = (typeof PORTABLE_DOMAINS)[number];
|
|
17
17
|
/**
|
|
18
18
|
* 非便携域的**排除理由表**(#282 件5 附带裁定:禁裸排除)。
|
package/dist/portable-domains.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 同形**。当年 `limits` 域就是这样在单机腿静默丢了一整片限额发布(远端腿同批可用,所以更难看出来)。
|
|
13
13
|
* 今日的兜底:漏登记的文件会被 `FileConfigStore`(`./node`)的 `"unread-config-file"` 警告点名(fail-loud,
|
|
14
14
|
* 见 getEffective),但那只在消费方接了 `onWarning` 时才响 —— **登记仍是第一现场的义务**。 */
|
|
15
|
-
export const PORTABLE_DOMAINS = ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits"];
|
|
15
|
+
export const PORTABLE_DOMAINS = ["models", "rosters", "skills", "mcp", "plugins", "scenarios", "collab", "governance", "execution", "prompts", "limits", "a2a"];
|
|
16
16
|
/**
|
|
17
17
|
* 非便携域的**排除理由表**(#282 件5 附带裁定:禁裸排除)。
|
|
18
18
|
*
|
package/dist/remote-exec.d.ts
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
|
|
13
13
|
* agent). No isolation.
|
|
14
14
|
* - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
|
|
15
|
+
* Either:
|
|
16
|
+
* - `none` (18.0.0) no execution environment for the agent's tools — the file form of `REMOTE_EXEC=none`. No fields:
|
|
17
|
+
* there is nothing to configure.
|
|
15
18
|
*
|
|
16
19
|
* 7.0.0: every `provider` word here is an execution lane word (`EXECUTION_LANE_WORDS`, `execution-lanes.ts` — the ONE
|
|
17
20
|
* vocabulary; `test/execution-lanes-7.0.0.test.ts` pins `provider ⊆ EXECUTION_LANE_WORDS`, at runtime and at compile time).
|
|
@@ -90,5 +93,7 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
90
93
|
serial: z.ZodString;
|
|
91
94
|
adbPath: z.ZodOptional<z.ZodString>;
|
|
92
95
|
mountPath: z.ZodOptional<z.ZodString>;
|
|
96
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
97
|
+
provider: z.ZodLiteral<"none">;
|
|
93
98
|
}, z.core.$strip>], "provider">;
|
|
94
99
|
export type RemoteExecSpec = z.infer<typeof RemoteExecSpec>;
|
package/dist/remote-exec.js
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
|
|
13
13
|
* agent). No isolation.
|
|
14
14
|
* - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
|
|
15
|
+
* Either:
|
|
16
|
+
* - `none` (18.0.0) no execution environment for the agent's tools — the file form of `REMOTE_EXEC=none`. No fields:
|
|
17
|
+
* there is nothing to configure.
|
|
15
18
|
*
|
|
16
19
|
* 7.0.0: every `provider` word here is an execution lane word (`EXECUTION_LANE_WORDS`, `execution-lanes.ts` — the ONE
|
|
17
20
|
* vocabulary; `test/execution-lanes-7.0.0.test.ts` pins `provider ⊆ EXECUTION_LANE_WORDS`, at runtime and at compile time).
|
|
@@ -147,6 +150,8 @@ export const RemoteExecSpec = z.discriminatedUnion("provider", [
|
|
|
147
150
|
adbPath: execStr.optional(),
|
|
148
151
|
mountPath: execStr.optional(),
|
|
149
152
|
}),
|
|
153
|
+
// ── none (18.0.0): no execution environment for the agent's tools (REMOTE_EXEC=none). Zero fields — nothing to configure, no secrets.
|
|
154
|
+
z.object({ provider: z.literal("none") }),
|
|
150
155
|
], {
|
|
151
156
|
// A retired lane word as `provider` (no matching discriminator) gets the tombstone's way out; any other miss keeps zod's sentence.
|
|
152
157
|
// `provider` is read from the input's OWN keys (the input is the caller's object: an inherited / polluted `provider` is not the word they wrote; a proxy that cannot be read has no word to refuse)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有叶子模块(运行期零依赖,浏览器安全;不进 `package.json` 的 exports,主桶经 `./mcp-request-entry` 转出这里的公开名):
|
|
3
|
+
* **一个名字能不能作「名 → 串」记录的成员名**的唯一判据 —— 原型链上的三个名字永远不能:`__proto__`(按赋值拷贝会变成原型;zod 的 record 读键前先跳过它、不判它的值)、
|
|
4
|
+
* `constructor` / `prototype`(读方对普通对象做 `obj[名]` 时撞上原型成员)。
|
|
5
|
+
* 三个读口问它,同一张表、同一个答案:域 schema 的凭据引用集合(`./types` 的 `credentialRefs`:在场 ⇒ 集合坏)· 公开的记录读器(`./mcp-request-entry` 的 `readPlainStringRecord` /
|
|
6
|
+
* `isStringRecord`,请求面传输的 `env` / `headers` 同读:剔出副本并点名)· 中心引用解析口(`./center-refs`,经记录读器:在场 ⇒ `refs_unreadable`)。
|
|
7
|
+
* 住在叶子里,是因为 `./types` 与 `./mcp-request-entry` 都要它,而后者反过来引 `./types`。
|
|
8
|
+
*/
|
|
9
|
+
/** 保留成员名,闭集。 */
|
|
10
|
+
export declare const RESERVED_MEMBER_NAMES: readonly ["__proto__", "constructor", "prototype"];
|
|
11
|
+
export type ReservedMemberName = (typeof RESERVED_MEMBER_NAMES)[number];
|
|
12
|
+
/** 这个名字是保留成员名吗(大小写敏感:`Constructor` / `X-Prototype` 不是)。 */
|
|
13
|
+
export declare function isReservedMemberName(name: string): name is ReservedMemberName;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 包内私有叶子模块(运行期零依赖,浏览器安全;不进 `package.json` 的 exports,主桶经 `./mcp-request-entry` 转出这里的公开名):
|
|
3
|
+
* **一个名字能不能作「名 → 串」记录的成员名**的唯一判据 —— 原型链上的三个名字永远不能:`__proto__`(按赋值拷贝会变成原型;zod 的 record 读键前先跳过它、不判它的值)、
|
|
4
|
+
* `constructor` / `prototype`(读方对普通对象做 `obj[名]` 时撞上原型成员)。
|
|
5
|
+
* 三个读口问它,同一张表、同一个答案:域 schema 的凭据引用集合(`./types` 的 `credentialRefs`:在场 ⇒ 集合坏)· 公开的记录读器(`./mcp-request-entry` 的 `readPlainStringRecord` /
|
|
6
|
+
* `isStringRecord`,请求面传输的 `env` / `headers` 同读:剔出副本并点名)· 中心引用解析口(`./center-refs`,经记录读器:在场 ⇒ `refs_unreadable`)。
|
|
7
|
+
* 住在叶子里,是因为 `./types` 与 `./mcp-request-entry` 都要它,而后者反过来引 `./types`。
|
|
8
|
+
*/
|
|
9
|
+
/** 保留成员名,闭集。 */
|
|
10
|
+
export const RESERVED_MEMBER_NAMES = ["__proto__", "constructor", "prototype"];
|
|
11
|
+
/** 这个名字是保留成员名吗(大小写敏感:`Constructor` / `X-Prototype` 不是)。 */
|
|
12
|
+
export function isReservedMemberName(name) {
|
|
13
|
+
return RESERVED_MEMBER_NAMES.includes(name);
|
|
14
|
+
}
|
package/dist/retired-envs.js
CHANGED
|
@@ -75,6 +75,8 @@ export const RETIRED_ENVS = [
|
|
|
75
75
|
["HOST_EXEC_SPOOL_DISABLED", "HOST_EXEC_SPOOL_ENABLED", "replaced", negativeAliasWhy("HOST_EXEC_SPOOL_DISABLED", "HOST_EXEC_SPOOL_ENABLED")],
|
|
76
76
|
// 无人值守腿的模式旋钮退役:这些腿的答集是常量,不是旋钮。
|
|
77
77
|
["UNATTENDED_PERMISSION_MODE", "since server 7.109.0 the unattended legs (leader workers and their repair legs, run-local, and their durable resumes) always run permissionMode \"default\" with an approver that answers by ask class: a machine answers only \"ordinary\" asks, and every other class goes to the leg's approver seat (run-local's terminal prompt) or parks / is refused — there is no mode to choose", "removed"],
|
|
78
|
+
// 执行车道总开关:每个部署都点名自己的车道之后它恒真(「点名了没有」只剩一个答者 REMOTE_EXEC),留着 = 两个键答一个问题;「没有执行环境」也是一个声明(REMOTE_EXEC=none)。
|
|
79
|
+
["REQUIRE_EXECUTION_LANE", "REMOTE_EXEC", "replaced", "every deployment names its execution lane; REMOTE_EXEC=none declares one with no execution environment"],
|
|
78
80
|
];
|
|
79
81
|
// 冻结:拿到导出的人改不了拒启判据(读拒句的与投影 wire 的是同一个数组对象)。
|
|
80
82
|
for (const row of RETIRED_ENVS)
|
package/dist/secret-refs.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface EnvRef {
|
|
|
18
18
|
envName: string;
|
|
19
19
|
refs: EnvRefUse[];
|
|
20
20
|
}
|
|
21
|
-
/** Every env-NAME referenced across models / workers / mcp, grouped + sorted by name.
|
|
21
|
+
/** Every env-NAME referenced across models / workers / mcp / a2a, grouped + sorted by name.
|
|
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
|
/**
|
package/dist/secret-refs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Every env-NAME referenced across models / workers / mcp, grouped + sorted by name.
|
|
1
|
+
/** Every env-NAME referenced across models / workers / mcp / a2a, grouped + sorted by name.
|
|
2
2
|
* (integrations domain DELETED in 0.6.0 — OA/Gitea coordinates ride worker.env/secretEnv, EXPERT-REDESIGN §9.) */
|
|
3
3
|
export function collectEnvRefs(eff) {
|
|
4
4
|
const map = new Map();
|
|
@@ -33,6 +33,10 @@ export function collectEnvRefs(eff) {
|
|
|
33
33
|
for (const [key, envName] of Object.entries(refs ?? {}))
|
|
34
34
|
add(envName, "mcp", `server "${s.name}" ${kind}.${key}`);
|
|
35
35
|
}
|
|
36
|
+
// 18.0.0: an A2A peer's header references are env-NAMEs the reading side must provide, like an http MCP server's.
|
|
37
|
+
for (const p of eff.a2a.peers)
|
|
38
|
+
for (const [key, envName] of Object.entries(p.headerRefs ?? {}))
|
|
39
|
+
add(envName, "a2a", `peer "${p.name}" headerRefs.${key}`);
|
|
36
40
|
return [...map.entries()].map(([envName, refs]) => ({ envName, refs })).sort((a, b) => a.envName.localeCompare(b.envName));
|
|
37
41
|
}
|
|
38
42
|
/**
|