@sema-agent/core 7.35.2 → 7.36.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/brain/anthropic.js +6 -6
  3. package/dist/brain/errors.d.ts +1 -14
  4. package/dist/brain/errors.js +2 -6
  5. package/dist/brain/open-responses.js +7 -7
  6. package/dist/brain/openai.js +7 -7
  7. package/dist/brain/provider-echo-shape.d.ts +26 -0
  8. package/dist/brain/provider-echo-shape.js +12 -0
  9. package/dist/brain/tool-call-id.d.ts +27 -15
  10. package/dist/brain/tool-call-id.js +43 -0
  11. package/dist/core/ask-question.d.ts +0 -30
  12. package/dist/core/ask-question.js +3 -2
  13. package/dist/core/checkpoint-store.d.ts +5 -3
  14. package/dist/core/checkpoint-store.js +2 -1
  15. package/dist/core/context-edit.d.ts +4 -0
  16. package/dist/core/context-edit.js +5 -2
  17. package/dist/core/engine-notice.d.ts +7 -0
  18. package/dist/core/gate-lanes.js +12 -10
  19. package/dist/core/governance-codes.d.ts +1 -1
  20. package/dist/core/governance-codes.js +2 -0
  21. package/dist/core/hooks.d.ts +3 -0
  22. package/dist/core/hooks.js +4 -2
  23. package/dist/core/mode-policy.js +2 -1
  24. package/dist/core/permission-rule-model.d.ts +2 -2
  25. package/dist/core/permission-rule-model.js +2 -2
  26. package/dist/core/permission-rules.js +2 -5
  27. package/dist/core/runner/active-skill-scope.js +5 -5
  28. package/dist/core/runner/decide-continuation.js +2 -1
  29. package/dist/core/runner/permission-rule-lanes.d.ts +3 -0
  30. package/dist/core/runner/permission-rule-lanes.js +7 -6
  31. package/dist/core/runner/prepare-context-lane.d.ts +4 -0
  32. package/dist/core/runner/prepare-context-lane.js +7 -5
  33. package/dist/core/runner/prepare-gate-stations.js +17 -12
  34. package/dist/core/runner/prepare-park-ask.d.ts +2 -0
  35. package/dist/core/runner/prepare-park-ask.js +7 -6
  36. package/dist/core/runner/prepare-policy-chain.d.ts +2 -0
  37. package/dist/core/runner/prepare-policy-chain.js +10 -9
  38. package/dist/core/runner/prepare-safety-scan.js +9 -2
  39. package/dist/core/runner/prepare-task.js +3 -3
  40. package/dist/core/runner/resume-apply.js +2 -1
  41. package/dist/core/runner/run-attachment-seats.js +3 -1
  42. package/dist/core/runner/run-harness-handlers.js +6 -1
  43. package/dist/core/runner/run-leg.js +11 -9
  44. package/dist/core/runner/run-recovery-lanes.js +2 -0
  45. package/dist/core/runner/session-rule-policy.js +5 -4
  46. package/dist/core/runner/tool-call-id-fold-seat.d.ts +13 -0
  47. package/dist/core/runner/tool-call-id-fold-seat.js +31 -0
  48. package/dist/core/sensitive-path-policy.js +2 -2
  49. package/dist/core/side-query.d.ts +5 -1
  50. package/dist/core/side-query.js +5 -1
  51. package/dist/core/tool-name-readings.d.ts +366 -0
  52. package/dist/core/tool-name-readings.js +136 -0
  53. package/dist/core/tool-policy.d.ts +5 -5
  54. package/dist/core/tool-policy.js +15 -13
  55. package/dist/core/tool-registry.d.ts +1 -31
  56. package/dist/core/tool-registry.js +2 -22
  57. package/dist/core/tool-result-budget.d.ts +3 -0
  58. package/dist/core/tool-result-budget.js +5 -3
  59. package/dist/core/tool-roster.d.ts +30 -9
  60. package/dist/core/tool-roster.js +45 -5
  61. package/dist/engine/llm/types.d.ts +4 -0
  62. package/dist/tools/fs/file-read-check.d.ts +1 -1
  63. package/dist/tools/fs/file-read-check.js +4 -3
  64. package/dist/tools/fs/shell-read-check.d.ts +15 -1
  65. package/dist/tools/fs/shell-read-check.js +5 -2
  66. package/package.json +2 -1
  67. package/wire-changes/core@7.36.0.json +288 -0
@@ -0,0 +1,366 @@
1
+ /**
2
+ * The ONE table of how every per-call site keyed on an engine tool name reads a call's names, and the read ports a
3
+ * site must go through. A call answers to several names once a caller tool declares aliases (the model's spelling,
4
+ * the wire name the gate resolves it to, the other aliases that resolve to the same row); which of them a site reads
5
+ * is a property of the SITE, declared here once:
6
+ *
7
+ * · `any` — a TIGHTEN keyed on an engine name set, or a rule name a user wrote: the call is held under EVERY name it
8
+ * answers to ({@link heldBy}); a set-valued table is read as the union of its entries ({@link heldValues}).
9
+ * · `seat` — a multi-key seat keyed on engine names: ONE name is chosen ({@link seatOf}) and every key of the seat is
10
+ * read under it. `tier` = the name whose irreversibility tier is strictest (`never < maybe < always`, an
11
+ * absent tier lowest, ties to the earlier name of the call); `member` = the first name the table holds.
12
+ * · `wire` — a GRANT or a ROUTING keyed on an engine name: the call's wire name only ({@link wireOf}).
13
+ * · `spelling` — a record of what the model said (stream frames, transcripts, counters): the harness event's own name;
14
+ * no port — listed so the table is complete.
15
+ *
16
+ * A site passes its {@link SiteId}; the port's parameter type admits only the ids of its kind, so moving a row to another
17
+ * kind fails to compile at the site. `gate:name-readings` holds the rest: a name-keyed table read outside a port, a call's
18
+ * name compared with an engine name outside {@link wireOf}, and a site id spelled at more than one place.
19
+ *
20
+ * Layer-0 vocabulary: depends on nothing but the tool declaration's tier type and, type-only, the mark-source word
21
+ * (`ToolMarkSource`); nothing here runs at import time.
22
+ */
23
+ import type { ToolSpec } from "./tool-spec.js";
24
+ export type NameReading = {
25
+ readonly kind: "any";
26
+ readonly file: string;
27
+ } | {
28
+ readonly kind: "wire";
29
+ readonly file: string;
30
+ } | {
31
+ readonly kind: "spelling";
32
+ readonly file: string;
33
+ } | {
34
+ readonly kind: "seat";
35
+ readonly rank: "tier" | "member";
36
+ readonly file: string;
37
+ };
38
+ export declare const NAME_READINGS: {
39
+ readonly "gate.compliance": {
40
+ readonly kind: "any";
41
+ readonly file: string;
42
+ };
43
+ readonly "gate.egress": {
44
+ readonly kind: "any";
45
+ readonly file: string;
46
+ };
47
+ readonly "gate.peerMessage": {
48
+ readonly kind: "any";
49
+ readonly file: string;
50
+ };
51
+ readonly "gate.markSources": {
52
+ readonly kind: "any";
53
+ readonly file: string;
54
+ };
55
+ readonly "gate.shellSeat": {
56
+ readonly kind: "seat";
57
+ readonly rank: "tier";
58
+ readonly file: string;
59
+ };
60
+ readonly "gate.sandboxBoundary.egress": {
61
+ readonly kind: "any";
62
+ readonly file: string;
63
+ };
64
+ readonly "gate.sandboxBoundary.hand": {
65
+ readonly kind: "wire";
66
+ readonly file: string;
67
+ };
68
+ readonly "gate.preToolUse": {
69
+ readonly kind: "wire";
70
+ readonly file: string;
71
+ };
72
+ readonly "fold.readStation": {
73
+ readonly kind: "seat";
74
+ readonly rank: "member";
75
+ readonly file: string;
76
+ };
77
+ readonly "lanes.contentQuestionClass": {
78
+ readonly kind: "any";
79
+ readonly file: string;
80
+ };
81
+ readonly "lanes.readOnlyShellAllow": {
82
+ readonly kind: "wire";
83
+ readonly file: string;
84
+ };
85
+ readonly "lanes.modeFileCommand": {
86
+ readonly kind: "wire";
87
+ readonly file: string;
88
+ };
89
+ readonly "lanes.contentAskRoute": {
90
+ readonly kind: "wire";
91
+ readonly file: string;
92
+ };
93
+ readonly "lanes.orgContentAskExemption": {
94
+ readonly kind: "wire";
95
+ readonly file: string;
96
+ };
97
+ readonly "lanes.persistedEditSkip": {
98
+ readonly kind: "wire";
99
+ readonly file: string;
100
+ };
101
+ readonly "lanes.persistedLaneSkip": {
102
+ readonly kind: "wire";
103
+ readonly file: string;
104
+ };
105
+ readonly "lanes.allowLayerQuestionSkip": {
106
+ readonly kind: "wire";
107
+ readonly file: string;
108
+ };
109
+ readonly "lanes.sandboxQuestionSkip": {
110
+ readonly kind: "wire";
111
+ readonly file: string;
112
+ };
113
+ readonly "policy.nameLists": {
114
+ readonly kind: "any";
115
+ readonly file: string;
116
+ };
117
+ readonly "policy.shellToolsList": {
118
+ readonly kind: "any";
119
+ readonly file: string;
120
+ };
121
+ readonly "policy.projectionReplay": {
122
+ readonly kind: "any";
123
+ readonly file: string;
124
+ };
125
+ readonly "policy.sessionRules": {
126
+ readonly kind: "any";
127
+ readonly file: string;
128
+ };
129
+ readonly "policy.sessionRules.effect": {
130
+ readonly kind: "wire";
131
+ readonly file: string;
132
+ };
133
+ readonly "policy.skillScope": {
134
+ readonly kind: "any";
135
+ readonly file: string;
136
+ };
137
+ readonly "policy.skillScope.effect": {
138
+ readonly kind: "wire";
139
+ readonly file: string;
140
+ };
141
+ readonly "policy.permissionRules": {
142
+ readonly kind: "any";
143
+ readonly file: string;
144
+ };
145
+ readonly "policy.persistedGrammar": {
146
+ readonly kind: "any";
147
+ readonly file: string;
148
+ };
149
+ readonly "policy.durableQuestion": {
150
+ readonly kind: "any";
151
+ readonly file: string;
152
+ };
153
+ readonly "policy.sensitivePathGuardedByName": {
154
+ readonly kind: "any";
155
+ readonly file: string;
156
+ };
157
+ readonly "floor.protectivePathTarget": {
158
+ readonly kind: "seat";
159
+ readonly rank: "member";
160
+ readonly file: string;
161
+ };
162
+ readonly "mode.planRosterRow": {
163
+ readonly kind: "wire";
164
+ readonly file: string;
165
+ };
166
+ readonly "offer.laneTool": {
167
+ readonly kind: "wire";
168
+ readonly file: string;
169
+ };
170
+ readonly "offer.orgQuestionExemption": {
171
+ readonly kind: "wire";
172
+ readonly file: string;
173
+ };
174
+ readonly "offer.marks": {
175
+ readonly kind: "seat";
176
+ readonly rank: "tier";
177
+ readonly file: string;
178
+ };
179
+ readonly "offer.marks.egress": {
180
+ readonly kind: "any";
181
+ readonly file: string;
182
+ };
183
+ readonly "park.riskDescriptor": {
184
+ readonly kind: "seat";
185
+ readonly rank: "tier";
186
+ readonly file: string;
187
+ };
188
+ readonly "park.syncFirstRoute": {
189
+ readonly kind: "wire";
190
+ readonly file: string;
191
+ };
192
+ readonly "report.riskAxes": {
193
+ readonly kind: "seat";
194
+ readonly rank: "tier";
195
+ readonly file: string;
196
+ };
197
+ readonly "report.riskAxes.egress": {
198
+ readonly kind: "any";
199
+ readonly file: string;
200
+ };
201
+ readonly "report.riskAxes.negatives": {
202
+ readonly kind: "seat";
203
+ readonly rank: "member";
204
+ readonly file: string;
205
+ };
206
+ readonly "verify.wroteThisRun": {
207
+ readonly kind: "wire";
208
+ readonly file: string;
209
+ };
210
+ readonly "verify.wroteThisRunSeed": {
211
+ readonly kind: "wire";
212
+ readonly file: string;
213
+ };
214
+ readonly "event.toolStartEnd": {
215
+ readonly kind: "spelling";
216
+ readonly file: string;
217
+ };
218
+ readonly "hook.postToolUse": {
219
+ readonly kind: "wire";
220
+ readonly file: string;
221
+ };
222
+ readonly "hook.postToolUseFailure": {
223
+ readonly kind: "wire";
224
+ readonly file: string;
225
+ };
226
+ readonly "hook.postToolBatch": {
227
+ readonly kind: "spelling";
228
+ readonly file: string;
229
+ };
230
+ readonly "stats.subagentToolCounter": {
231
+ readonly kind: "spelling";
232
+ readonly file: string;
233
+ };
234
+ readonly "activity.sink": {
235
+ readonly kind: "spelling";
236
+ readonly file: string;
237
+ };
238
+ readonly "reconcile.orphanEffect": {
239
+ readonly kind: "spelling";
240
+ readonly file: string;
241
+ };
242
+ readonly "inherited.questionFloat": {
243
+ readonly kind: "wire";
244
+ readonly file: string;
245
+ };
246
+ readonly "park.contentKindProjection": {
247
+ readonly kind: "wire";
248
+ readonly file: string;
249
+ };
250
+ readonly "resume.contentAskRow": {
251
+ readonly kind: "wire";
252
+ readonly file: string;
253
+ };
254
+ readonly "resume.editRecheckQuestionSkip": {
255
+ readonly kind: "wire";
256
+ readonly file: string;
257
+ };
258
+ readonly "resume.questionArgsShown": {
259
+ readonly kind: "wire";
260
+ readonly file: string;
261
+ };
262
+ readonly "budget.exemptResult": {
263
+ readonly kind: "wire";
264
+ readonly file: string;
265
+ };
266
+ readonly "compaction.compactableResult": {
267
+ readonly kind: "wire";
268
+ readonly file: string;
269
+ };
270
+ };
271
+ type Readings = typeof NAME_READINGS;
272
+ export type SiteId = keyof Readings;
273
+ export type AnySite = {
274
+ [K in SiteId]: Readings[K]["kind"] extends "any" ? K : never;
275
+ }[SiteId];
276
+ export type WireSite = {
277
+ [K in SiteId]: Readings[K]["kind"] extends "wire" ? K : never;
278
+ }[SiteId];
279
+ export type SpellingSite = {
280
+ [K in SiteId]: Readings[K]["kind"] extends "spelling" ? K : never;
281
+ }[SiteId];
282
+ export type TierSeatSite = {
283
+ [K in SiteId]: Readings[K] extends {
284
+ kind: "seat";
285
+ rank: "tier";
286
+ } ? K : never;
287
+ }[SiteId];
288
+ export type MemberSeatSite = {
289
+ [K in SiteId]: Readings[K] extends {
290
+ kind: "seat";
291
+ rank: "member";
292
+ } ? K : never;
293
+ }[SiteId];
294
+ export type SeatSite = TierSeatSite | MemberSeatSite;
295
+ /** A call as a site reads it: the wire name and, on a gated request, the face whose `names` lists every name it answers to. */
296
+ export interface NamedCall {
297
+ readonly toolName: string;
298
+ readonly face?: {
299
+ readonly names?: readonly string[];
300
+ } | undefined;
301
+ }
302
+ /**
303
+ * Every name the call answers to — the face's `names` when they include the call's name, else the call's name alone (a
304
+ * bare request, or a face minted for another name). A tool-name rule a host matches reaches the call iff its name is on
305
+ * this list; inside the engine only the ports below read it.
306
+ */
307
+ export declare function ruleNamesOf(call: NamedCall): readonly string[];
308
+ /** An `any` site's membership read: does `set` hold any of the call's names? */
309
+ export declare function heldBy(site: AnySite, set: {
310
+ has(name: string): boolean;
311
+ }, call: NamedCall): boolean;
312
+ /** An `any` site's value read: every entry `map` holds under any of the call's names, in the call's name order. */
313
+ export declare function heldValues<V>(site: AnySite, map: {
314
+ get(name: string): V | undefined;
315
+ }, call: NamedCall): V[];
316
+ /** A `wire` site's read: the call's wire name. */
317
+ export declare function wireOf(site: WireSite, call: {
318
+ readonly toolName: string;
319
+ }): string;
320
+ /** A tool's irreversibility tier, the one vocabulary `ToolSpec.irreversibility` declares. */
321
+ type IrreversibilityTierWord = NonNullable<ToolSpec["irreversibility"]>;
322
+ /** One seat, read under ONE name: the name the port chose, the value it holds there, and the seat's other keys under that name. */
323
+ export interface Seat<V> {
324
+ readonly name: string;
325
+ readonly value: V;
326
+ /** Another key of this seat, read under the seat's name. */
327
+ read<W>(table: {
328
+ get(name: string): W | undefined;
329
+ }): W | undefined;
330
+ /** Another key of this seat, a membership, read under the seat's name. */
331
+ holds(set: {
332
+ has(name: string): boolean;
333
+ }): boolean;
334
+ }
335
+ /** A tier seat: the risk seat of the engine's irreversibility tables. */
336
+ export interface TierSeat extends Seat<IrreversibilityTierWord> {
337
+ /**
338
+ * The seat's shell-seat provenance: the seat's name is in `shellGated` (the engine's shell seat is the sole positive
339
+ * source of its tier) AND no name the call answers to carries a deployment mark in `markSources` — a mark on any of the
340
+ * call's names outranks the shell seat's attribution.
341
+ */
342
+ shellGated(shellGated: {
343
+ has(name: string): boolean;
344
+ }, markSources: {
345
+ get(name: string): ReadonlySet<string> | undefined;
346
+ }): boolean;
347
+ }
348
+ /**
349
+ * A `seat` site's read. A `tier` seat takes the irreversibility tier table and picks the name whose tier is strictest;
350
+ * a `member` seat takes a table (a map, or a set whose value reads `true`) and picks the first name it holds.
351
+ */
352
+ export declare function seatOf(site: TierSeatSite, tiers: {
353
+ get(name: string): IrreversibilityTierWord | undefined;
354
+ }, call: NamedCall): TierSeat | undefined;
355
+ export declare function seatOf<V>(site: MemberSeatSite, table: {
356
+ get(name: string): V | undefined;
357
+ }, call: NamedCall): Seat<V> | undefined;
358
+ export declare function seatOf(site: MemberSeatSite, table: {
359
+ has(name: string): boolean;
360
+ }, call: NamedCall): Seat<true> | undefined;
361
+ export declare function seatOf<V>(site: MemberSeatSite, table: {
362
+ get(name: string): V | undefined;
363
+ } | {
364
+ has(name: string): boolean;
365
+ }, call: NamedCall): Seat<V | true> | undefined;
366
+ export {};
@@ -0,0 +1,136 @@
1
+ const GATE = "src/core/runner/prepare-gate-stations.ts";
2
+ const CHAIN = "src/core/runner/prepare-policy-chain.ts";
3
+ const LANES = "src/core/gate-lanes.ts";
4
+ const POLICY = "src/core/tool-policy.ts";
5
+ const OFFER = "src/core/runner/permission-rule-lanes.ts";
6
+ const PARK = "src/core/runner/prepare-park-ask.ts";
7
+ const HANDLERS = "src/core/runner/run-harness-handlers.ts";
8
+ const any = (file) => ({ kind: "any", file });
9
+ const wire = (file) => ({ kind: "wire", file });
10
+ const spelling = (file) => ({ kind: "spelling", file });
11
+ const seat = (rank, file) => ({ kind: "seat", rank, file });
12
+ export const NAME_READINGS = {
13
+ "gate.compliance": any(GATE),
14
+ "gate.egress": any(GATE),
15
+ "gate.peerMessage": any(GATE),
16
+ "gate.markSources": any(GATE),
17
+ "gate.shellSeat": seat("tier", GATE),
18
+ "gate.sandboxBoundary.egress": any(CHAIN),
19
+ "gate.sandboxBoundary.hand": wire(CHAIN),
20
+ "gate.preToolUse": wire("src/core/hooks.ts"),
21
+ "fold.readStation": seat("member", "src/tools/fs/shell-read-check.ts"),
22
+ "lanes.contentQuestionClass": any(LANES),
23
+ "lanes.readOnlyShellAllow": wire(LANES),
24
+ "lanes.modeFileCommand": wire(LANES),
25
+ "lanes.contentAskRoute": wire(LANES),
26
+ "lanes.orgContentAskExemption": wire(LANES),
27
+ "lanes.persistedEditSkip": wire(LANES),
28
+ "lanes.persistedLaneSkip": wire(LANES),
29
+ "lanes.allowLayerQuestionSkip": wire(LANES),
30
+ "lanes.sandboxQuestionSkip": wire(LANES),
31
+ "policy.nameLists": any(POLICY),
32
+ "policy.shellToolsList": any(POLICY),
33
+ "policy.projectionReplay": any(POLICY),
34
+ "policy.sessionRules": any("src/core/runner/session-rule-policy.ts"),
35
+ "policy.sessionRules.effect": wire("src/core/runner/session-rule-policy.ts"),
36
+ "policy.skillScope": any("src/core/runner/active-skill-scope.ts"),
37
+ "policy.skillScope.effect": wire("src/core/runner/active-skill-scope.ts"),
38
+ "policy.permissionRules": any("src/core/permission-rules.ts"),
39
+ "policy.persistedGrammar": any("src/core/permission-rule-model.ts"),
40
+ "policy.durableQuestion": any("src/core/ask-question.ts"),
41
+ "policy.sensitivePathGuardedByName": any("src/core/sensitive-path-policy.ts"),
42
+ "floor.protectivePathTarget": seat("member", "src/core/tool-registry.ts"),
43
+ "mode.planRosterRow": wire("src/core/mode-policy.ts"),
44
+ "offer.laneTool": wire(OFFER),
45
+ "offer.orgQuestionExemption": wire(OFFER),
46
+ "offer.marks": seat("tier", OFFER),
47
+ "offer.marks.egress": any(OFFER),
48
+ "park.riskDescriptor": seat("tier", PARK),
49
+ "park.syncFirstRoute": wire(PARK),
50
+ "report.riskAxes": seat("tier", CHAIN),
51
+ "report.riskAxes.egress": any(CHAIN),
52
+ "report.riskAxes.negatives": seat("member", CHAIN),
53
+ "verify.wroteThisRun": wire(HANDLERS),
54
+ "verify.wroteThisRunSeed": wire("src/core/runner/run-attachment-seats.ts"),
55
+ "event.toolStartEnd": spelling(HANDLERS),
56
+ "hook.postToolUse": wire(GATE),
57
+ "hook.postToolUseFailure": wire(GATE),
58
+ "hook.postToolBatch": spelling(HANDLERS),
59
+ "stats.subagentToolCounter": spelling("src/agents/subagent.ts"),
60
+ "activity.sink": spelling(HANDLERS),
61
+ "reconcile.orphanEffect": spelling("src/core/session-reconcile.ts"),
62
+ "inherited.questionFloat": wire(CHAIN),
63
+ "park.contentKindProjection": wire("src/core/checkpoint-store.ts"),
64
+ "resume.contentAskRow": wire("src/core/runner/decide-continuation.ts"),
65
+ "resume.editRecheckQuestionSkip": wire("src/core/runner/resume-apply.ts"),
66
+ "resume.questionArgsShown": wire("src/core/runner/run-leg.ts"),
67
+ "budget.exemptResult": wire("src/core/tool-result-budget.ts"),
68
+ "compaction.compactableResult": wire("src/core/context-edit.ts"),
69
+ };
70
+ export function ruleNamesOf(call) {
71
+ const names = call.face?.names;
72
+ return names !== undefined && names.includes(call.toolName) ? names : [call.toolName];
73
+ }
74
+ function readingOf(site, kind) {
75
+ const r = NAME_READINGS[site];
76
+ if (r.kind !== kind)
77
+ throw new Error(`tool-name reading: site ${JSON.stringify(site)} is a ${r.kind} site, read through the ${kind} port`);
78
+ return r;
79
+ }
80
+ export function heldBy(site, set, call) {
81
+ readingOf(site, "any");
82
+ return ruleNamesOf(call).some((n) => set.has(n));
83
+ }
84
+ export function heldValues(site, map, call) {
85
+ readingOf(site, "any");
86
+ const out = [];
87
+ for (const n of ruleNamesOf(call)) {
88
+ const v = map.get(n);
89
+ if (v !== undefined)
90
+ out.push(v);
91
+ }
92
+ return out;
93
+ }
94
+ export function wireOf(site, call) {
95
+ readingOf(site, "wire");
96
+ return call.toolName;
97
+ }
98
+ const DEPLOYMENT_MARK = "deployment";
99
+ const TIER_RANK = { never: 1, maybe: 2, always: 3 };
100
+ function seatAt(name, value) {
101
+ return {
102
+ name,
103
+ value,
104
+ read: (table) => table.get(name),
105
+ holds: (set) => set.has(name),
106
+ };
107
+ }
108
+ export function seatOf(site, table, call) {
109
+ const r = readingOf(site, "seat");
110
+ const names = ruleNamesOf(call);
111
+ const valueAt = (n) => (typeof table.get === "function" ? table.get(n) : table.has(n) ? true : undefined);
112
+ if (r.kind === "seat" && r.rank === "tier") {
113
+ let best;
114
+ for (const n of names) {
115
+ const v = valueAt(n);
116
+ if (v === undefined)
117
+ continue;
118
+ if (best === undefined || TIER_RANK[v] > TIER_RANK[best.value])
119
+ best = { name: n, value: v };
120
+ }
121
+ if (best === undefined)
122
+ return undefined;
123
+ const chosen = seatAt(best.name, best.value);
124
+ const tierSeat = {
125
+ ...chosen,
126
+ shellGated: (shellGated, markSources) => shellGated.has(chosen.name) && !names.some((n) => markSources.get(n)?.has(DEPLOYMENT_MARK) === true),
127
+ };
128
+ return tierSeat;
129
+ }
130
+ for (const n of names) {
131
+ const v = valueAt(n);
132
+ if (v !== undefined)
133
+ return seatAt(n, v);
134
+ }
135
+ return undefined;
136
+ }
@@ -4,10 +4,10 @@ import { type Settlement } from "./gate-outcome.js";
4
4
  /** A tool call presented to a policy before it executes. */
5
5
  /** design/388 B6 — the per-call face (see {@link ToolCallRequest.face}). */
6
6
  export interface ToolCallFace {
7
- /** Every name a tool-name rule may spell this tool by: the tool's wire name first, then each alias that resolves to
8
- * it on the leg's roster (`rosterToolName`). A rule reaches the call iff its tool name is on this list — read it
9
- * through {@link ruleNamesOf}, never off `toolName` alone. Absent on a face rebuilt from a park row that predates it
10
- * (the call's own name is then the list). */
7
+ /** Every name a tool-name rule may spell this tool by: the name the call was made under first (the model's spelling),
8
+ * then the tool's wire name and each other alias that resolves to it on the leg's roster (`rosterToolName`). A rule
9
+ * reaches the call iff its tool name is on this list — read it through `ruleNamesOf`, never off `toolName` alone.
10
+ * Absent on a face rebuilt from a park row that predates it (the call's own name is then the list). */
11
11
  names?: readonly string[];
12
12
  family: import("./tool-face.js").ToolFamily;
13
13
  pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
@@ -18,7 +18,7 @@ export interface ToolCallFace {
18
18
  }
19
19
  export interface ToolCallRequest {
20
20
  /** The tool's wire name — the Runner's gate resolves a call made under an alias to the wire name of the tool it
21
- * dispatches to before any policy runs (`rosterToolName`); the names a rule may use for it are {@link ruleNamesOf}. */
21
+ * dispatches to before any policy runs (`rosterToolName`); the names a rule may use for it are `ruleNamesOf`. */
22
22
  toolName: string;
23
23
  args: unknown;
24
24
  toolCallId: string;
@@ -8,7 +8,7 @@ import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
8
8
  import { join, normalize as normalizePath, sep } from "node:path";
9
9
  import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
10
10
  import { protectivePathTargetOf } from "./tool-registry.js";
11
- import { ruleNamesOf, toolNameSetHas } from "./tool-roster.js";
11
+ import { heldBy } from "./tool-name-readings.js";
12
12
  import { boundInputHashOf } from "./canonical-json.js";
13
13
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "./untrusted-text.js";
14
14
  import { describeRejectedValue } from "./tool-search-setting.js";
@@ -17,6 +17,8 @@ import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermission
17
17
  import { protocolOf } from "./protocol-table.js";
18
18
  import { isAbsoluteForFamily, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
19
19
  import { ASSIGNMENT_PREFIX, writtenAssignmentPrefix } from "../tools/fs/bash-program-position.js";
20
+ const nameListHolds = (set, req) => heldBy("policy.nameLists", set, req);
21
+ const shellToolsListHolds = (set, req) => heldBy("policy.shellToolsList", set, req);
20
22
  export function advertisedWritableRootsSeat(seat) {
21
23
  return seat === undefined ? {} : { advertisedWritableRoots: Object.freeze([...seat.roots]) };
22
24
  }
@@ -165,21 +167,21 @@ export function namespacedCoveringHit(covering, toolName) {
165
167
  return covering.length > 0 && covering.some((e) => namespacedRuleNameCovers(e, toolName));
166
168
  }
167
169
  export function checkToolPolicyProjection(projection, req) {
168
- const names = ruleNamesOf(req);
170
+ const listed = (list) => heldBy("policy.projectionReplay", { has: (n) => list.includes(n) }, req);
169
171
  for (const c of projection.components) {
170
172
  if (c.kind === "tool_deny") {
171
- if (names.some((n) => c.names.includes(n)) || namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
173
+ if (listed(c.names) || namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
172
174
  return { action: "deny", message: `tool "${req.toolName}" is denied by a frozen inherited policy projection` };
173
175
  }
174
176
  continue;
175
177
  }
176
178
  if (c.kind === "tool_allowlist") {
177
- if (!names.some((n) => c.names.includes(n)) && !namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
179
+ if (!listed(c.names) && !namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
178
180
  return { action: "deny", message: `tool "${req.toolName}" is not in a frozen inherited policy projection's allowlist` };
179
181
  }
180
182
  continue;
181
183
  }
182
- if (!names.some((n) => c.tools.includes(n)))
184
+ if (!listed(c.tools))
183
185
  continue;
184
186
  const command = req.args?.command;
185
187
  if (typeof command !== "string") {
@@ -388,10 +390,10 @@ export function createAllowDenyPolicy(opts) {
388
390
  nameSets: [{ ...(opts.allow ? { allow: [...opts.allow] } : {}), ...(opts.deny ? { deny: [...opts.deny] } : {}) }],
389
391
  check(req) {
390
392
  const toolName = req.toolName;
391
- if (toolNameSetHas(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
393
+ if (nameListHolds(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
392
394
  return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
393
395
  }
394
- if (allow && !toolNameSetHas(allow, req) && !namespacedCoveringHit(allowCovering, toolName)) {
396
+ if (allow && !nameListHolds(allow, req) && !namespacedCoveringHit(allowCovering, toolName)) {
395
397
  return { action: "deny", message: `tool "${req.toolName}" is not in the allowlist` };
396
398
  }
397
399
  return ALLOW;
@@ -428,10 +430,10 @@ export function createApprovalPolicy(opts) {
428
430
  ],
429
431
  async check(req, signal) {
430
432
  const toolName = req.toolName;
431
- if (toolNameSetHas(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
433
+ if (nameListHolds(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
432
434
  return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
433
435
  }
434
- if (toolNameSetHas(need, req) || namespacedCoveringHit(needCovering, toolName)) {
436
+ if (nameListHolds(need, req) || namespacedCoveringHit(needCovering, toolName)) {
435
437
  const attest = (d, settlement) => attestEngineSettlement(d, { settlement, origin: "policy" }, req);
436
438
  if (signal?.aborted) {
437
439
  return attest({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)` }, settledByNobody("task_aborted"));
@@ -481,7 +483,7 @@ export function createApprovalPolicy(opts) {
481
483
  }
482
484
  return attest({ action: "deny", message: `approval denied for "${req.toolName}"` }, settledByPerson("human_refused", undefined));
483
485
  }
484
- if (opts.denyByDefault && !toolNameSetHas(auto, req) && !namespacedCoveringHit(autoCovering, toolName)) {
486
+ if (opts.denyByDefault && !nameListHolds(auto, req) && !namespacedCoveringHit(autoCovering, toolName)) {
485
487
  return { action: "deny", message: `tool "${req.toolName}" requires explicit allow` };
486
488
  }
487
489
  return ALLOW;
@@ -621,7 +623,7 @@ export function createCoarseCommandNamePolicy(opts) {
621
623
  return brandPolicyAskClass({
622
624
  projection,
623
625
  check(req) {
624
- if (!toolNameSetHas(shellTools, req))
626
+ if (!shellToolsListHolds(shellTools, req))
625
627
  return ALLOW;
626
628
  const command = req.args?.command;
627
629
  if (typeof command !== "string") {
@@ -875,7 +877,7 @@ export function createUnverifiableDeletePolicy(opts) {
875
877
  const shellTools = canonicalToolNameSet(opts?.tools);
876
878
  return brandPolicyAskClass({
877
879
  check(req) {
878
- if (!toolNameSetHas(shellTools, req))
880
+ if (!shellToolsListHolds(shellTools, req))
879
881
  return ALLOW;
880
882
  const command = req.args?.command;
881
883
  if (typeof command !== "string")
@@ -972,7 +974,7 @@ export function createTranscriptIntegrityPolicy(opts) {
972
974
  });
973
975
  return brandPolicyAskClass({
974
976
  check(req) {
975
- if (toolNameSetHas(shellTools, req)) {
977
+ if (shellToolsListHolds(shellTools, req)) {
976
978
  const command = req.args?.command;
977
979
  if (typeof command !== "string")
978
980
  return ALLOW;
@@ -52,36 +52,6 @@ export interface ToolCallLike {
52
52
  } | undefined;
53
53
  }
54
54
  type CallPathTarget = ToolCallPathTarget;
55
- /**
56
- * The names a tool-name rule may spell this call's tool by — the face's `names` when they include the call's name,
57
- * else the call's name alone (a bare request, or a face minted for another name). Every name-keyed matcher and the
58
- * protective path floor read a rule's tool name against THIS list, which is the roster's one canonicalization seen
59
- * from the call's side: a rule written against an alias and one written against the wire name reach the same calls.
60
- */
61
- export declare function ruleNamesOf(req: Pick<ToolCallLike, "toolName" | "face">): readonly string[];
62
- /** Does the call answer to `name` — is it one of the call's names ({@link ruleNamesOf})? The engine's TIGHTENS keyed on
63
- * one of its own tool names read this, so a tool that wears the name under any of its names is tightened (the
64
- * fail-safe direction); a grant or a routing keyed on an engine tool name reads the wire name. */
65
- export declare function callAnswersTo(req: Pick<ToolCallLike, "toolName" | "face">, name: string): boolean;
66
- /** Does a set of rule tool names name this call's tool ({@link ruleNamesOf})? */
67
- export declare function toolNameSetHas(set: {
68
- has(name: string): boolean;
69
- }, req: Pick<ToolCallLike, "toolName" | "face">): boolean;
70
- /** Does `set` hold any of the call's names ({@link ruleNamesOf})? The read of a MEMBERSHIP tighten keyed on the engine's
71
- * own tool names (the egress set, a read station's tools, the compliance veto): a tool is held under every name it
72
- * answers to. A grant or a routing keyed on an engine tool name reads the wire name instead. */
73
- export declare function anyNamed(set: {
74
- has(name: string): boolean;
75
- }, names: readonly string[]): boolean;
76
- /** The first of the call's names ({@link ruleNamesOf}, the spelling first) that `map` holds an entry for, with the entry.
77
- * The read of a SEAT keyed on the engine's own tool names (a tier, a mark's sources): every key of one seat is read under
78
- * the ONE name this returns — a seat is never assembled from two names' entries. */
79
- export declare function firstNamed<V>(map: {
80
- get(name: string): V | undefined;
81
- }, names: readonly string[]): {
82
- readonly name: string;
83
- readonly value: V;
84
- } | undefined;
85
55
  /**
86
56
  * design/388 B6/B17 — the path target a PROTECTIVE fence reads (a deny-list judging a write's target: sensitive paths,
87
57
  * frozen paths, write protection, the transcript gate; edit tracking): the object face's own declaration first, the
@@ -89,7 +59,7 @@ export declare function firstNamed<V>(map: {
89
59
  * face that declares none. Reading the catalog for an undeclared same-name tool can only TIGHTEN a deny-list (it adds
90
60
  * a slot to judge — the pre-388 posture, never wider), which is exactly why this floor is wrong for a grant.
91
61
  *
92
- * The floor reads EVERY name the call answers to (`ruleNamesOf`: the face's `names`, else the call's name alone) in
62
+ * The floor is a `member` seat over every name the call answers to (the face's `names`, else the call's name alone) in
93
63
  * the face's order — the name the call was made under first, then the wire name, then the other aliases — and the
94
64
  * first name the catalog declares a target for wins: a call made under a catalog name and resolved to a tool that
95
65
  * declares none keeps THAT name's slot, also when the tool answers to a second catalog name with another slot.