@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.
- package/CHANGELOG.md +86 -2
- package/README.md +25 -11
- package/dist/api/auth-bridge.d.ts +1 -1
- package/dist/api/auth-bridge.js +1 -1
- package/dist/config-fns.d.ts +4 -4
- package/dist/config-fns.js +6 -6
- package/dist/file-edit.js +1 -1
- package/dist/file-store.d.ts +3 -3
- package/dist/file-store.js +6 -6
- package/dist/fleet.d.ts +1 -1
- package/dist/fleet.js +1 -1
- package/dist/hooks.d.ts +8 -3
- package/dist/hooks.js +8 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/local-load.d.ts +1 -1
- package/dist/remote-exec.d.ts +8 -8
- package/dist/remote-exec.js +8 -8
- package/dist/safety-merge-spec.d.ts +154 -8
- package/dist/safety-merge-spec.js +80 -11
- package/dist/scheduler-store-node.d.ts +1 -1
- package/dist/scheduler-store-node.js +1 -1
- package/dist/scheduler-store.js +1 -1
- package/dist/types.d.ts +342 -5
- package/dist/types.js +72 -23
- package/package.json +1 -1
|
@@ -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,
|
|
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 —
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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,
|
|
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 —
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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 (
|
|
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 (
|
|
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
|
package/dist/scheduler-store.js
CHANGED
|
@@ -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 键分叉的迁移半场(
|
|
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 时重铸(纯函数,幂等:重写后判据自不满足)。
|