@sema-agent/client-core 0.77.2 → 0.78.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/dist/index.js CHANGED
@@ -30,6 +30,7 @@ export * from './sessionPolicyCapability.js';
30
30
  export * from './memoryComplianceCapability.js';
31
31
  export * from './memoryOriginCapability.js';
32
32
  export * from './memoryEntriesWire.js';
33
+ export * from './memoryVerbsWire.js';
33
34
  export * from './deviceExecutorManagementCapability.js';
34
35
  export * from './mcpReconnect.js';
35
36
  export * from './leaderConflict.js';
@@ -77,7 +77,7 @@ export function mcpLivenessRollupOf(rows) {
77
77
  const epochLabel = (ms) => Math.abs(ms) <= 8.64e15 ? new Date(ms).toISOString() : String(ms);
78
78
  export function mcpEngineLegHealthDetail(health) {
79
79
  if (health === undefined)
80
- return 'mcp liveness not reported (no liveness observation is available to this client)';
80
+ return 'mcp liveness not reported (this client holds no liveness reading for this leg)';
81
81
  const unreadable = health.unreadableCells === true ? ' (one or more liveness records could not be read)' : '';
82
82
  const at = health.observedAt !== undefined ? `, last observed at ${epochLabel(health.observedAt)}` : '';
83
83
  switch (health.reading) {
@@ -91,6 +91,7 @@ export function mcpEngineLegHealthDetail(health) {
91
91
  }
92
92
  return `mcp liveness: observed on this leg, but what came back does not answer whether the server can be reached${at}${unreadable}`;
93
93
  default:
94
- return `mcp liveness not reported (no observation record on this leg's roster)${unreadable}`;
94
+ return 'mcp liveness not reported (no liveness observation is available to this client, and this client'
95
+ + ` cannot tell which cause applies)${unreadable}`;
95
96
  }
96
97
  }
package/dist/mcpPanel.js CHANGED
@@ -90,7 +90,7 @@ export function mcpPanelLastLegDetail(view, opts) {
90
90
  if (view.lastLegMcpUnreadable === true) {
91
91
  return 'last leg mcp unreadable (the engine sent a shape this client cannot read)';
92
92
  }
93
- return 'last leg mcp not reported (no leg in the retention window, no manifest on the last leg, or the engine predates it)';
93
+ return 'last leg mcp not reported (the panel carried no entry for the last leg; this client cannot tell which cause applies)';
94
94
  }
95
95
  const _mcpPanelShapePin = (p) => p;
96
96
  void _mcpPanelShapePin;
@@ -0,0 +1,106 @@
1
+ import type { EntryProvenanceAccount, MemoryErasureRequest, MemoryErasureAttestation, MemoryOriginClearRequest, MemoryOriginClearReceipt, MemoryOriginClearanceRow, MemoryOriginClearancesResult, MemoryOriginExternalEntry, MemoryOriginExternalResult } from '@sema-agent/sdk';
2
+ import type { MemoryComplianceReading } from './memoryComplianceCapability.js';
3
+ import type { MemoryOriginReading } from './memoryOriginCapability.js';
4
+ import type { MemoryEraseReceiptReading, MemoryOriginClearanceReading } from './memoryEntriesWire.js';
5
+ export interface MemoryFacade {
6
+ provenance(entryId: string, opts?: {
7
+ signal?: AbortSignal;
8
+ }): Promise<EntryProvenanceAccount>;
9
+ erase(body: MemoryErasureRequest, opts?: {
10
+ signal?: AbortSignal;
11
+ }): Promise<MemoryErasureAttestation>;
12
+ originExternal(scopes: string[], opts?: {
13
+ signal?: AbortSignal;
14
+ }): Promise<MemoryOriginExternalResult>;
15
+ originClearances(opts?: {
16
+ signal?: AbortSignal;
17
+ }): Promise<MemoryOriginClearancesResult>;
18
+ originClear(entryId: string, body: MemoryOriginClearRequest, opts?: {
19
+ signal?: AbortSignal;
20
+ }): Promise<MemoryOriginClearReceipt>;
21
+ }
22
+ export declare const MEMORY_VERB_REFUSAL_CAUSES: readonly ["face_absent", "client_too_old", "unreadable_request", "store_absent", "operator_only", "unauthorized", "service_token_required", "id_invalid", "request_id_required", "body_shape", "query_invalid", "selector_invalid", "evidence_unavailable", "selector_mismatch", "census_incomplete", "clear_unattributed", "clear_invalid", "clear_not_marked", "clear_challenged", "clear_conflict", "clear_pending"];
23
+ export type MemoryVerbRefusalCause = (typeof MEMORY_VERB_REFUSAL_CAUSES)[number];
24
+ export declare const MEMORY_VERB_UNKNOWN_WHYS: readonly ["no_verdict", "server_fault", "control_plane_corrupt", "clear_unknown", "clear_failed", "result_unreadable", "unsupported_version", "identity_mismatch", "unclassified"];
25
+ export type MemoryVerbUnknownWhy = (typeof MEMORY_VERB_UNKNOWN_WHYS)[number];
26
+ export type MemoryVerbRefused = {
27
+ status: 'refused';
28
+ cause: MemoryVerbRefusalCause;
29
+ message: string;
30
+ };
31
+ export type MemoryVerbUnknown = {
32
+ status: 'unknown';
33
+ why: MemoryVerbUnknownWhy;
34
+ message: string;
35
+ };
36
+ export type MemoryVerbFailure = MemoryVerbRefused | MemoryVerbUnknown;
37
+ export declare function classifyMemoryFailure(e: unknown): MemoryVerbFailure;
38
+ export interface MemoryProvenanceCall {
39
+ entryId: string;
40
+ capability: MemoryComplianceReading;
41
+ }
42
+ export type MemoryProvenanceOutcome = {
43
+ status: 'account';
44
+ account: EntryProvenanceAccount;
45
+ } | MemoryVerbRefused | MemoryVerbUnknown;
46
+ export declare function readEntryProvenance(facade: MemoryFacade, call: MemoryProvenanceCall, opts?: {
47
+ signal?: AbortSignal;
48
+ }): Promise<MemoryProvenanceOutcome>;
49
+ export interface MemoryEraseCall {
50
+ body: MemoryErasureRequest;
51
+ capability: MemoryComplianceReading;
52
+ }
53
+ export type MemoryEraseOutcome = {
54
+ status: 'attested';
55
+ receipt: Extract<MemoryEraseReceiptReading, {
56
+ kind: 'present';
57
+ }>;
58
+ } | MemoryVerbRefused | MemoryVerbUnknown;
59
+ export declare function eraseMemoryEntries(facade: MemoryFacade, call: MemoryEraseCall, opts?: {
60
+ signal?: AbortSignal;
61
+ }): Promise<MemoryEraseOutcome>;
62
+ export interface MemoryOriginExternalCall {
63
+ scopes: string[];
64
+ capability: MemoryOriginReading;
65
+ }
66
+ export type MemoryOriginExternalOutcome = {
67
+ status: 'listed';
68
+ scopes: string[];
69
+ entries: MemoryOriginExternalEntry[];
70
+ } | MemoryVerbRefused | MemoryVerbUnknown;
71
+ export declare function listExternalOriginEntries(facade: MemoryFacade, call: MemoryOriginExternalCall, opts?: {
72
+ signal?: AbortSignal;
73
+ }): Promise<MemoryOriginExternalOutcome>;
74
+ export declare function externalOriginCoverage(outcome: MemoryOriginExternalOutcome, asked: {
75
+ scopes: string[];
76
+ }): {
77
+ kind: 'audited';
78
+ scopes: string[];
79
+ } | {
80
+ kind: 'unknown';
81
+ why: 'not_listed' | 'coverage_shortfall';
82
+ };
83
+ export interface MemoryOriginClearancesCall {
84
+ capability: MemoryOriginReading;
85
+ }
86
+ export type MemoryOriginClearancesOutcome = {
87
+ status: 'ledger';
88
+ clearances: MemoryOriginClearanceRow[];
89
+ } | MemoryVerbRefused | MemoryVerbUnknown;
90
+ export declare function listOriginClearances(facade: MemoryFacade, call: MemoryOriginClearancesCall, opts?: {
91
+ signal?: AbortSignal;
92
+ }): Promise<MemoryOriginClearancesOutcome>;
93
+ export interface MemoryOriginClearCall {
94
+ entryId: string;
95
+ body: MemoryOriginClearRequest;
96
+ capability: MemoryOriginReading;
97
+ }
98
+ export type MemoryOriginClearOutcome = {
99
+ status: 'cleared';
100
+ receipt: Extract<MemoryOriginClearanceReading, {
101
+ kind: 'present';
102
+ }>;
103
+ } | MemoryVerbRefused | MemoryVerbUnknown;
104
+ export declare function clearEntryOrigin(facade: MemoryFacade, call: MemoryOriginClearCall, opts?: {
105
+ signal?: AbortSignal;
106
+ }): Promise<MemoryOriginClearOutcome>;
@@ -0,0 +1,423 @@
1
+ import { wireFailureShapeOf } from './wireFailureShape.js';
2
+ import { memoryComplianceVerbsAvailable } from './memoryComplianceCapability.js';
3
+ import { memoryOriginVerbsAvailable } from './memoryOriginCapability.js';
4
+ import { readMemoryEraseReceipt, readMemoryOriginClearanceReceipt } from './memoryEntriesWire.js';
5
+ export const MEMORY_VERB_REFUSAL_CAUSES = Object.freeze([
6
+ 'face_absent',
7
+ 'client_too_old',
8
+ 'unreadable_request',
9
+ 'store_absent',
10
+ 'operator_only',
11
+ 'unauthorized',
12
+ 'service_token_required',
13
+ 'id_invalid',
14
+ 'request_id_required',
15
+ 'body_shape',
16
+ 'query_invalid',
17
+ 'selector_invalid',
18
+ 'evidence_unavailable',
19
+ 'selector_mismatch',
20
+ 'census_incomplete',
21
+ 'clear_unattributed',
22
+ 'clear_invalid',
23
+ 'clear_not_marked',
24
+ 'clear_challenged',
25
+ 'clear_conflict',
26
+ 'clear_pending',
27
+ ]);
28
+ export const MEMORY_VERB_UNKNOWN_WHYS = Object.freeze([
29
+ 'no_verdict',
30
+ 'server_fault',
31
+ 'control_plane_corrupt',
32
+ 'clear_unknown',
33
+ 'clear_failed',
34
+ 'result_unreadable',
35
+ 'unsupported_version',
36
+ 'identity_mismatch',
37
+ 'unclassified',
38
+ ]);
39
+ function refusedBy(cause, message) {
40
+ return { status: 'refused', cause, message };
41
+ }
42
+ function unknownBy(why, message) {
43
+ return { status: 'unknown', why, message };
44
+ }
45
+ export function classifyMemoryFailure(e) {
46
+ const { status, errorCode, message } = wireFailureShapeOf(e);
47
+ if (errorCode === undefined || errorCode === '')
48
+ return unknownBy('no_verdict', message);
49
+ switch (errorCode) {
50
+ case 'auth.principal_required':
51
+ return refusedBy('unauthorized', message);
52
+ case 'auth.operator_only':
53
+ return refusedBy('operator_only', message);
54
+ case 'auth.service_token_required':
55
+ return refusedBy('service_token_required', message);
56
+ case 'capability.memory_engine_required':
57
+ return refusedBy('store_absent', message);
58
+ case 'request.id_invalid':
59
+ return refusedBy('id_invalid', message);
60
+ case 'request.request_id_required':
61
+ return refusedBy('request_id_required', message);
62
+ case 'request.body_shape':
63
+ return refusedBy('body_shape', message);
64
+ case 'request.query_invalid':
65
+ return refusedBy('query_invalid', message);
66
+ case 'config.memory_erasure_request':
67
+ return refusedBy('selector_invalid', message);
68
+ case 'memory.erasure_evidence_unavailable':
69
+ return refusedBy('evidence_unavailable', message);
70
+ case 'memory.erasure_selector_mismatch':
71
+ return refusedBy('selector_mismatch', message);
72
+ case 'memory.erasure_census_incomplete':
73
+ return refusedBy('census_incomplete', message);
74
+ case 'memory.origin_clear_unattributed':
75
+ return refusedBy('clear_unattributed', message);
76
+ case 'memory.origin_clear_invalid':
77
+ return refusedBy('clear_invalid', message);
78
+ case 'memory.origin_clear_not_marked':
79
+ return refusedBy('clear_not_marked', message);
80
+ case 'memory.origin_clear_challenged':
81
+ return refusedBy('clear_challenged', message);
82
+ case 'memory.origin_clear_conflict':
83
+ return refusedBy('clear_conflict', message);
84
+ case 'memory.origin_clear_pending':
85
+ return refusedBy('clear_pending', message);
86
+ case 'memory.origin_clear_unknown':
87
+ return unknownBy('clear_unknown', message);
88
+ case 'memory.origin_clear_failed':
89
+ return unknownBy('clear_failed', message);
90
+ case 'internal.memory_control_plane_corrupt':
91
+ return unknownBy('control_plane_corrupt', message);
92
+ default:
93
+ break;
94
+ }
95
+ if (errorCode.startsWith('auth.'))
96
+ return refusedBy('unauthorized', message);
97
+ if (status === undefined)
98
+ return unknownBy('no_verdict', message);
99
+ if (status >= 500)
100
+ return unknownBy('server_fault', message);
101
+ return unknownBy('unclassified', message);
102
+ }
103
+ function plainObject(v) {
104
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
105
+ }
106
+ function nonEmptyString(v) {
107
+ return typeof v === 'string' && v !== '';
108
+ }
109
+ const MAX_SCOPE_NAMES = 1_000;
110
+ function snapshotScopeNames(v) {
111
+ if (!Array.isArray(v))
112
+ return null;
113
+ let n;
114
+ try {
115
+ n = v.length;
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ if (typeof n !== 'number' || !Number.isSafeInteger(n) || n <= 0 || n > MAX_SCOPE_NAMES)
121
+ return null;
122
+ const out = [];
123
+ for (let i = 0; i < n; i++) {
124
+ let item;
125
+ try {
126
+ item = v[i];
127
+ }
128
+ catch {
129
+ return null;
130
+ }
131
+ if (!nonEmptyString(item))
132
+ return null;
133
+ out.push(item);
134
+ }
135
+ return out;
136
+ }
137
+ function gateRefusal(available, face) {
138
+ if (available !== 'no')
139
+ return undefined;
140
+ return refusedBy('face_absent', `this engine reports the ${face} as off on this deployment, so nothing was sent; the wire does not say which half is missing (a memory backend that does not own the engine control plane, or an empty operator roster), so do not guess which`);
141
+ }
142
+ const COMPLIANCE_FACE = 'memory compliance face (entry provenance and erasure)';
143
+ const ORIGIN_FACE = 'external-origin face (origin listing, clearance ledger and the un-mark valve)';
144
+ function readCapabilityKind(capability, bit) {
145
+ const kind = plainObject(capability) ? capability.kind : undefined;
146
+ if (!nonEmptyString(kind)) {
147
+ throw new TypeError(`memoryVerbsWire: \`capability\` (this call reading of capabilities.${bit}) is required — a default reading anchor would silently answer ` +
148
+ 'for whichever engine happens to be installed, and this step decides whether a request goes on the wire at all');
149
+ }
150
+ return kind;
151
+ }
152
+ function complianceGate(capability) {
153
+ const kind = readCapabilityKind(capability, 'memoryCompliance');
154
+ return gateRefusal(memoryComplianceVerbsAvailable({ kind }), COMPLIANCE_FACE);
155
+ }
156
+ function originGate(capability) {
157
+ const kind = readCapabilityKind(capability, 'memoryOrigin');
158
+ return gateRefusal(memoryOriginVerbsAvailable({ kind }), ORIGIN_FACE);
159
+ }
160
+ const PROVENANCE_ACCOUNT_VERSION = 1;
161
+ function narrowProvenanceAccount(body, entryId) {
162
+ let v;
163
+ let id;
164
+ let binding;
165
+ let custody;
166
+ let contributors;
167
+ let contentState;
168
+ let hasExclusion;
169
+ let exclusion;
170
+ let bindingState;
171
+ let custodyState;
172
+ let custodyEvents;
173
+ let exclusionCode;
174
+ try {
175
+ if (!plainObject(body))
176
+ return { ok: false, why: 'result_unreadable' };
177
+ v = body.v;
178
+ id = body.id;
179
+ binding = body.binding;
180
+ custody = body.custody;
181
+ contributors = body.contributors;
182
+ contentState = body.contentState;
183
+ hasExclusion = Object.hasOwn(body, 'exclusion');
184
+ exclusion = hasExclusion ? body.exclusion : undefined;
185
+ bindingState = plainObject(binding) ? binding.state : undefined;
186
+ custodyState = plainObject(custody) ? custody.state : undefined;
187
+ custodyEvents = plainObject(custody) ? custody.events : undefined;
188
+ exclusionCode = plainObject(exclusion) ? exclusion.code : undefined;
189
+ }
190
+ catch {
191
+ return { ok: false, why: 'result_unreadable' };
192
+ }
193
+ if (typeof v !== 'number' || !Number.isFinite(v))
194
+ return { ok: false, why: 'result_unreadable' };
195
+ if (v !== PROVENANCE_ACCOUNT_VERSION)
196
+ return { ok: false, why: 'unsupported_version' };
197
+ if (!nonEmptyString(id))
198
+ return { ok: false, why: 'result_unreadable' };
199
+ if (!plainObject(binding) || !nonEmptyString(bindingState))
200
+ return { ok: false, why: 'result_unreadable' };
201
+ if (!plainObject(custody) || !nonEmptyString(custodyState) || !Array.isArray(custodyEvents)) {
202
+ return { ok: false, why: 'result_unreadable' };
203
+ }
204
+ if (!Array.isArray(contributors) || !nonEmptyString(contentState)) {
205
+ return { ok: false, why: 'result_unreadable' };
206
+ }
207
+ if (hasExclusion && !(plainObject(exclusion) && nonEmptyString(exclusionCode))) {
208
+ return { ok: false, why: 'result_unreadable' };
209
+ }
210
+ if (id !== entryId)
211
+ return { ok: false, why: 'identity_mismatch' };
212
+ return { ok: true, account: body };
213
+ }
214
+ export async function readEntryProvenance(facade, call, opts) {
215
+ if (typeof facade?.provenance !== 'function') {
216
+ return refusedBy('client_too_old', 'this client has no entry-provenance verb — nothing was sent; upgrade the client that builds the memory port');
217
+ }
218
+ const gate = complianceGate(call?.capability);
219
+ if (gate !== undefined)
220
+ return gate;
221
+ const entryId = call?.entryId;
222
+ if (!nonEmptyString(entryId)) {
223
+ return refusedBy('unreadable_request', 'an entry provenance question needs an entry id — addressing by an absent id matches nothing, so nothing was sent');
224
+ }
225
+ let res;
226
+ try {
227
+ res = await facade.provenance(entryId, opts);
228
+ }
229
+ catch (e) {
230
+ return classifyMemoryFailure(e);
231
+ }
232
+ const narrowed = narrowProvenanceAccount(res, entryId);
233
+ if (!narrowed.ok) {
234
+ return unknownBy(narrowed.why, narrowed.why === 'unsupported_version'
235
+ ? 'the provenance account came back stamped with a newer envelope version than this client knows — refusing to reinterpret it; upgrade the client'
236
+ : narrowed.why === 'identity_mismatch'
237
+ ? 'the provenance account came back for a different entry id than the one that was asked about — refusing to present it as this entry account'
238
+ : 'the provenance call answered 2xx with a body this client cannot branch on (envelope, entry id, binding or custody) — refusing to present it as an account');
239
+ }
240
+ return { status: 'account', account: narrowed.account };
241
+ }
242
+ export async function eraseMemoryEntries(facade, call, opts) {
243
+ if (typeof facade?.erase !== 'function') {
244
+ return refusedBy('client_too_old', 'this client has no compliance-erasure verb — nothing was sent, so nothing was erased; upgrade the client that builds the memory port');
245
+ }
246
+ const gate = complianceGate(call?.capability);
247
+ if (gate !== undefined)
248
+ return gate;
249
+ const body = call?.body;
250
+ let requestId;
251
+ let select;
252
+ try {
253
+ if (!plainObject(body)) {
254
+ return refusedBy('unreadable_request', 'an erasure request needs a request body — refusing to mint one out of nothing, so nothing was sent');
255
+ }
256
+ requestId = body.requestId;
257
+ select = body.select;
258
+ }
259
+ catch {
260
+ return refusedBy('unreadable_request', 'the erasure request body could not be inspected — nothing was sent');
261
+ }
262
+ if (!nonEmptyString(requestId)) {
263
+ return refusedBy('unreadable_request', 'an erasure request needs a stable request id of its own minting — it is this request idempotency identity and a retry must carry the same one, so nothing was sent');
264
+ }
265
+ if (!plainObject(select)) {
266
+ return refusedBy('unreadable_request', 'an erasure request needs a selector object — which selector is valid is the engine judgement, but an absent one addresses nothing, so nothing was sent');
267
+ }
268
+ let res;
269
+ try {
270
+ res = await facade.erase(call.body, opts);
271
+ }
272
+ catch (e) {
273
+ return classifyMemoryFailure(e);
274
+ }
275
+ const reading = readMemoryEraseReceipt(res);
276
+ if (reading.kind !== 'present') {
277
+ return unknownBy('result_unreadable', `the erasure call answered 2xx with an attestation this client cannot read (${reading.why}) — refusing to report the erasure as attested; re-read the attestation by its request id to reconcile`);
278
+ }
279
+ if (reading.requestId !== requestId) {
280
+ return unknownBy('identity_mismatch', 'the erasure attestation came back under a different request id than the one that was sent — refusing to present it as the outcome of this call');
281
+ }
282
+ return { status: 'attested', receipt: reading };
283
+ }
284
+ export async function listExternalOriginEntries(facade, call, opts) {
285
+ if (typeof facade?.originExternal !== 'function') {
286
+ return refusedBy('client_too_old', 'this client has no external-origin listing verb — nothing was sent; upgrade the client that builds the memory port');
287
+ }
288
+ const gate = originGate(call?.capability);
289
+ if (gate !== undefined)
290
+ return gate;
291
+ let asked;
292
+ try {
293
+ asked = snapshotScopeNames(call?.scopes);
294
+ }
295
+ catch {
296
+ asked = null;
297
+ }
298
+ if (asked === null) {
299
+ return refusedBy('unreadable_request', 'an external-origin listing needs a readable, non-empty list of scope keys — this engine generation has no scope-enumeration read face, so the list comes from the caller and an unreadable one is not sent at all');
300
+ }
301
+ let res;
302
+ try {
303
+ res = await facade.originExternal(asked, opts);
304
+ }
305
+ catch (e) {
306
+ return classifyMemoryFailure(e);
307
+ }
308
+ let rawScopes;
309
+ let rawEntries;
310
+ try {
311
+ if (!plainObject(res)) {
312
+ return unknownBy('result_unreadable', 'the external-origin listing answered 2xx with a body this client cannot read — refusing to present it as a listing');
313
+ }
314
+ rawScopes = res.scopes;
315
+ rawEntries = res.entries;
316
+ }
317
+ catch {
318
+ return unknownBy('result_unreadable', 'the external-origin listing answered 2xx with a body this client cannot read — refusing to present it as a listing');
319
+ }
320
+ let audited;
321
+ try {
322
+ audited = snapshotScopeNames(rawScopes);
323
+ }
324
+ catch {
325
+ audited = null;
326
+ }
327
+ if (audited === null || !Array.isArray(rawEntries)) {
328
+ return unknownBy('result_unreadable', 'the external-origin listing answered 2xx without a readable audited-scope echo or entry list — refusing to present a listing whose coverage cannot be stated');
329
+ }
330
+ return { status: 'listed', scopes: audited, entries: rawEntries };
331
+ }
332
+ export function externalOriginCoverage(outcome, asked) {
333
+ if (asked === null || typeof asked !== 'object' || !Array.isArray(asked.scopes)) {
334
+ throw new TypeError('externalOriginCoverage: second argument { scopes: string[] } is required — the answer covers only the scopes that were named, ' +
335
+ 'and an empty listing must never be read as a clean store');
336
+ }
337
+ if (outcome?.status !== 'listed')
338
+ return { kind: 'unknown', why: 'not_listed' };
339
+ const wanted = snapshotScopeNames(asked.scopes);
340
+ if (wanted === null)
341
+ return { kind: 'unknown', why: 'coverage_shortfall' };
342
+ const seen = new Set(outcome.scopes);
343
+ for (const s of wanted) {
344
+ if (!seen.has(s))
345
+ return { kind: 'unknown', why: 'coverage_shortfall' };
346
+ }
347
+ return { kind: 'audited', scopes: outcome.scopes };
348
+ }
349
+ export async function listOriginClearances(facade, call, opts) {
350
+ if (typeof facade?.originClearances !== 'function') {
351
+ return refusedBy('client_too_old', 'this client has no clearance-ledger verb — nothing was sent; upgrade the client that builds the memory port');
352
+ }
353
+ const gate = originGate(call?.capability);
354
+ if (gate !== undefined)
355
+ return gate;
356
+ let res;
357
+ try {
358
+ res = await facade.originClearances(opts);
359
+ }
360
+ catch (e) {
361
+ return classifyMemoryFailure(e);
362
+ }
363
+ let rows;
364
+ try {
365
+ if (!plainObject(res)) {
366
+ return unknownBy('result_unreadable', 'the clearance ledger answered 2xx with a body this client cannot read — refusing to present it as a ledger');
367
+ }
368
+ rows = res.clearances;
369
+ }
370
+ catch {
371
+ return unknownBy('result_unreadable', 'the clearance ledger answered 2xx with a body this client cannot read — refusing to present it as a ledger');
372
+ }
373
+ if (!Array.isArray(rows)) {
374
+ return unknownBy('result_unreadable', 'the clearance ledger answered 2xx without a readable row list — refusing to present an empty ledger this client never actually read');
375
+ }
376
+ return { status: 'ledger', clearances: rows };
377
+ }
378
+ export async function clearEntryOrigin(facade, call, opts) {
379
+ if (typeof facade?.originClear !== 'function') {
380
+ return refusedBy('client_too_old', 'this client has no origin-clear verb — nothing was sent, so no marker was cleared; upgrade the client that builds the memory port');
381
+ }
382
+ const gate = originGate(call?.capability);
383
+ if (gate !== undefined)
384
+ return gate;
385
+ const entryId = call?.entryId;
386
+ if (!nonEmptyString(entryId)) {
387
+ return refusedBy('unreadable_request', 'clearing an origin marker needs an entry id — addressing by an absent id matches nothing, so nothing was sent');
388
+ }
389
+ const body = call?.body;
390
+ let requestId;
391
+ let reason;
392
+ try {
393
+ if (!plainObject(body)) {
394
+ return refusedBy('unreadable_request', 'clearing an origin marker needs a request body — refusing to mint one out of nothing, so nothing was sent');
395
+ }
396
+ requestId = body.requestId;
397
+ reason = body.reason;
398
+ }
399
+ catch {
400
+ return refusedBy('unreadable_request', 'the origin-clear request body could not be inspected — nothing was sent');
401
+ }
402
+ if (!nonEmptyString(requestId)) {
403
+ return refusedBy('unreadable_request', 'an origin clearance needs a request id: it is the audit attribution for an irreversible act, and a signature needs a name, so nothing was sent');
404
+ }
405
+ if (typeof reason !== 'string') {
406
+ return refusedBy('unreadable_request', 'an origin clearance needs a reason string — whether a given reason is acceptable is the engine judgement, but a non-string one cannot be recorded at all, so nothing was sent');
407
+ }
408
+ let res;
409
+ try {
410
+ res = await facade.originClear(entryId, call.body, opts);
411
+ }
412
+ catch (e) {
413
+ return classifyMemoryFailure(e);
414
+ }
415
+ const reading = readMemoryOriginClearanceReceipt(res);
416
+ if (reading.kind !== 'present') {
417
+ return unknownBy('result_unreadable', `the origin-clear call answered 2xx with a receipt this client cannot read (${reading.why}) — refusing to report the marker as cleared; read the clearance ledger back to reconcile`);
418
+ }
419
+ if (reading.entryId !== entryId) {
420
+ return unknownBy('identity_mismatch', 'the origin-clear receipt came back for a different entry than the one that was sent — refusing to present it as the outcome of this call');
421
+ }
422
+ return { status: 'cleared', receipt: reading };
423
+ }
@@ -37,6 +37,7 @@ export interface TaskRequestInput {
37
37
  limits?: Record<string, unknown>;
38
38
  interactiveTools?: false;
39
39
  oneShot?: true;
40
+ excludeAllTools?: true;
40
41
  outputSchema?: NonNullable<TaskRequest['outputSchema']>;
41
42
  maxCostUsd?: number;
42
43
  taskBudget?: number;
@@ -30,6 +30,7 @@ export const REQUEST_FIELD_MATRIX = [
30
30
  { field: 'maxCostUsd', lanes: ['print'], live: true, why: '花费上限声明(sdk `TaskRequest.maxCostUsd`,请求体**顶层**位;上游向下夹到部署上限 —— 只许要得更少)。与 `limits` 同一条理由只在无人值守车道:交互面由人随时中断。🔴 坏值(非数 / 非有限 / 非正)在本层**响亮拒**:上游对这一位的坏值是安静忽略并回落部署上限,不是 4xx(见 `refuseBadMaxCostUsd`)' },
31
31
  { field: 'attachmentIds', lanes: ['interactive'], live: true, why: '附件**字节通道**(uploadAttachment 回执 id;server ≥1.289 绑定会话并把文件物化进 run 的 attachments/)。有上传入口的端才有 id 可引用:web/desktop 有,壳走 images / 本地文件路径,`-p` 车道连 uploadAttachment 都没有 ⇒ print 缺席是**没有来源**,不是漏。🔴 与上面的 `attachments` 同名不同物(那是 turn 边界的配置键),两条永远不许合并' },
32
32
  { field: 'excludeTools', lanes: ['interactive', 'print'], live: true, why: 'design/161 W3(2026-07-30):真 wire 键(SDK types `excludeTools`)且早在 seatContract `START_SESSION_OPTION_KEYS` 里,却是 desktop 手写 taskReq 的第 9 键、矩阵外——而 stamp 门对未登记键**静默丢弃**,真实危险形=「用户显式排除的工具被静默放回」(权限方向回归,类型层不报)。今天只有 desktop 在发;壳的工具面走 roster/interactiveTools 另一条路。0.77.0 补 print 座(三问:① wire 位与车道无关 —— sdk 具名位、提交体真收 `body.excludeTools`;② 来源 —— print 今天没有排除入口,但这是**收紧方向**的声明,传了只进回执 = 端不读回执就把用户排除的工具放回;③ 口径 —— 仍是 live 门后的位,与交互面同一行)' },
33
+ { field: 'excludeAllTools', lanes: ['interactive', 'print'], live: true, why: '0.78.0(sdk 11.x 具名位 `excludeAllTools`,server ≥7.90.0 请求面):整只工具面卸载 —— 调用方工具 / 脚手架 / 手 / MCP / A2A / skills 一个都不挂,终局必然纯文本(wire 上 `wiring_manifest.tools.count === 0`,整条流零 tool_start / tool_end / tool_disclosure)。与 excludeTools 是两种语义、刻意不折叠(`"*"` 是一个合法的工具名,「卸全部」不能写成 `excludeTools:["*"]`);两键同带各自生效。类型只收字面 `true`(server 只把 literal true 写上任务规格,`false` = 替引擎重申它自己的缺省 ⇒ 不发、与缺席同义、不进回执);坏值响亮拒(收紧方向的声明不许被打字错误安静丢掉)。两条车道都是提交面 ⇒ 都能声明。探测靠键闭集、没有能力位:没接线的 server(≤7.89.x)400 `request.body_shape` 并在 `unknownKeys` 里逐字点名,本层不替它猜;A2A 入站面不承载本键(零工具面走 skill 路由)' },
33
34
  { field: 'settings.ultracode', lanes: ['interactive'], live: true, why: 'design/111:sticky `/effort ultracode` 拨盘 + 当轮关键词嗅探,两个来源都只在交互面存在', gap: true },
34
35
  { field: 'systemPrompt', lanes: ['interactive', 'print'], live: false, why: '整段替换系统提示(sdk `TaskRequest.systemPrompt`;上游字符数超限 400 响亮拒,本层原样透传)。两条车道都有来源:交互面 = CC QueryParams.systemPrompt,无人值守面 = `--system-prompt[-file]` 一类旗。与追加位 `appendSystemPrompt` 不是一个位(那一位归 live 兜底层,见 `LIVE_DEFAULT_FIELDS`)' },
35
36
  { field: 'reasoningEffort', lanes: ['interactive', 'print'], live: false, why: '思考档位(sdk `TaskRequest.reasoningEffort` 七词闭集;上游对表外词 400 响亮拒 ⇒ 本层不预铸第二判官)。档位是请求级的位、与车道无关:交互面 = `/effort` 拨盘,无人值守面 = 档位旗。🔴 端交的必须是**档位词**:「思考 token 数」今天没有上游对位(见 `UPSTREAM_ABSENT_REQUEST_INTENTS` 的 `maxThinkingTokens`),别把一个数字折成档位词发出去' },
@@ -81,6 +82,7 @@ const INPUT_KEY_ROUTES = {
81
82
  attachments: row('attachments'),
82
83
  attachmentIds: row('attachmentIds'),
83
84
  excludeTools: row('excludeTools'),
85
+ excludeAllTools: row('excludeAllTools'),
84
86
  finalVerification: row('finalVerification'),
85
87
  limits: row('limits'),
86
88
  interactiveTools: row('interactiveTools'),
@@ -171,6 +173,7 @@ const VALUE_LEVEL_IN_PLAY = Object.freeze({
171
173
  images: (v) => Array.isArray(v) && v.length > 0,
172
174
  attachmentIds: (v) => Array.isArray(v) && v.length > 0,
173
175
  excludeTools: (v) => Array.isArray(v) && v.length > 0,
176
+ excludeAllTools: (v) => v === true,
174
177
  memoryCapture: (v) => v === 'off',
175
178
  memory: (v) => v === 'off',
176
179
  rewind: (v) => typeof v === 'object' && v !== null && rewindSubKeys().some((k) => hasOwn(v, k) && present(v[k])),
@@ -379,6 +382,13 @@ const refuseBadMemoryOff = (v) => {
379
382
  '`"on"`,`{ enabled: false }` 对象形也不是 wire 拼法)且对坏拼写**响亮拒**(server 400 `request.field_invalid`)。' +
380
383
  '本层若静默删键,这一次运行会照常挂上记忆面而声明人不会知道 —— 与 memoryCapture 同一条纪律。错误文本只报形状,不回显值。');
381
384
  };
385
+ const refuseBadExcludeAllTools = (v) => {
386
+ if (v === undefined || v === null || v === true || v === false)
387
+ return;
388
+ throw new TypeError('buildTaskRequest: excludeAllTools 只接受字面量 `true`(`false` / 缺席 = 不卸载)—— ' +
389
+ `收到 ${coarseShape(v)}。这一位是**整只工具面卸载**的收紧声明:本层若把坏值静默删键,这一次运行会挂上声明人以为已经卸掉的全部工具。` +
390
+ '错误文本只报形状,不回显值。');
391
+ };
382
392
  const refuseBadMemoryCapture = (v) => {
383
393
  if (v === undefined || v === null)
384
394
  return;
@@ -416,12 +426,14 @@ export function assembleTaskRequest(input, lane) {
416
426
  const resolvedOpen = resolvedRoute.via === 'matrix' && omissionCauseOf(standingOf(resolvedRoute), lane, live) === undefined;
417
427
  const resolvedWire = resolvedOpen ? resolvedSnapshotForWire(resolvedIn) : {};
418
428
  const maxCostUsdIn = input.maxCostUsd;
429
+ const excludeAllToolsIn = input.excludeAllTools;
419
430
  const INPUT_KEYS = Object.keys(INPUT_KEY_ROUTES);
420
431
  const i = snapshotByName(input, INPUT_KEYS, {
421
432
  live,
422
433
  memoryCapture: memoryCaptureIn,
423
434
  memory: memoryIn,
424
435
  maxCostUsd: maxCostUsdIn,
436
+ excludeAllTools: excludeAllToolsIn,
425
437
  settings: settingsIn,
426
438
  });
427
439
  const SETTINGS_KEYS = Object.keys(SETTINGS_KEY_ROUTES);
@@ -429,6 +441,7 @@ export function assembleTaskRequest(input, lane) {
429
441
  refuseBadMemoryCapture(memoryCaptureIn);
430
442
  refuseBadMemoryOff(memoryIn);
431
443
  refuseBadMaxCostUsd(maxCostUsdIn);
444
+ refuseBadExcludeAllTools(excludeAllToolsIn);
432
445
  const rewindRoute = INPUT_KEY_ROUTES.rewind;
433
446
  const REWIND_KEYS = rewindRoute.via === 'matrix' ? rewindRoute.field.split('/') : [];
434
447
  const rewindIn = i.rewind;
@@ -553,6 +566,7 @@ export function assembleTaskRequest(input, lane) {
553
566
  ...(on('lenses') ? { lenses: i.lenses } : {}),
554
567
  ...(on('attachmentIds') ? { attachmentIds: [...(i.attachmentIds ?? [])] } : {}),
555
568
  ...(on('excludeTools') ? { excludeTools: [...(i.excludeTools ?? [])] } : {}),
569
+ ...(on('excludeAllTools') ? { excludeAllTools: true } : {}),
556
570
  ...(on('clientContext') ? { clientContext: i.clientContext } : {}),
557
571
  ...(on('scratchpadDir') ? { scratchpadDir: i.scratchpadDir } : {}),
558
572
  ...(on('finalVerification') ? { finalVerification: i.finalVerification } : {}),
@@ -29,6 +29,15 @@ export declare function stripUntrustedFence(text: string): {
29
29
  label: string;
30
30
  body: string;
31
31
  } | null;
32
+ export type DisplayBody = {
33
+ fenced: true;
34
+ label: string;
35
+ body: string;
36
+ } | {
37
+ fenced: false;
38
+ body: string;
39
+ };
40
+ export declare function displayBody(text: unknown): DisplayBody | undefined;
32
41
  export type ModelFacingTaskOutput = {
33
42
  kind: 'bash';
34
43
  taskId?: string | undefined;