@sema-agent/settings-schema 1.3.0 → 1.4.1

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.
@@ -14,7 +14,7 @@
14
14
  * admitted or rejected as one value under its `minTrust`. A resolver reads this table and has NO per-key
15
15
  * special-casing.
16
16
  *
17
- * MEMBER-LEVEL ADMISSION (1.1.0 — the WRITTEN layer of the permission-rule dual-channel ruling, board [5797] ④;
17
+ * MEMBER-LEVEL ADMISSION (1.1.0 — the WRITTEN layer of the permission-rule dual-channel ruling, [ref] ④;
18
18
  * the executing layer is core's `prepareCcImport` door + `RuleAddOrigin`, the resolver/shell halves are cli's):
19
19
  * A category may declare `members`, each with its own admission floor and DIRECTION. Today only `permissions`
20
20
  * does — `allow` / `deny` / `ask`, a CLOSED set ({@link SafetyMergeMember}; an unknown member is a compile error
@@ -39,7 +39,7 @@
39
39
  * requiresRealApproval). Consumers read admission through {@link memberAdmission} / {@link memberMinTrust} /
40
40
  * {@link membersOf} — never through a hardcoded allow/deny/ask table of their own (single source).
41
41
  *
42
- * TRUST ORDERING PRIMITIVES (1.2.0 — board [5796] ordering question → [5805] claim / [5807] consumer statement):
42
+ * TRUST ORDERING PRIMITIVES (1.2.0 — [ref] ordering question → claim / consumer statement):
43
43
  * The floor words are only meaningful against a LADDER, and that ladder is v2-design §1:29, verbatim:
44
44
  * `MANAGED 6 > GLOBAL 5 > PROJECT 4 > LOCAL 3 > SESSION 2 > TASK 1`. {@link trustRank} publishes exactly those
45
45
  * numbers and {@link isAtLeast} the comparison a resolver makes (`rank(layer) ≥ rank(floor)` ⇒ admitted), so no
@@ -83,16 +83,44 @@ export declare function isAtLeast(layer: MinTrust, minTrust: MinTrust): boolean;
83
83
  export declare const MemberDirection: z.ZodEnum<["loosen", "tighten"]>;
84
84
  export type MemberDirection = z.infer<typeof MemberDirection>;
85
85
  /** One member's admission row. `minTrust` ABSENT ⇒ the category's `minTrust` applies: a row can refine the floor
86
- * only by SAYING so; omission never lowers it (fail-closed by construction). */
86
+ * only by SAYING so; omission never lowers it (fail-closed by construction).
87
+ *
88
+ * `boolPole` (S-59 件八, 对抗复审 r3 [high]) — VALUE-DIRECTIONAL admission for a boolean member whose two poles
89
+ * point opposite ways (`enabled:true` narrows the sandbox, `enabled:false` opens it): a write EQUAL to
90
+ * `boolPole.strict` (strict equality, never truthiness — a string "true" is not the pole) is the tightening
91
+ * write and is admitted at `boolPole.minTrust`; ANY other write — the loose pole, a non-boolean, or a
92
+ * valueless query — is admitted at the ROW floor. 🔴 Vintage-safety law: a boolPole row's OWN `minTrust`
93
+ * must be the LOOSEN floor (the category's), so a consumer that predates this field (its schema strips
94
+ * `boolPole`) reads the row as tighten@<loosen-floor> and withholds BOTH poles from low-trust layers —
95
+ * fail-closed (it loses only the convenience of low-trust tightening, it can never admit a loosening write).
96
+ * Resolution is value-aware: pass the candidate value to {@link memberAdmission} / {@link memberMinTrust}. */
87
97
  export declare const MemberAdmission: z.ZodObject<{
88
98
  direction: z.ZodEnum<["loosen", "tighten"]>;
89
99
  minTrust: z.ZodOptional<z.ZodEnum<["local", "session", "project", "global", "managed", "task"]>>;
100
+ boolPole: z.ZodOptional<z.ZodObject<{
101
+ strict: z.ZodBoolean;
102
+ minTrust: z.ZodEnum<["local", "session", "project", "global", "managed", "task"]>;
103
+ }, "strip", z.ZodTypeAny, {
104
+ strict: boolean;
105
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
106
+ }, {
107
+ strict: boolean;
108
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
109
+ }>>;
90
110
  }, "strip", z.ZodTypeAny, {
91
111
  direction: "loosen" | "tighten";
92
112
  minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
113
+ boolPole?: {
114
+ strict: boolean;
115
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
116
+ } | undefined;
93
117
  }, {
94
118
  direction: "loosen" | "tighten";
95
119
  minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
120
+ boolPole?: {
121
+ strict: boolean;
122
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
123
+ } | undefined;
96
124
  }>;
97
125
  export type MemberAdmission = z.infer<typeof MemberAdmission>;
98
126
  /** The RESOLVED admission a consumer branches on — the category fallback already applied, so `minTrust` is never
@@ -100,6 +128,12 @@ export type MemberAdmission = z.infer<typeof MemberAdmission>;
100
128
  export interface ResolvedMemberAdmission {
101
129
  readonly direction: MemberDirection;
102
130
  readonly minTrust: MinTrust;
131
+ /** Present iff the row is value-directional; `minTrust` above already reflects the VALUE handed in (row floor
132
+ * when no value / not the strict pole). Exposed for presentation faces; enforcement should pass the value. */
133
+ readonly boolPole?: {
134
+ readonly strict: boolean;
135
+ readonly minTrust: MinTrust;
136
+ };
103
137
  }
104
138
  /** WHERE the category is enforced (STRENGTH) — MUST stay in lockstep with `EffectiveKey.enforcement` (types.ts).
105
139
  * gate=data-plane engine, server=control-plane/TOB boundary, resolver=merger correctness, client=UI only. */
@@ -202,12 +236,30 @@ export declare const MergeCategorySpec: z.ZodObject<{
202
236
  members: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
203
237
  direction: z.ZodEnum<["loosen", "tighten"]>;
204
238
  minTrust: z.ZodOptional<z.ZodEnum<["local", "session", "project", "global", "managed", "task"]>>;
239
+ boolPole: z.ZodOptional<z.ZodObject<{
240
+ strict: z.ZodBoolean;
241
+ minTrust: z.ZodEnum<["local", "session", "project", "global", "managed", "task"]>;
242
+ }, "strip", z.ZodTypeAny, {
243
+ strict: boolean;
244
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
245
+ }, {
246
+ strict: boolean;
247
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
248
+ }>>;
205
249
  }, "strip", z.ZodTypeAny, {
206
250
  direction: "loosen" | "tighten";
207
251
  minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
252
+ boolPole?: {
253
+ strict: boolean;
254
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
255
+ } | undefined;
208
256
  }, {
209
257
  direction: "loosen" | "tighten";
210
258
  minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
259
+ boolPole?: {
260
+ strict: boolean;
261
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
262
+ } | undefined;
211
263
  }>>>;
212
264
  }, "strip", z.ZodTypeAny, {
213
265
  enforcement: "gate" | "server" | "resolver" | "client";
@@ -231,6 +283,10 @@ export declare const MergeCategorySpec: z.ZodObject<{
231
283
  members?: Record<string, {
232
284
  direction: "loosen" | "tighten";
233
285
  minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
286
+ boolPole?: {
287
+ strict: boolean;
288
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
289
+ } | undefined;
234
290
  }> | undefined;
235
291
  coreAxis?: "permissionDecision" | "toolEffect" | "egress" | "irreversibility" | "safetyAxis" | "severity" | "shellGate" | undefined;
236
292
  }, {
@@ -255,6 +311,10 @@ export declare const MergeCategorySpec: z.ZodObject<{
255
311
  members?: Record<string, {
256
312
  direction: "loosen" | "tighten";
257
313
  minTrust?: "local" | "session" | "global" | "project" | "managed" | "task" | undefined;
314
+ boolPole?: {
315
+ strict: boolean;
316
+ minTrust: "local" | "session" | "global" | "project" | "managed" | "task";
317
+ } | undefined;
258
318
  }> | undefined;
259
319
  coreAxis?: "permissionDecision" | "toolEffect" | "egress" | "irreversibility" | "safetyAxis" | "severity" | "shellGate" | undefined;
260
320
  }>;
@@ -341,6 +401,88 @@ export declare const SAFETY_MERGE_SPEC: {
341
401
  readonly fail: "closed";
342
402
  readonly seam: "ExecutionEnv (fs+net) + sensitive-path";
343
403
  readonly coreAxis: "egress";
404
+ readonly members: {
405
+ readonly enabled: {
406
+ readonly direction: "tighten";
407
+ readonly minTrust: "global";
408
+ readonly boolPole: {
409
+ readonly strict: true;
410
+ readonly minTrust: "local";
411
+ };
412
+ };
413
+ readonly allowUnsandboxedCommands: {
414
+ readonly direction: "tighten";
415
+ readonly minTrust: "global";
416
+ readonly boolPole: {
417
+ readonly strict: false;
418
+ readonly minTrust: "local";
419
+ };
420
+ };
421
+ readonly autoAllowBashIfSandboxed: {
422
+ readonly direction: "tighten";
423
+ readonly minTrust: "global";
424
+ readonly boolPole: {
425
+ readonly strict: false;
426
+ readonly minTrust: "local";
427
+ };
428
+ };
429
+ readonly enableWeakerNestedSandbox: {
430
+ readonly direction: "tighten";
431
+ readonly minTrust: "global";
432
+ readonly boolPole: {
433
+ readonly strict: false;
434
+ readonly minTrust: "local";
435
+ };
436
+ };
437
+ readonly enableWeakerNetworkIsolation: {
438
+ readonly direction: "tighten";
439
+ readonly minTrust: "global";
440
+ readonly boolPole: {
441
+ readonly strict: false;
442
+ readonly minTrust: "local";
443
+ };
444
+ };
445
+ readonly "network.allowManagedDomainsOnly": {
446
+ readonly direction: "tighten";
447
+ readonly minTrust: "global";
448
+ readonly boolPole: {
449
+ readonly strict: true;
450
+ readonly minTrust: "local";
451
+ };
452
+ };
453
+ readonly "network.allowAllUnixSockets": {
454
+ readonly direction: "tighten";
455
+ readonly minTrust: "global";
456
+ readonly boolPole: {
457
+ readonly strict: false;
458
+ readonly minTrust: "local";
459
+ };
460
+ };
461
+ readonly "network.allowLocalBinding": {
462
+ readonly direction: "tighten";
463
+ readonly minTrust: "global";
464
+ readonly boolPole: {
465
+ readonly strict: false;
466
+ readonly minTrust: "local";
467
+ };
468
+ };
469
+ readonly "filesystem.allowManagedReadPathsOnly": {
470
+ readonly direction: "tighten";
471
+ readonly minTrust: "global";
472
+ readonly boolPole: {
473
+ readonly strict: true;
474
+ readonly minTrust: "local";
475
+ };
476
+ };
477
+ readonly "filesystem.denyWrite": {
478
+ readonly direction: "tighten";
479
+ readonly minTrust: "local";
480
+ };
481
+ readonly "filesystem.denyRead": {
482
+ readonly direction: "tighten";
483
+ readonly minTrust: "local";
484
+ };
485
+ };
344
486
  };
345
487
  readonly hooks: {
346
488
  readonly mergeShape: "runAll";
@@ -449,7 +591,7 @@ export type SafetyMergeCategory = keyof typeof SAFETY_MERGE_SPEC;
449
591
  * DISTRIBUTIVE over `C` on purpose: a resolver holds a WIDENED `SafetyMergeCategory` (out of its key→category
450
592
  * map), and a non-distributive form would index the whole union at once, fail the `members` check on the
451
593
  * whole-key categories and collapse to `never` — typing `membersOf(category)` as `readonly never[]` at exactly
452
- * the seam these helpers exist for (codex R2-F1). */
594
+ * the seam these helpers exist for (对抗复审 R2-F1). */
453
595
  export type SafetyMergeMember<C extends SafetyMergeCategory> = C extends SafetyMergeCategory ? (typeof SAFETY_MERGE_SPEC)[C] extends {
454
596
  readonly members: infer M;
455
597
  } ? Extract<keyof M, string> : never : never;
@@ -462,12 +604,16 @@ export declare function membersOf<C extends SafetyMergeCategory>(category: C): r
462
604
  * Fail-CLOSED and loud on both misuse arms — a category without `members` has no member to name, and a member
463
605
  * outside the declared set is a caller bug, not "use the category floor" (that silent arm is exactly what would
464
606
  * let a typo admit a bucket under the wrong rule).
607
+ * Value-aware (对抗复审 r3): pass the CANDIDATE VALUE being written for a value-directional (`boolPole`) member —
608
+ * the strict pole resolves to `boolPole.minTrust`, everything else (loose pole / non-boolean / no value) to the
609
+ * row floor. Rows without `boolPole` ignore the value entirely.
465
610
  * @param categoryLabel only decorates the error message (the spec value carries no name of its own).
466
611
  */
467
- export declare function resolveMemberAdmission(spec: MergeCategorySpec, member: string, categoryLabel?: string): ResolvedMemberAdmission;
612
+ export declare function resolveMemberAdmission(spec: MergeCategorySpec, member: string, categoryLabel?: string, value?: unknown): ResolvedMemberAdmission;
468
613
  /** A member's resolved admission (direction + floor, category fallback applied) from the published table.
469
- * Unknown category or member ⇒ RangeError (never a fallback). */
470
- export declare function memberAdmission<C extends SafetyMergeCategory>(category: C, member: SafetyMergeMember<C>): ResolvedMemberAdmission;
614
+ * Unknown category or member ⇒ RangeError (never a fallback). Value-aware: pass the candidate value for a
615
+ * `boolPole` member (see {@link resolveMemberAdmission}); omitting it answers the fail-closed row floor. */
616
+ export declare function memberAdmission<C extends SafetyMergeCategory>(category: C, member: SafetyMergeMember<C>, value?: unknown): ResolvedMemberAdmission;
471
617
  /** A member's admission FLOOR from the published table — the word a resolver compares the layer's trust against
472
618
  * (`isAtLeast(layer, floor)` ⇒ admitted; the ladder is {@link trustRank}). Convenience over {@link memberAdmission}. */
473
- export declare function memberMinTrust<C extends SafetyMergeCategory>(category: C, member: SafetyMergeMember<C>): MinTrust;
619
+ export declare function memberMinTrust<C extends SafetyMergeCategory>(category: C, member: SafetyMergeMember<C>, value?: unknown): MinTrust;
@@ -14,7 +14,7 @@
14
14
  * admitted or rejected as one value under its `minTrust`. A resolver reads this table and has NO per-key
15
15
  * special-casing.
16
16
  *
17
- * MEMBER-LEVEL ADMISSION (1.1.0 — the WRITTEN layer of the permission-rule dual-channel ruling, board [5797] ④;
17
+ * MEMBER-LEVEL ADMISSION (1.1.0 — the WRITTEN layer of the permission-rule dual-channel ruling, [ref] ④;
18
18
  * the executing layer is core's `prepareCcImport` door + `RuleAddOrigin`, the resolver/shell halves are cli's):
19
19
  * A category may declare `members`, each with its own admission floor and DIRECTION. Today only `permissions`
20
20
  * does — `allow` / `deny` / `ask`, a CLOSED set ({@link SafetyMergeMember}; an unknown member is a compile error
@@ -39,7 +39,7 @@
39
39
  * requiresRealApproval). Consumers read admission through {@link memberAdmission} / {@link memberMinTrust} /
40
40
  * {@link membersOf} — never through a hardcoded allow/deny/ask table of their own (single source).
41
41
  *
42
- * TRUST ORDERING PRIMITIVES (1.2.0 — board [5796] ordering question → [5805] claim / [5807] consumer statement):
42
+ * TRUST ORDERING PRIMITIVES (1.2.0 — [ref] ordering question → claim / consumer statement):
43
43
  * The floor words are only meaningful against a LADDER, and that ladder is v2-design §1:29, verbatim:
44
44
  * `MANAGED 6 > GLOBAL 5 > PROJECT 4 > LOCAL 3 > SESSION 2 > TASK 1`. {@link trustRank} publishes exactly those
45
45
  * numbers and {@link isAtLeast} the comparison a resolver makes (`rank(layer) ≥ rank(floor)` ⇒ admitted), so no
@@ -95,10 +95,21 @@ export function isAtLeast(layer, minTrust) {
95
95
  * contract. */
96
96
  export const MemberDirection = z.enum(["loosen", "tighten"]);
97
97
  /** One member's admission row. `minTrust` ABSENT ⇒ the category's `minTrust` applies: a row can refine the floor
98
- * only by SAYING so; omission never lowers it (fail-closed by construction). */
98
+ * only by SAYING so; omission never lowers it (fail-closed by construction).
99
+ *
100
+ * `boolPole` (S-59 件八, 对抗复审 r3 [high]) — VALUE-DIRECTIONAL admission for a boolean member whose two poles
101
+ * point opposite ways (`enabled:true` narrows the sandbox, `enabled:false` opens it): a write EQUAL to
102
+ * `boolPole.strict` (strict equality, never truthiness — a string "true" is not the pole) is the tightening
103
+ * write and is admitted at `boolPole.minTrust`; ANY other write — the loose pole, a non-boolean, or a
104
+ * valueless query — is admitted at the ROW floor. 🔴 Vintage-safety law: a boolPole row's OWN `minTrust`
105
+ * must be the LOOSEN floor (the category's), so a consumer that predates this field (its schema strips
106
+ * `boolPole`) reads the row as tighten@<loosen-floor> and withholds BOTH poles from low-trust layers —
107
+ * fail-closed (it loses only the convenience of low-trust tightening, it can never admit a loosening write).
108
+ * Resolution is value-aware: pass the candidate value to {@link memberAdmission} / {@link memberMinTrust}. */
99
109
  export const MemberAdmission = z.object({
100
110
  direction: MemberDirection,
101
111
  minTrust: MinTrust.optional(),
112
+ boolPole: z.object({ strict: z.boolean(), minTrust: MinTrust }).optional(),
102
113
  });
103
114
  /** WHERE the category is enforced (STRENGTH) — MUST stay in lockstep with `EffectiveKey.enforcement` (types.ts).
104
115
  * gate=data-plane engine, server=control-plane/TOB boundary, resolver=merger correctness, client=UI only. */
@@ -157,7 +168,56 @@ export const SAFETY_MERGE_SPEC = {
157
168
  handsReadOnly: { mergeShape: "replace", minTrust: "global", tighten: { rule: "boolStricter", strict: true }, enforcement: "gate", fail: "closed", seam: "handsReadOnly" },
158
169
  shellGate: { mergeShape: "replace", minTrust: "global", tighten: { rule: "enumRank", order: ["off", "classify", "always"] }, enforcement: "gate", fail: "closed", seam: "shellGate", coreAxis: "shellGate" },
159
170
  approver: { mergeShape: "replace", minTrust: "global", tighten: { rule: "none" }, enforcement: "gate", fail: "closed", seam: "onAsk (conflict → reject)" },
160
- sandboxNetwork: { mergeShape: "denyFirst", minTrust: "global", tighten: { rule: "setNarrower" }, enforcement: "gate", fail: "closed", seam: "ExecutionEnv (fs+net) + sensitive-path", coreAxis: "egress" },
171
+ // S-59 件八 member-level admission for the sandbox category (the shell's temporary "which sub-key counts
172
+ // as tightening" table lifts upstream; its gate keeps only the VALUE-pole half as executing detail).
173
+ // The members below are the CLOSED SET a low-trust settings layer may still set — restricting needs no trust,
174
+ // so each is tighten@"local" (the lowest settings-FILE tier, same law as permissions.deny/ask). Direction
175
+ // evidence, per member, from the shell's sandbox schema `.describe()` texts (its `SandboxSettingsSchema`):
176
+ // · enabled the sandbox being ON confines commands ⇒ setting `true` tightens (false = loosen pole);
177
+ // · allowUnsandboxedCommands `false` = the unsandboxed escape hatch is refused ⇒ tighten (true = loosen pole);
178
+ // · autoAllowBashIfSandboxed `false` = no auto-allow just for being sandboxed ⇒ tighten;
179
+ // · enableWeakerNestedSandbox `false` = the weaker nested sandbox stays off ⇒ tighten;
180
+ // · enableWeakerNetworkIsolation `false` = the "Reduces security" switch stays off ⇒ tighten;
181
+ // · network.allowManagedDomainsOnly `true` = only managed-layer domains honoured ⇒ tighten (its CONSUMER
182
+ // additionally reads policy settings only — that is the shell schema's own semantics, not admission law);
183
+ // · network.allowAllUnixSockets / network.allowLocalBinding `false` = the opening stays shut ⇒ tighten;
184
+ // · filesystem.allowManagedReadPathsOnly `true` ⇒ tighten (same managed-only consumer caveat);
185
+ // · filesystem.denyWrite / filesystem.denyRead — deny arrays only ever narrow ⇒ tighten in every write.
186
+ // A boolean member's admission is VALUE-directional and the pole is MODELED (对抗复审 r3 [high]): each boolean
187
+ // row carries `boolPole {strict, minTrust:"local"}` and keeps its ROW floor at the loosen floor "global" —
188
+ // value-aware consumers admit the strict pole at "local"; pole-blind consumers (whose schema strips boolPole)
189
+ // read tighten@global and withhold BOTH poles from low-trust layers (fail-closed: convenience lost, never a
190
+ // loosening write admitted). The two deny arrays narrow in every write, so they are plain tighten@"local".
191
+ // DELIBERATELY NOT members (⇒ the 1.1.0 header law: an undeclared member stays under the category minTrust
192
+ // "global" — exactly the consumer's fail-closed withhold arm, so FUTURE sub-keys are withheld by default):
193
+ // · the loosen face — network.allowedDomains / network.allowUnixSockets / filesystem.allowWrite /
194
+ // filesystem.allowRead (re-allow within denyRead = a loosener) / excludedCommands (named commands escape
195
+ // the sandbox) / ignoreViolations / ripgrep (binary override) — widening is a privilege;
196
+ // · failIfUnavailable — tightening on the security axis but its consequence is REFUSING TO START: admitted
197
+ // low, a cloned repo writing {enabled:true, failIfUnavailable:true} turns a "tighten" key into a
198
+ // denial-of-service primitive on any sandbox-less machine. An availability hard-gate is an operator's
199
+ // decision ⇒ it rides the loosen floor. ("Tightening is always safe" holds INSIDE this boundary — the
200
+ // boundary is written down rather than the sentence deleted.)
201
+ // · INDETERMINATE (flagged for the shell to confirm, fail-closed meanwhile): network.httpProxyPort /
202
+ // network.socksProxyPort (strictly routing, but they can steer sandboxed traffic to an arbitrary local
203
+ // port) and enabledPlatforms (platform-set edits cut both ways) — unlisted ⇒ category floor.
204
+ sandboxNetwork: {
205
+ mergeShape: "denyFirst", minTrust: "global", tighten: { rule: "setNarrower" }, enforcement: "gate", fail: "closed",
206
+ seam: "ExecutionEnv (fs+net) + sensitive-path", coreAxis: "egress",
207
+ members: {
208
+ enabled: { direction: "tighten", minTrust: "global", boolPole: { strict: true, minTrust: "local" } },
209
+ allowUnsandboxedCommands: { direction: "tighten", minTrust: "global", boolPole: { strict: false, minTrust: "local" } },
210
+ autoAllowBashIfSandboxed: { direction: "tighten", minTrust: "global", boolPole: { strict: false, minTrust: "local" } },
211
+ enableWeakerNestedSandbox: { direction: "tighten", minTrust: "global", boolPole: { strict: false, minTrust: "local" } },
212
+ enableWeakerNetworkIsolation: { direction: "tighten", minTrust: "global", boolPole: { strict: false, minTrust: "local" } },
213
+ "network.allowManagedDomainsOnly": { direction: "tighten", minTrust: "global", boolPole: { strict: true, minTrust: "local" } },
214
+ "network.allowAllUnixSockets": { direction: "tighten", minTrust: "global", boolPole: { strict: false, minTrust: "local" } },
215
+ "network.allowLocalBinding": { direction: "tighten", minTrust: "global", boolPole: { strict: false, minTrust: "local" } },
216
+ "filesystem.allowManagedReadPathsOnly": { direction: "tighten", minTrust: "global", boolPole: { strict: true, minTrust: "local" } },
217
+ "filesystem.denyWrite": { direction: "tighten", minTrust: "local" },
218
+ "filesystem.denyRead": { direction: "tighten", minTrust: "local" },
219
+ },
220
+ },
161
221
  hooks: { mergeShape: "runAll", minTrust: "project", tighten: { rule: "none" }, enforcement: "gate", fail: "closed", seam: "hooks (single source or adapter-composed)" },
162
222
  mcpServers: { mergeShape: "overrideByName", minTrust: "project", tighten: { rule: "none" }, enforcement: "resolver", fail: "open", seam: "spec.mcp + allowTools" },
163
223
  rulesInstructions: { mergeShape: "concat", minTrust: "project", tighten: { rule: "none" }, enforcement: "resolver", fail: "open", seam: "appendSystemPrompt / memory (GLOBAL→PROJECT→SESSION)" },
@@ -191,9 +251,12 @@ export function membersOf(category) {
191
251
  * Fail-CLOSED and loud on both misuse arms — a category without `members` has no member to name, and a member
192
252
  * outside the declared set is a caller bug, not "use the category floor" (that silent arm is exactly what would
193
253
  * let a typo admit a bucket under the wrong rule).
254
+ * Value-aware (对抗复审 r3): pass the CANDIDATE VALUE being written for a value-directional (`boolPole`) member —
255
+ * the strict pole resolves to `boolPole.minTrust`, everything else (loose pole / non-boolean / no value) to the
256
+ * row floor. Rows without `boolPole` ignore the value entirely.
194
257
  * @param categoryLabel only decorates the error message (the spec value carries no name of its own).
195
258
  */
196
- export function resolveMemberAdmission(spec, member, categoryLabel = "(unnamed category)") {
259
+ export function resolveMemberAdmission(spec, member, categoryLabel = "(unnamed category)", value) {
197
260
  if (spec.members === undefined) {
198
261
  throw new RangeError(`SAFETY_MERGE_SPEC: category '${categoryLabel}' has no member-level admission — its minTrust applies to the whole key`);
199
262
  }
@@ -201,15 +264,21 @@ export function resolveMemberAdmission(spec, member, categoryLabel = "(unnamed c
201
264
  throw new RangeError(`SAFETY_MERGE_SPEC: unknown member '${member}' of category '${categoryLabel}' (members: ${Object.keys(spec.members).join(", ")})`);
202
265
  }
203
266
  const row = spec.members[member];
204
- return { direction: row.direction, minTrust: row.minTrust ?? spec.minTrust };
267
+ const rowFloor = row.minTrust ?? spec.minTrust;
268
+ if (row.boolPole !== undefined) {
269
+ const floor = value === row.boolPole.strict ? row.boolPole.minTrust : rowFloor;
270
+ return { direction: row.direction, minTrust: floor, boolPole: row.boolPole };
271
+ }
272
+ return { direction: row.direction, minTrust: rowFloor };
205
273
  }
206
274
  /** A member's resolved admission (direction + floor, category fallback applied) from the published table.
207
- * Unknown category or member ⇒ RangeError (never a fallback). */
208
- export function memberAdmission(category, member) {
209
- return resolveMemberAdmission(categorySpec(category), member, category);
275
+ * Unknown category or member ⇒ RangeError (never a fallback). Value-aware: pass the candidate value for a
276
+ * `boolPole` member (see {@link resolveMemberAdmission}); omitting it answers the fail-closed row floor. */
277
+ export function memberAdmission(category, member, value) {
278
+ return resolveMemberAdmission(categorySpec(category), member, category, value);
210
279
  }
211
280
  /** A member's admission FLOOR from the published table — the word a resolver compares the layer's trust against
212
281
  * (`isAtLeast(layer, floor)` ⇒ admitted; the ladder is {@link trustRank}). Convenience over {@link memberAdmission}. */
213
- export function memberMinTrust(category, member) {
214
- return memberAdmission(category, member).minTrust;
282
+ export function memberMinTrust(category, member, value) {
283
+ return memberAdmission(category, member, value).minTrust;
215
284
  }
@@ -30,7 +30,7 @@ export type SchedulerStoreMutation = {
30
30
  reason: 'aborted';
31
31
  };
32
32
  /**
33
- * The ONE cross-process mutation primitive for the shared scheduler store (board [1001]①/[1003]②
33
+ * The ONE cross-process mutation primitive for the shared scheduler store ([ref]①/②
34
34
  * server L1-2 + shell 对抗评审 F1 双向合流后的契约层统一原语). Every WRITER (engine backend, resident
35
35
  * daemon fire/reap, session reap, sweeps) must go through this instead of hand-rolled
36
36
  * load→filter→save — the naked sequence is a lost-update race: two writers load the same base
@@ -47,7 +47,7 @@ export function fileSchedulerStore(path) {
47
47
  };
48
48
  }
49
49
  /**
50
- * The ONE cross-process mutation primitive for the shared scheduler store (board [1001]①/[1003]②
50
+ * The ONE cross-process mutation primitive for the shared scheduler store ([ref]①/②
51
51
  * server L1-2 + shell 对抗评审 F1 双向合流后的契约层统一原语). Every WRITER (engine backend, resident
52
52
  * daemon fire/reap, session reap, sweeps) must go through this instead of hand-rolled
53
53
  * load→filter→save — the naked sequence is a lost-update race: two writers load the same base
@@ -44,7 +44,7 @@ export function isSchedulerRecord(v) {
44
44
  * record must leave a trace reachable by whoever can act on it).
45
45
  */
46
46
  /**
47
- * core 5.5.0 durable 键分叉的迁移半场(board [2439]/[2440]/[2444];server 裁 (a) 一次性迁移,拒读侧
47
+ * core 5.5.0 durable 键分叉的迁移半场([ref];server 裁 (a) 一次性迁移,拒读侧
48
48
  * 双键容忍窗)。旧铸键链 `sessionId ?? principal ?? taskId ?? "default"` 让 durable 行的隔离键随会话
49
49
  * 走——新会话新 sessionId ⇒ 旧 durable job 永远 not_found。core 5.5.0 起 durable 铸键=
50
50
  * `principal ?? "default"`;存量旧键行由**本层**在 parse 时重铸(纯函数,幂等:重写后判据自不满足)。