@sema-agent/settings-schema 12.3.0 → 13.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 +170 -0
- package/README.md +32 -4
- package/conformance/VERSION +1 -1
- package/conformance/cases/c01-entry-salvage-then-typo/expected.json +1 -3
- package/conformance/cases/c03-literal-dotted-top-key/expected.json +4 -12
- package/conformance/cases/c10-record-key-secret-redacted-twice/expected.json +1 -3
- package/conformance/cases/c23-mixed-validity-models/expected.json +2 -6
- package/conformance/cases/c24-valid-entries-with-unknown-keys/expected.json +2 -6
- package/conformance/cases/c25-published-roundtrip-zero-warnings/expected.json +2 -6
- package/conformance/cases/c30-version-triple-a-first/expected.json +2 -6
- package/conformance/cases/c31-version-triple-b-changed/expected.json +2 -6
- package/conformance/cases/c32-version-triple-a-restored/expected.json +2 -6
- package/conformance/cases/c40-s712-identity-only-what-reads-out/expected.json +1 -3
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +1 -3
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +2 -6
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +1 -3
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +1 -3
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +1 -3
- package/conformance/cases/c47-s811-roles-key-url-userinfo-withheld/expected.json +1 -3
- package/conformance/cases/c48-s935-model-headers-transported/expected.json +6 -18
- package/conformance/cases/c49-s935-model-headers-secret-entry-dropped/expected.json +1 -3
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +1 -3
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +1 -3
- package/conformance/cases/c72-dangling-reference-write-seat-refuses/expected.json +1 -3
- package/conformance/cases/cc03-model-compat-unknown-subkey/expected.json +2 -6
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +1 -3
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +1 -3
- package/conformance/cases/cli05-long-and-prefix-colliding-names/expected.json +6 -18
- package/conformance/cases/t01-absent-domain-key-not-null/expected.json +1 -3
- package/conformance/cases/t03-prototype-name-key-not-swallowed/expected.json +2 -6
- package/conformance/cases/t04-secret-literal-never-in-warning-value/expected.json +1 -3
- package/dist/hooks-config.d.ts +235 -0
- package/dist/hooks-config.js +458 -0
- package/dist/hooks.d.ts +16 -154
- package/dist/hooks.js +17 -111
- package/dist/http-hook-allowlists.d.ts +63 -0
- package/dist/http-hook-allowlists.js +73 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -1
- package/dist/mcp-server-policy.d.ts +75 -0
- package/dist/mcp-server-policy.js +84 -0
- package/dist/safety-merge-spec.d.ts +13 -3
- package/dist/safety-merge-spec.js +10 -3
- package/dist/settings-sources.d.ts +91 -0
- package/dist/settings-sources.js +107 -0
- package/dist/types.d.ts +6 -6
- package/dist/types.js +9 -5
- package/dist/ui-language.d.ts +40 -0
- package/dist/ui-language.js +37 -0
- package/dist/url-pattern.d.ts +33 -0
- package/dist/url-pattern.js +239 -0
- package/package.json +1 -1
package/dist/hooks.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* discovering it the hard way (the 1.3.0 / 1.6.0 / 1.8.0 / 2.0.0 pattern).
|
|
14
14
|
*
|
|
15
15
|
* CONSUMERS
|
|
16
|
-
* - service (the worker server): `TaskRequest.settings.hooks` wire face = `HooksConfig`
|
|
16
|
+
* - service (the worker server): `TaskRequest.settings.hooks` wire face = `HooksConfig` (`./hooks-config`); the worker-side
|
|
17
17
|
* hook-runner translates matching entries into core `TaskSpec.hooks` in-process callbacks. Single-user
|
|
18
18
|
* gate first (requirePrincipal semantics, same as MCP injection ).
|
|
19
19
|
* - core (engine): per-event input/output shapes for the engine-owned events; `stoppedBy` on settle.
|
|
@@ -26,11 +26,21 @@
|
|
|
26
26
|
* only from their owner; the shell must NOT double-fire engine-owned events. Configuring an event whose
|
|
27
27
|
* owner hasn't shipped its runner yet is LEGAL (no validation error) — it fires when that half ships.
|
|
28
28
|
*
|
|
29
|
+
* CONFIG SURFACE: what a settings document's `hooks` key may carry — the event vocabulary {@link HOOK_EVENTS}, the entry types, the ONE
|
|
30
|
+
* classifier `classifyHooksConfig` and the zod faces that read it (`HooksConfig` / `HookMatcherEntry` / `HookEntry` …) — is defined in
|
|
31
|
+
* `./hooks-config` and re-exported here whole; import it from here (or the main barrel) as before.
|
|
32
|
+
*
|
|
33
|
+
* WORKSPACE TRUST AND THE HOOKS EXECUTION GATE (contract; the gate executes in the client core, in one place — not in this package):
|
|
34
|
+
* when the host reports the workspace `declined` (`WORKSPACE_TRUST_STATES`), NO hook runs from ANY settings layer — the user, managed
|
|
35
|
+
* and flag layers included (CC 2.1.296: an untrusted session runs no hooks at all, `cli296.js:249597`). `no_trust_step` (the host has no
|
|
36
|
+
* trust step) differs from `declined` at this gate only: the gate stays open, and what applies is layer admission alone
|
|
37
|
+
* (`layerAdmission`: a repository-controllable layer's hooks are `trust_withheld` until the workspace is `accepted`). The gate is not
|
|
38
|
+
* layer admission: it lives in neither `SAFETY_MERGE_SPEC` nor `SETTINGS_SOURCE_CONTRACT`.
|
|
39
|
+
*
|
|
29
40
|
* Pure zod contract — zero runtime deps beyond zod, browser-safe, same as the rest of this package.
|
|
30
41
|
*/
|
|
31
42
|
import { z } from "zod";
|
|
32
|
-
|
|
33
|
-
export declare const HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "Notification", "UserPromptSubmit", "UserPromptExpansion", "SessionStart", "SessionEnd", "Stop", "StopFailure", "SubagentStart", "SubagentStop", "PreCompact", "PostCompact", "PermissionRequest", "PermissionDenied", "Setup", "TeammateIdle", "TaskCreated", "TaskCompleted", "Elicitation", "ElicitationResult", "ConfigChange", "WorktreeCreate", "WorktreeRemove", "InstructionsLoaded", "CwdChanged", "FileChanged", "MessageDisplay", "DirectoryAdded", "PreModelSwitch", "PostModelSwitch"];
|
|
43
|
+
export * from "./hooks-config.js";
|
|
34
44
|
export declare const HookEventName: z.ZodEnum<{
|
|
35
45
|
PreToolUse: "PreToolUse";
|
|
36
46
|
PostToolUse: "PostToolUse";
|
|
@@ -223,11 +233,11 @@ export type AssistantTurnError = z.infer<typeof AssistantTurnError>;
|
|
|
223
233
|
/** SessionEnd `reason` values (CC EXIT_REASONS). */
|
|
224
234
|
export declare const SESSION_EXIT_REASONS: readonly ["clear", "resume", "logout", "prompt_input_exit", "other", "bypass_permissions_disabled"];
|
|
225
235
|
export declare const SessionExitReason: z.ZodEnum<{
|
|
236
|
+
other: "other";
|
|
226
237
|
clear: "clear";
|
|
227
238
|
resume: "resume";
|
|
228
239
|
logout: "logout";
|
|
229
240
|
prompt_input_exit: "prompt_input_exit";
|
|
230
|
-
other: "other";
|
|
231
241
|
bypass_permissions_disabled: "bypass_permissions_disabled";
|
|
232
242
|
}>;
|
|
233
243
|
export type SessionExitReason = z.infer<typeof SessionExitReason>;
|
|
@@ -372,11 +382,11 @@ export declare const SessionEndHookInput: z.ZodObject<{
|
|
|
372
382
|
agent_type: z.ZodOptional<z.ZodString>;
|
|
373
383
|
hook_event_name: z.ZodLiteral<"SessionEnd">;
|
|
374
384
|
reason: z.ZodEnum<{
|
|
385
|
+
other: "other";
|
|
375
386
|
clear: "clear";
|
|
376
387
|
resume: "resume";
|
|
377
388
|
logout: "logout";
|
|
378
389
|
prompt_input_exit: "prompt_input_exit";
|
|
379
|
-
other: "other";
|
|
380
390
|
bypass_permissions_disabled: "bypass_permissions_disabled";
|
|
381
391
|
}>;
|
|
382
392
|
}, z.core.$strip>;
|
|
@@ -982,11 +992,11 @@ export declare const HookInput: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
982
992
|
agent_type: z.ZodOptional<z.ZodString>;
|
|
983
993
|
hook_event_name: z.ZodLiteral<"SessionEnd">;
|
|
984
994
|
reason: z.ZodEnum<{
|
|
995
|
+
other: "other";
|
|
985
996
|
clear: "clear";
|
|
986
997
|
resume: "resume";
|
|
987
998
|
logout: "logout";
|
|
988
999
|
prompt_input_exit: "prompt_input_exit";
|
|
989
|
-
other: "other";
|
|
990
1000
|
bypass_permissions_disabled: "bypass_permissions_disabled";
|
|
991
1001
|
}>;
|
|
992
1002
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -2111,143 +2121,6 @@ export type HookOutput = z.infer<typeof HookOutput>;
|
|
|
2111
2121
|
export declare const HOOK_INJECTED_ENV: readonly ["CLAUDE_PROJECT_DIR", "CLAUDE_ENV_FILE"];
|
|
2112
2122
|
/** Default per-hook timeout when an entry does not set one (seconds; CC default). */
|
|
2113
2123
|
export declare const DEFAULT_HOOK_TIMEOUT_SECONDS = 60;
|
|
2114
|
-
/** `command` — shell command on the worker host (DESIGN POINT ②). First runner phase. */
|
|
2115
|
-
export declare const CommandHookEntry: z.ZodObject<{
|
|
2116
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2117
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2118
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2119
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2120
|
-
type: z.ZodLiteral<"command">;
|
|
2121
|
-
command: z.ZodString;
|
|
2122
|
-
shell: z.ZodOptional<z.ZodEnum<{
|
|
2123
|
-
bash: "bash";
|
|
2124
|
-
powershell: "powershell";
|
|
2125
|
-
}>>;
|
|
2126
|
-
async: z.ZodOptional<z.ZodBoolean>;
|
|
2127
|
-
asyncRewake: z.ZodOptional<z.ZodBoolean>;
|
|
2128
|
-
}, z.core.$strip>;
|
|
2129
|
-
/** `prompt` — one-shot LLM evaluation of the hook input ($ARGUMENTS = input JSON). */
|
|
2130
|
-
export declare const PromptHookEntry: z.ZodObject<{
|
|
2131
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2132
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2133
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2134
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2135
|
-
type: z.ZodLiteral<"prompt">;
|
|
2136
|
-
prompt: z.ZodString;
|
|
2137
|
-
model: z.ZodOptional<z.ZodString>;
|
|
2138
|
-
}, z.core.$strip>;
|
|
2139
|
-
/** `agent` — agentic verifier loop over the hook input ($ARGUMENTS = input JSON). */
|
|
2140
|
-
export declare const AgentHookEntry: z.ZodObject<{
|
|
2141
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2142
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2143
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2144
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2145
|
-
type: z.ZodLiteral<"agent">;
|
|
2146
|
-
prompt: z.ZodString;
|
|
2147
|
-
model: z.ZodOptional<z.ZodString>;
|
|
2148
|
-
}, z.core.$strip>;
|
|
2149
|
-
/** `http` — POST the hook input JSON to a URL. Headers may reference $NAMEs listed in allowedEnvVars
|
|
2150
|
-
* (env-NAME interpolation only — the config NEVER carries a secret value; same boundary as the rest
|
|
2151
|
-
* of this package). */
|
|
2152
|
-
export declare const HttpHookEntry: z.ZodObject<{
|
|
2153
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2154
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2155
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2156
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2157
|
-
type: z.ZodLiteral<"http">;
|
|
2158
|
-
url: z.ZodString;
|
|
2159
|
-
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
2160
|
-
allowedEnvVars: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2161
|
-
}, z.core.$strip>;
|
|
2162
|
-
/** One configured hook (CC settings shape + shell superset fields; discriminated on `type`). */
|
|
2163
|
-
export declare const HookEntry: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
2164
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2165
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2166
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2167
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2168
|
-
type: z.ZodLiteral<"command">;
|
|
2169
|
-
command: z.ZodString;
|
|
2170
|
-
shell: z.ZodOptional<z.ZodEnum<{
|
|
2171
|
-
bash: "bash";
|
|
2172
|
-
powershell: "powershell";
|
|
2173
|
-
}>>;
|
|
2174
|
-
async: z.ZodOptional<z.ZodBoolean>;
|
|
2175
|
-
asyncRewake: z.ZodOptional<z.ZodBoolean>;
|
|
2176
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
2177
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2178
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2179
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2180
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2181
|
-
type: z.ZodLiteral<"prompt">;
|
|
2182
|
-
prompt: z.ZodString;
|
|
2183
|
-
model: z.ZodOptional<z.ZodString>;
|
|
2184
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
2185
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2186
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2187
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2188
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2189
|
-
type: z.ZodLiteral<"agent">;
|
|
2190
|
-
prompt: z.ZodString;
|
|
2191
|
-
model: z.ZodOptional<z.ZodString>;
|
|
2192
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
2193
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2194
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2195
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2196
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2197
|
-
type: z.ZodLiteral<"http">;
|
|
2198
|
-
url: z.ZodString;
|
|
2199
|
-
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
2200
|
-
allowedEnvVars: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2201
|
-
}, z.core.$strip>], "type">;
|
|
2202
|
-
export type HookEntry = z.infer<typeof HookEntry>;
|
|
2203
|
-
export type CommandHookEntry = z.infer<typeof CommandHookEntry>;
|
|
2204
|
-
export type PromptHookEntry = z.infer<typeof PromptHookEntry>;
|
|
2205
|
-
export type AgentHookEntry = z.infer<typeof AgentHookEntry>;
|
|
2206
|
-
export type HttpHookEntry = z.infer<typeof HttpHookEntry>;
|
|
2207
|
-
/** One matcher group: `matcher` (see HOOK_EVENT_MATCHER_FIELD) + the hooks it fires. */
|
|
2208
|
-
export declare const HookMatcherEntry: z.ZodObject<{
|
|
2209
|
-
matcher: z.ZodOptional<z.ZodString>;
|
|
2210
|
-
hooks: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
2211
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2212
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2213
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2214
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2215
|
-
type: z.ZodLiteral<"command">;
|
|
2216
|
-
command: z.ZodString;
|
|
2217
|
-
shell: z.ZodOptional<z.ZodEnum<{
|
|
2218
|
-
bash: "bash";
|
|
2219
|
-
powershell: "powershell";
|
|
2220
|
-
}>>;
|
|
2221
|
-
async: z.ZodOptional<z.ZodBoolean>;
|
|
2222
|
-
asyncRewake: z.ZodOptional<z.ZodBoolean>;
|
|
2223
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
2224
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2225
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2226
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2227
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2228
|
-
type: z.ZodLiteral<"prompt">;
|
|
2229
|
-
prompt: z.ZodString;
|
|
2230
|
-
model: z.ZodOptional<z.ZodString>;
|
|
2231
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
2232
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2233
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2234
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2235
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2236
|
-
type: z.ZodLiteral<"agent">;
|
|
2237
|
-
prompt: z.ZodString;
|
|
2238
|
-
model: z.ZodOptional<z.ZodString>;
|
|
2239
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
2240
|
-
if: z.ZodOptional<z.ZodString>;
|
|
2241
|
-
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2242
|
-
statusMessage: z.ZodOptional<z.ZodString>;
|
|
2243
|
-
once: z.ZodOptional<z.ZodBoolean>;
|
|
2244
|
-
type: z.ZodLiteral<"http">;
|
|
2245
|
-
url: z.ZodString;
|
|
2246
|
-
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
2247
|
-
allowedEnvVars: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2248
|
-
}, z.core.$strip>], "type">>;
|
|
2249
|
-
}, z.core.$strip>;
|
|
2250
|
-
export type HookMatcherEntry = z.infer<typeof HookMatcherEntry>;
|
|
2251
2124
|
/**
|
|
2252
2125
|
* Does `matcher` select `name`? —— **the one reading** of a hooks matcher (11.0.0). The shell and the server both ask this
|
|
2253
2126
|
* function; neither keeps a copy.
|
|
@@ -2313,17 +2186,6 @@ export interface HookEnvInterpolation {
|
|
|
2313
2186
|
* Pure: no I/O, no logging, no global env —— `env` is the caller's view (`process.env` at the call site). Inputs are not modified.
|
|
2314
2187
|
*/
|
|
2315
2188
|
export declare function interpolateHookEnvVars(headers: Readonly<Record<string, string>>, allowedEnvVars: readonly string[], env: Readonly<Record<string, string | undefined>>): HookEnvInterpolation;
|
|
2316
|
-
/**
|
|
2317
|
-
* The hooks config record — the shape `TaskRequest.settings.hooks` carries on the wire and the shell
|
|
2318
|
-
* reads from settings JSON: `{ [eventName]: [{ matcher?, hooks: [...] }] }`. Keys limited to the 33
|
|
2319
|
-
* contract events (an unknown event name fails parse — catch typos at the wire); all keys optional.
|
|
2320
|
-
*
|
|
2321
|
-
* 🔴 The closed set is what makes a typo loud, and it is ALSO what makes falling behind CC expensive: an
|
|
2322
|
-
* event this package has not adopted yet takes the WHOLE settings file down with it, not just its own key.
|
|
2323
|
-
* That blast radius is why {@link HOOK_EVENTS} now has an upstream drift gate rather than a human promise.
|
|
2324
|
-
*/
|
|
2325
|
-
export type HooksConfig = Partial<Record<HookEventName, HookMatcherEntry[]>>;
|
|
2326
|
-
export declare const HooksConfig: z.ZodType<HooksConfig>;
|
|
2327
2189
|
/**
|
|
2328
2190
|
* DESIGN POINT ③ (slot semantics — RESERVED, core to finalize): how `spec.hooks` composes with the
|
|
2329
2191
|
* engine's own `deps.hooks` (e.g. the service's process-level toolTracer). "override" = the spec slot
|
package/dist/hooks.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* discovering it the hard way (the 1.3.0 / 1.6.0 / 1.8.0 / 2.0.0 pattern).
|
|
14
14
|
*
|
|
15
15
|
* CONSUMERS
|
|
16
|
-
* - service (the worker server): `TaskRequest.settings.hooks` wire face = `HooksConfig`
|
|
16
|
+
* - service (the worker server): `TaskRequest.settings.hooks` wire face = `HooksConfig` (`./hooks-config`); the worker-side
|
|
17
17
|
* hook-runner translates matching entries into core `TaskSpec.hooks` in-process callbacks. Single-user
|
|
18
18
|
* gate first (requirePrincipal semantics, same as MCP injection ).
|
|
19
19
|
* - core (engine): per-event input/output shapes for the engine-owned events; `stoppedBy` on settle.
|
|
@@ -26,58 +26,25 @@
|
|
|
26
26
|
* only from their owner; the shell must NOT double-fire engine-owned events. Configuring an event whose
|
|
27
27
|
* owner hasn't shipped its runner yet is LEGAL (no validation error) — it fires when that half ships.
|
|
28
28
|
*
|
|
29
|
+
* CONFIG SURFACE: what a settings document's `hooks` key may carry — the event vocabulary {@link HOOK_EVENTS}, the entry types, the ONE
|
|
30
|
+
* classifier `classifyHooksConfig` and the zod faces that read it (`HooksConfig` / `HookMatcherEntry` / `HookEntry` …) — is defined in
|
|
31
|
+
* `./hooks-config` and re-exported here whole; import it from here (or the main barrel) as before.
|
|
32
|
+
*
|
|
33
|
+
* WORKSPACE TRUST AND THE HOOKS EXECUTION GATE (contract; the gate executes in the client core, in one place — not in this package):
|
|
34
|
+
* when the host reports the workspace `declined` (`WORKSPACE_TRUST_STATES`), NO hook runs from ANY settings layer — the user, managed
|
|
35
|
+
* and flag layers included (CC 2.1.296: an untrusted session runs no hooks at all, `cli296.js:249597`). `no_trust_step` (the host has no
|
|
36
|
+
* trust step) differs from `declined` at this gate only: the gate stays open, and what applies is layer admission alone
|
|
37
|
+
* (`layerAdmission`: a repository-controllable layer's hooks are `trust_withheld` until the workspace is `accepted`). The gate is not
|
|
38
|
+
* layer admission: it lives in neither `SAFETY_MERGE_SPEC` nor `SETTINGS_SOURCE_CONTRACT`.
|
|
39
|
+
*
|
|
29
40
|
* Pure zod contract — zero runtime deps beyond zod, browser-safe, same as the rest of this package.
|
|
30
41
|
*/
|
|
31
42
|
import { z } from "zod";
|
|
43
|
+
import { HOOK_EVENTS } from "./hooks-config.js";
|
|
32
44
|
import { INTEGER } from "./integer.js";
|
|
33
|
-
|
|
45
|
+
export * from "./hooks-config.js";
|
|
34
46
|
// ── event set ──────────────────────────────────────────────────────────────────────────────────────────
|
|
35
|
-
// The
|
|
36
|
-
// reader of this file, not a contract: they are module-private, and the ONE exported closed set is {@link HOOK_EVENTS}.
|
|
37
|
-
/** CC 2.1.198 full hook-event set (jP array) — 27 events. Order matches the shell's HOOK_EVENTS. */
|
|
38
|
-
const CC_198_HOOK_EVENTS = [
|
|
39
|
-
"PreToolUse",
|
|
40
|
-
"PostToolUse",
|
|
41
|
-
"PostToolUseFailure",
|
|
42
|
-
"PostToolBatch",
|
|
43
|
-
"Notification",
|
|
44
|
-
"UserPromptSubmit",
|
|
45
|
-
"UserPromptExpansion",
|
|
46
|
-
"SessionStart",
|
|
47
|
-
"SessionEnd",
|
|
48
|
-
"Stop",
|
|
49
|
-
"StopFailure",
|
|
50
|
-
"SubagentStart",
|
|
51
|
-
"SubagentStop",
|
|
52
|
-
"PreCompact",
|
|
53
|
-
"PostCompact",
|
|
54
|
-
"PermissionRequest",
|
|
55
|
-
"PermissionDenied",
|
|
56
|
-
"Setup",
|
|
57
|
-
"TeammateIdle",
|
|
58
|
-
"TaskCreated",
|
|
59
|
-
"TaskCompleted",
|
|
60
|
-
"Elicitation",
|
|
61
|
-
"ElicitationResult",
|
|
62
|
-
"ConfigChange",
|
|
63
|
-
"WorktreeCreate",
|
|
64
|
-
"WorktreeRemove",
|
|
65
|
-
"InstructionsLoaded",
|
|
66
|
-
];
|
|
67
|
-
/**
|
|
68
|
-
* Post-CC-2.1.198 additions (sema superset; CC added them after 198).
|
|
69
|
-
* APPEND-ONLY: new members go at the tail (1.3.0 `DirectoryAdded`, CC 2.1.219 — CC's own tuple places it
|
|
70
|
-
* between FileChanged and MessageDisplay; the declaration order here is not normative, the enum is a set).
|
|
71
|
-
* 2.0.0 appends the model-switch pair (`PreModelSwitch` / `PostModelSwitch`, CC 2.1.259 — absent from the
|
|
72
|
-
* 2.1.250 corpus, so 250→259 is when they landed upstream).
|
|
73
|
-
*
|
|
74
|
-
* 🔴 This list is no longer maintained by hand-reading a CLI bundle: `test/upstream-drift.test.ts` fails
|
|
75
|
-
* (naming the missing words) whenever the CC anchor snapshot in `test/fixtures/cc-hook-events.json` holds an
|
|
76
|
-
* event this set does not, and that snapshot is produced by `scripts/extract-cc-hook-events.mjs`, never typed.
|
|
77
|
-
*/
|
|
78
|
-
const POST_198_HOOK_EVENTS = ["CwdChanged", "FileChanged", "MessageDisplay", "DirectoryAdded", "PreModelSwitch", "PostModelSwitch"];
|
|
79
|
-
/** The full 33-event contract set = CC 198's 27 + the 6 post-198 events — the one exported closed set of hook event names. */
|
|
80
|
-
export const HOOK_EVENTS = [...CC_198_HOOK_EVENTS, ...POST_198_HOOK_EVENTS];
|
|
47
|
+
// The ONE closed set of hook event names is {@link HOOK_EVENTS} (`./hooks-config`, re-exported above; drift-gated against CC).
|
|
81
48
|
export const HookEventName = z.enum(HOOK_EVENTS);
|
|
82
49
|
// ── ownership (owner-single) ─────────────────────────────────────────────────────────────────────
|
|
83
50
|
export const HookOwner = z.enum(["engine", "shell"]);
|
|
@@ -612,8 +579,8 @@ export const HookOutput = z.union([AsyncHookOutput, SyncHookOutput]);
|
|
|
612
579
|
// - env = the worker host process env, PLUS the injected names in HOOK_INJECTED_ENV below
|
|
613
580
|
// - timeout = per-entry `timeout` (seconds), else DEFAULT_HOOK_TIMEOUT_SECONDS
|
|
614
581
|
// - stdin = the HookInput JSON; stdout parsed as HookOutput (see exit-code semantics above)
|
|
615
|
-
// `prompt` / `agent` / `http` hook types are part of the config contract (
|
|
616
|
-
//
|
|
582
|
+
// `prompt` / `agent` / `http` / `mcp_tool` hook types are part of the config contract (`HOOK_ENTRY_TYPES`); a runner
|
|
583
|
+
// may phase them in after `command` — config validity is not phased (`classifyHooksConfig` is the one reading of it).
|
|
617
584
|
/** Env names the hook runner injects into a command hook's environment (CC-parity). */
|
|
618
585
|
export const HOOK_INJECTED_ENV = [
|
|
619
586
|
/** Absolute path of the project/workspace root. */
|
|
@@ -623,66 +590,6 @@ export const HOOK_INJECTED_ENV = [
|
|
|
623
590
|
];
|
|
624
591
|
/** Default per-hook timeout when an entry does not set one (seconds; CC default). */
|
|
625
592
|
export const DEFAULT_HOOK_TIMEOUT_SECONDS = 60;
|
|
626
|
-
/** A non-empty string with no NUL byte (spawn()/JSON-safe). */
|
|
627
|
-
const hookStr = z.string().min(1).refine((s) => !s.includes("\0"), buildQuotedIssue `must not contain a NUL byte`);
|
|
628
|
-
/**
|
|
629
|
-
* `if` condition — permission-rule syntax (e.g. "Bash(git *)") evaluated against tool_name/tool_input
|
|
630
|
-
* BEFORE spawning the hook (only meaningful on tool events). Shell superset field, CC-compatible.
|
|
631
|
-
*/
|
|
632
|
-
const IfCondition = z.string().optional();
|
|
633
|
-
const HookEntryCommon = {
|
|
634
|
-
if: IfCondition,
|
|
635
|
-
/** Timeout in seconds for this entry (default DEFAULT_HOOK_TIMEOUT_SECONDS). */
|
|
636
|
-
timeout: z.number().positive().optional(),
|
|
637
|
-
/** Spinner status message (shell superset). */
|
|
638
|
-
statusMessage: z.string().optional(),
|
|
639
|
-
/** Run once, then remove (shell superset). */
|
|
640
|
-
once: z.boolean().optional(),
|
|
641
|
-
};
|
|
642
|
-
/** `command` — shell command on the worker host (DESIGN POINT ②). First runner phase. */
|
|
643
|
-
export const CommandHookEntry = z.object({
|
|
644
|
-
type: z.literal("command"),
|
|
645
|
-
command: hookStr,
|
|
646
|
-
/** 'bash' = the user's $SHELL family; 'powershell' = pwsh. Default bash. */
|
|
647
|
-
shell: z.enum(["bash", "powershell"]).optional(),
|
|
648
|
-
/** Run in background without blocking (shell superset). */
|
|
649
|
-
async: z.boolean().optional(),
|
|
650
|
-
/** Background + wake the model on exit 2 (implies async; shell superset). */
|
|
651
|
-
asyncRewake: z.boolean().optional(),
|
|
652
|
-
...HookEntryCommon,
|
|
653
|
-
});
|
|
654
|
-
/** `prompt` — one-shot LLM evaluation of the hook input ($ARGUMENTS = input JSON). */
|
|
655
|
-
export const PromptHookEntry = z.object({
|
|
656
|
-
type: z.literal("prompt"),
|
|
657
|
-
prompt: hookStr,
|
|
658
|
-
model: z.string().optional(),
|
|
659
|
-
...HookEntryCommon,
|
|
660
|
-
});
|
|
661
|
-
/** `agent` — agentic verifier loop over the hook input ($ARGUMENTS = input JSON). */
|
|
662
|
-
export const AgentHookEntry = z.object({
|
|
663
|
-
type: z.literal("agent"),
|
|
664
|
-
prompt: hookStr,
|
|
665
|
-
model: z.string().optional(),
|
|
666
|
-
...HookEntryCommon,
|
|
667
|
-
});
|
|
668
|
-
/** `http` — POST the hook input JSON to a URL. Headers may reference $NAMEs listed in allowedEnvVars
|
|
669
|
-
* (env-NAME interpolation only — the config NEVER carries a secret value; same boundary as the rest
|
|
670
|
-
* of this package). */
|
|
671
|
-
export const HttpHookEntry = z.object({
|
|
672
|
-
type: z.literal("http"),
|
|
673
|
-
url: hookStr,
|
|
674
|
-
headers: z.record(z.string(), z.string()).optional(),
|
|
675
|
-
allowedEnvVars: z.array(z.string()).optional(),
|
|
676
|
-
...HookEntryCommon,
|
|
677
|
-
});
|
|
678
|
-
/** One configured hook (CC settings shape + shell superset fields; discriminated on `type`). */
|
|
679
|
-
export const HookEntry = z.discriminatedUnion("type", [CommandHookEntry, PromptHookEntry, AgentHookEntry, HttpHookEntry]);
|
|
680
|
-
/** One matcher group: `matcher` (see HOOK_EVENT_MATCHER_FIELD) + the hooks it fires. */
|
|
681
|
-
export const HookMatcherEntry = z.object({
|
|
682
|
-
/** Read by {@link hookMatcherSelects} — the one reading (name list or unanchored JS regex; empty/`*`/absent matches everything). */
|
|
683
|
-
matcher: z.string().optional(),
|
|
684
|
-
hooks: z.array(HookEntry),
|
|
685
|
-
});
|
|
686
593
|
/** The characters a matcher may consist of to be read as a NAME LIST rather than a regex. */
|
|
687
594
|
const HOOK_MATCHER_NAME_LIST = /^[a-zA-Z0-9_|, -]+$/;
|
|
688
595
|
function readHookMatcher(matcher) {
|
|
@@ -849,7 +756,6 @@ export function interpolateHookEnvVars(headers, allowedEnvVars, env) {
|
|
|
849
756
|
// fromEntries defines own properties: a header keyed `__proto__` stays a header instead of replacing the prototype.
|
|
850
757
|
return { headers: Object.fromEntries(entries), blanked };
|
|
851
758
|
}
|
|
852
|
-
export const HooksConfig = z.partialRecord(HookEventName, z.array(HookMatcherEntry));
|
|
853
759
|
/**
|
|
854
760
|
* DESIGN POINT ③ (slot semantics — RESERVED, core to finalize): how `spec.hooks` composes with the
|
|
855
761
|
* engine's own `deps.hooks` (e.g. the service's process-level toolTracer). "override" = the spec slot
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http hook 两张名单 `allowedHttpHookUrls` / `httpHookAllowedEnvVars`(13.0.0,U3 定稿 [10538] / S-1597)—— 类目 `httpHookAllowlists`
|
|
3
|
+
* (`SAFETY_MERGE_SPEC.httpHookAllowlists` 那一行是这两键的合并律与准入地板的唯一真源),本模块是它的读法与判决。纯函数,浏览器安全。
|
|
4
|
+
*
|
|
5
|
+
* CC 2.1.296 锚(cli296.js:237460 describe 原句):URL 名单「When set, HTTP hooks with non-matching URLs are blocked. If undefined, all URLs
|
|
6
|
+
* are allowed. If empty array, no HTTP hooks are allowed」;变量名单「each hook's effective allowedEnvVars is the intersection with this list.
|
|
7
|
+
* If undefined, no restriction」;坏形名单 ⇒「no HTTP hooks may run」/「no environment variables may be interpolated」(按空名单读)。
|
|
8
|
+
*
|
|
9
|
+
* 一条律:**每一张准入的名单都必须放行**(跨层取交)。准入只有一个读者:`layerAdmission(层的源, 工作区读数, "httpHookAllowlists") === "admitted"`
|
|
10
|
+
* (`./settings-sources`:层档取自源表 `SETTINGS_SOURCE_CONTRACT`,地板取自本类目行 `minTrust: "global"` ⇒ 用户 / flag / 托管层准入,项目 / 本地层
|
|
11
|
+
* 在地板之下)。本模块不推档,层对象只带源名 —— 调用方给不了档,也就抬不了档。两处与 CC 的差都在「下层不能放宽」方向(登 docs/CC-DIVERGENCE.md):
|
|
12
|
+
* ① CC 各层**拼接**(「Arrays merge across settings sources」)⇒ 下层可往托管名单里加 URL = 绕过管理员 ⇒ 本包取交;
|
|
13
|
+
* ② CC 任意层可设 ⇒ 本包只认准入层:项目 / 本地层(仓库可控)的一个 `[]` 会让用户自己的 http 守卫 hook 不发出 = 守卫失效(放行方向),
|
|
14
|
+
* 与 `sandboxNetwork.failIfUnavailable` 同一条边界;它们写的名单既不收窄也不放宽。
|
|
15
|
+
* 判官出结构不出句:放行 ⇒ `undefined`;拦下 ⇒ {@link HttpHookUrlBlock}(哪张名单 / 为什么 / 出自哪几层),用户面那一句由各宿主按它铸。
|
|
16
|
+
*/
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { type SettingsSource, type WorkspaceTrustState } from "./settings-sources.js";
|
|
19
|
+
/** 两键(闭集,声明序)。 */
|
|
20
|
+
export declare const HTTP_HOOK_ALLOWLIST_KEYS: readonly ["allowedHttpHookUrls", "httpHookAllowedEnvVars"];
|
|
21
|
+
export type HttpHookAllowlistKey = (typeof HTTP_HOOK_ALLOWLIST_KEYS)[number];
|
|
22
|
+
/**
|
|
23
|
+
* 整份 `settings.json` 里本片段负责的两键:在场则必须是字符串数组。刻意 `passthrough`(与 `CrossSessionSettings` 同姿势):本片段不是整份
|
|
24
|
+
* 设置的闭集门。它是**写侧**的形门;读侧坏形不拒整份,按 {@link readHttpHookAllowlistLayer} 读成空名单(fail-closed)。
|
|
25
|
+
*/
|
|
26
|
+
export declare const HttpHookAllowlistSettings: z.ZodObject<{
|
|
27
|
+
allowedHttpHookUrls: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
28
|
+
httpHookAllowedEnvVars: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
29
|
+
}, z.core.$loose>;
|
|
30
|
+
export type HttpHookAllowlistSettings = z.infer<typeof HttpHookAllowlistSettings>;
|
|
31
|
+
/**
|
|
32
|
+
* 一个设置层对两键的读数。键缺席 = 这一层不设限;`[]` = 这一层一个都不许。`source` = 这一层是哪个设置源(闭集 `SETTINGS_SOURCES`,原样回显进
|
|
33
|
+
* `sources`);准入由判官按源经 `layerAdmission` 判,调用方不预先筛层、不给档。
|
|
34
|
+
*/
|
|
35
|
+
export interface HttpHookAllowlistLayer {
|
|
36
|
+
readonly source: SettingsSource;
|
|
37
|
+
readonly allowedHttpHookUrls?: readonly string[];
|
|
38
|
+
readonly httpHookAllowedEnvVars?: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
/** 一层的读法结果:层读数 + 读成空名单的坏形键(调用方据此告警;读法本身不出句)。 */
|
|
41
|
+
export interface HttpHookAllowlistLayerRead {
|
|
42
|
+
readonly layer: HttpHookAllowlistLayer;
|
|
43
|
+
readonly malformed: readonly HttpHookAllowlistKey[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 从一层的原始设置对象读出两键(唯一读法)。键缺席(或值为 `undefined`)⇒ 不设;字符串数组 ⇒ 原样;其余一切(非数组、含非串条目、`null`)
|
|
47
|
+
* ⇒ **读成 `[]`**(CC 同向:坏形名单 = 一个都不许)并在 `malformed` 点名。整份设置不是对象 ⇒ 两键都缺。
|
|
48
|
+
*/
|
|
49
|
+
export declare function readHttpHookAllowlistLayer(source: SettingsSource, settings: unknown): HttpHookAllowlistLayerRead;
|
|
50
|
+
/** 拦下一只 http hook 的原因。`rule`:`empty` = 有一张准入名单是空的(那一层改对之前一只都不许);`url` = 没命中那几张名单里的任何模式。
|
|
51
|
+
* `sources` = 不放行它的那几张名单所在的层(入参序)。零 URL 回显 —— URL 调用方自己有,按受众给显示形。 */
|
|
52
|
+
export interface HttpHookUrlBlock {
|
|
53
|
+
readonly list: "allowedHttpHookUrls";
|
|
54
|
+
readonly rule: "empty" | "url";
|
|
55
|
+
readonly sources: readonly SettingsSource[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 一只 http hook 的 URL 过不过名单:每一张准入的 `allowedHttpHookUrls` 都要有一条模式按放行出口({@link urlAllowPatternMatches})命中它。
|
|
59
|
+
* 没有准入层写这张名单 ⇒ 不限(`undefined`)。`workspace` = 宿主对工作区信任的读数(读口抛或闭集外按 `declined` 交),与 `layerAdmission` 同一个入参。
|
|
60
|
+
*/
|
|
61
|
+
export declare function httpHookUrlVerdict(url: string, layers: readonly HttpHookAllowlistLayer[], workspace: WorkspaceTrustState): HttpHookUrlBlock | undefined;
|
|
62
|
+
/** 一只 http hook 实际可插值进请求头的环境变量:它自己的 `allowedEnvVars` 与每一张准入的 `httpHookAllowedEnvVars` 取交(保持 hook 自己的序)。 */
|
|
63
|
+
export declare function httpHookEnvVarsAllowed(hookVars: readonly string[], layers: readonly HttpHookAllowlistLayer[], workspace: WorkspaceTrustState): string[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http hook 两张名单 `allowedHttpHookUrls` / `httpHookAllowedEnvVars`(13.0.0,U3 定稿 [10538] / S-1597)—— 类目 `httpHookAllowlists`
|
|
3
|
+
* (`SAFETY_MERGE_SPEC.httpHookAllowlists` 那一行是这两键的合并律与准入地板的唯一真源),本模块是它的读法与判决。纯函数,浏览器安全。
|
|
4
|
+
*
|
|
5
|
+
* CC 2.1.296 锚(cli296.js:237460 describe 原句):URL 名单「When set, HTTP hooks with non-matching URLs are blocked. If undefined, all URLs
|
|
6
|
+
* are allowed. If empty array, no HTTP hooks are allowed」;变量名单「each hook's effective allowedEnvVars is the intersection with this list.
|
|
7
|
+
* If undefined, no restriction」;坏形名单 ⇒「no HTTP hooks may run」/「no environment variables may be interpolated」(按空名单读)。
|
|
8
|
+
*
|
|
9
|
+
* 一条律:**每一张准入的名单都必须放行**(跨层取交)。准入只有一个读者:`layerAdmission(层的源, 工作区读数, "httpHookAllowlists") === "admitted"`
|
|
10
|
+
* (`./settings-sources`:层档取自源表 `SETTINGS_SOURCE_CONTRACT`,地板取自本类目行 `minTrust: "global"` ⇒ 用户 / flag / 托管层准入,项目 / 本地层
|
|
11
|
+
* 在地板之下)。本模块不推档,层对象只带源名 —— 调用方给不了档,也就抬不了档。两处与 CC 的差都在「下层不能放宽」方向(登 docs/CC-DIVERGENCE.md):
|
|
12
|
+
* ① CC 各层**拼接**(「Arrays merge across settings sources」)⇒ 下层可往托管名单里加 URL = 绕过管理员 ⇒ 本包取交;
|
|
13
|
+
* ② CC 任意层可设 ⇒ 本包只认准入层:项目 / 本地层(仓库可控)的一个 `[]` 会让用户自己的 http 守卫 hook 不发出 = 守卫失效(放行方向),
|
|
14
|
+
* 与 `sandboxNetwork.failIfUnavailable` 同一条边界;它们写的名单既不收窄也不放宽。
|
|
15
|
+
* 判官出结构不出句:放行 ⇒ `undefined`;拦下 ⇒ {@link HttpHookUrlBlock}(哪张名单 / 为什么 / 出自哪几层),用户面那一句由各宿主按它铸。
|
|
16
|
+
*/
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { layerAdmission } from "./settings-sources.js";
|
|
19
|
+
import { urlAllowPatternMatches } from "./url-pattern.js";
|
|
20
|
+
/** 两键(闭集,声明序)。 */
|
|
21
|
+
export const HTTP_HOOK_ALLOWLIST_KEYS = ["allowedHttpHookUrls", "httpHookAllowedEnvVars"];
|
|
22
|
+
/**
|
|
23
|
+
* 整份 `settings.json` 里本片段负责的两键:在场则必须是字符串数组。刻意 `passthrough`(与 `CrossSessionSettings` 同姿势):本片段不是整份
|
|
24
|
+
* 设置的闭集门。它是**写侧**的形门;读侧坏形不拒整份,按 {@link readHttpHookAllowlistLayer} 读成空名单(fail-closed)。
|
|
25
|
+
*/
|
|
26
|
+
export const HttpHookAllowlistSettings = z
|
|
27
|
+
.object({
|
|
28
|
+
allowedHttpHookUrls: z.array(z.string()).optional(),
|
|
29
|
+
httpHookAllowedEnvVars: z.array(z.string()).optional(),
|
|
30
|
+
})
|
|
31
|
+
.passthrough();
|
|
32
|
+
/**
|
|
33
|
+
* 从一层的原始设置对象读出两键(唯一读法)。键缺席(或值为 `undefined`)⇒ 不设;字符串数组 ⇒ 原样;其余一切(非数组、含非串条目、`null`)
|
|
34
|
+
* ⇒ **读成 `[]`**(CC 同向:坏形名单 = 一个都不许)并在 `malformed` 点名。整份设置不是对象 ⇒ 两键都缺。
|
|
35
|
+
*/
|
|
36
|
+
export function readHttpHookAllowlistLayer(source, settings) {
|
|
37
|
+
const bag = typeof settings === "object" && settings !== null && !Array.isArray(settings) ? settings : {};
|
|
38
|
+
const malformed = [];
|
|
39
|
+
const lists = {};
|
|
40
|
+
for (const key of HTTP_HOOK_ALLOWLIST_KEYS) {
|
|
41
|
+
if (!Object.hasOwn(bag, key) || bag[key] === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
const v = bag[key];
|
|
44
|
+
if (Array.isArray(v) && v.every((x) => typeof x === "string"))
|
|
45
|
+
lists[key] = [...v];
|
|
46
|
+
else {
|
|
47
|
+
lists[key] = [];
|
|
48
|
+
malformed.push(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { layer: { source, ...lists }, malformed };
|
|
52
|
+
}
|
|
53
|
+
/** 进判决的那几层(入参序):`layerAdmission` 答 `admitted` 的层。每一层都问 —— 闭集外的源 / 工作区读数在这里响亮抛,哪怕那一层没写名单。 */
|
|
54
|
+
function admittedLayers(layers, workspace) {
|
|
55
|
+
return layers.filter((l) => layerAdmission(l.source, workspace, "httpHookAllowlists") === "admitted");
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 一只 http hook 的 URL 过不过名单:每一张准入的 `allowedHttpHookUrls` 都要有一条模式按放行出口({@link urlAllowPatternMatches})命中它。
|
|
59
|
+
* 没有准入层写这张名单 ⇒ 不限(`undefined`)。`workspace` = 宿主对工作区信任的读数(读口抛或闭集外按 `declined` 交),与 `layerAdmission` 同一个入参。
|
|
60
|
+
*/
|
|
61
|
+
export function httpHookUrlVerdict(url, layers, workspace) {
|
|
62
|
+
const refusing = admittedLayers(layers, workspace).filter((l) => l.allowedHttpHookUrls !== undefined && !l.allowedHttpHookUrls.some((p) => urlAllowPatternMatches(url, p)));
|
|
63
|
+
if (refusing.length === 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
return { list: "allowedHttpHookUrls", rule: refusing.some((l) => l.allowedHttpHookUrls.length === 0) ? "empty" : "url", sources: refusing.map((l) => l.source) };
|
|
66
|
+
}
|
|
67
|
+
/** 一只 http hook 实际可插值进请求头的环境变量:它自己的 `allowedEnvVars` 与每一张准入的 `httpHookAllowedEnvVars` 取交(保持 hook 自己的序)。 */
|
|
68
|
+
export function httpHookEnvVarsAllowed(hookVars, layers, workspace) {
|
|
69
|
+
const lists = admittedLayers(layers, workspace)
|
|
70
|
+
.filter((l) => l.httpHookAllowedEnvVars !== undefined)
|
|
71
|
+
.map((l) => l.httpHookAllowedEnvVars);
|
|
72
|
+
return hookVars.filter((v) => lists.every((list) => list.includes(v)));
|
|
73
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -31,3 +31,8 @@ export * from "./web-admin-env.js";
|
|
|
31
31
|
export * from "./web-admin-credential.js";
|
|
32
32
|
export * from "./web-admin-session-url.js";
|
|
33
33
|
export * from "./retired-envs.js";
|
|
34
|
+
export * from "./settings-sources.js";
|
|
35
|
+
export * from "./url-pattern.js";
|
|
36
|
+
export * from "./http-hook-allowlists.js";
|
|
37
|
+
export * from "./mcp-server-policy.js";
|
|
38
|
+
export * from "./ui-language.js";
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ export * from "./remote-exec.js";
|
|
|
19
19
|
export * from "./safety-merge-spec.js"; // #14 per-category safety merge-spec (settings resolver authority)
|
|
20
20
|
export * from "./scheduler-store.js"; // R7 self-wake scheduler store CONTRACT (pure: type + parse/serialize + SchedulerStore interface; fs binding in ./node)
|
|
21
21
|
export * from "./cross-session.js"; // CC-parity `settings.json` cross-session keys (crossSessionInbound / dialogExpiry) — pure/browser-safe
|
|
22
|
-
export * from "./hooks.js"; // CC-parity hooks contract (
|
|
22
|
+
export * from "./hooks.js"; // CC-parity hooks contract (33-event set + owner map + per-event input/output shapes + the config surface: classifyHooksConfig and its zod faces HooksConfig / HookEntry …) & stop attribution (stoppedBy) — pure/browser-safe
|
|
23
23
|
export * from "./fleet.js"; // scheduling/fleet contract (drain protocol · instance discovery · dispatch policy · quota lease · usage report · quota reset event) — CONTROL-PLANE-SCHEDULING-DESIGN, pure/browser-safe
|
|
24
24
|
export * from "./workers-rows.js"; // 11.0.0 the workers domain's per-row verdict (readWorkersRows: a row that does not read is held, the rest read on) — permanent read semantics, pure
|
|
25
25
|
export * from "./api/auth.js"; // /api/v1/auth/* wire contract (RFC 8628 device flow + rotating refresh + logout + approve) — REGISTRY-REBUILD-DESIGN §2, M1 shipped shapes frozen, pure/browser-safe
|
|
@@ -31,6 +31,11 @@ export * from "./web-admin-env.js"; // WEB_ADMIN_ENV — every env name an engin
|
|
|
31
31
|
export * from "./web-admin-credential.js"; // S-698 web-admin credential FILE contract: WebAdminCredentialEnvelope (user-session envelope, strict) + parseWebAdminCredentialFile (bare / envelope / unreadable — the one discrimination law) + judgeWebAdminCredentialDeclaration(env, credential) (pick-exactly-one over the credential's WEB_ADMIN_ENV pair; WEB_ADMIN_CREDENTIALS = access | assertion), pure/browser-safe
|
|
32
32
|
export * from "./web-admin-session-url.js"; // webAdminSessionUrl(webBaseUrl, { worker, sessionId }) — THE web-admin session link template (<webBaseUrl>/chat?agent=<worker>&session=<sessionId>) + parseWebAdminSessionUrl(link) its paired reader, pure/browser-safe
|
|
33
33
|
export * from "./retired-envs.js"; // the retired-env TOMBSTONE table (renamed / replaced / removed, closed set) + retiredEnvRefusal(env) (the one refusal sentence) + buildRetiredEnvWire() — engine boot, catalog wire, shell and web-admin read this one table, pure/browser-safe
|
|
34
|
+
export * from "./settings-sources.js"; // settings SOURCES (merge order) + SETTINGS_SOURCE_CONTRACT (trust tier, repository-controllable) + WORKSPACE_TRUST_STATES + layerAdmission(source, workspace, category, member?, value?) — the one layer × key admission reading, pure/browser-safe
|
|
35
|
+
export * from "./url-pattern.js"; // 13.0.0 (S-1597): THE URL-list pattern judge, two exits — urlAllowPatternMatches (⊆ upstream g9, four holes closed) / urlDenyPatternMatches (⊇ upstream ∪ whole-string glob), pure/browser-safe
|
|
36
|
+
export * from "./http-hook-allowlists.js"; // 13.0.0 (U3): allowedHttpHookUrls / httpHookAllowedEnvVars — one layer reader + httpHookUrlVerdict / httpHookEnvVarsAllowed (admitted layers INTERSECT; SAFETY_MERGE_SPEC.httpHookAllowlists), pure/browser-safe
|
|
37
|
+
export * from "./mcp-server-policy.js"; // 13.0.0 (U6): mcpServerPolicyVerdict → McpPolicyBlock{name, list, rule, target, written, sources} (structure, no sentence; target judged expanded, echoed only as written), pure/browser-safe
|
|
38
|
+
export * from "./ui-language.js"; // 13.0.0 (S-1592): the UI-language settings fragment (semaUiLanguage, en | zh, category ui, user settings) + readUiLanguage, pure/browser-safe
|
|
34
39
|
// NOTE: the Node-fs-backed helpers (FileConfigStore + the file editor) are NOT in this barrel — they pull
|
|
35
40
|
// `node:fs`/`node:path` and would break a browser/client bundle that only wants the contract (e.g.
|
|
36
41
|
// web-admin's client components import the zod types through here). Import them from the Node-only
|