@sema-agent/client-core 0.76.0 → 0.76.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 +26 -0
- package/README.md +6 -1
- package/dist/adapt/arms.js +51 -48
- package/dist/adapt/ids.d.ts +29 -2
- package/dist/adapt/ids.js +29 -2
- package/dist/adapt/panelTasks.d.ts +4 -3
- package/dist/adapt/panelTasks.js +29 -11
- package/dist/adapt/textStream.js +5 -5
- package/dist/adapt/toolCards.js +2 -2
- package/dist/adapt/turnFlags.js +5 -5
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +8 -8
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +12 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +13 -20
- package/dist/adapter/runStream.js +14 -5
- package/dist/adapter/types.d.ts +12 -0
- package/dist/adapter/types.js +30 -0
- package/dist/approvalsStreamLiveCapability.js +15 -35
- package/dist/deviceExecutorManagementCapability.js +15 -35
- package/dist/engineCapReader.d.ts +77 -0
- package/dist/engineCapReader.js +87 -0
- package/dist/executionLaneCapability.js +15 -38
- package/dist/hitl/persistedRulesWire.d.ts +20 -0
- package/dist/hitl/persistedRulesWire.js +23 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +22 -1
- package/dist/mcpLiveness.d.ts +179 -0
- package/dist/mcpLiveness.js +218 -0
- package/dist/mcpPanel.d.ts +17 -0
- package/dist/mcpPanel.js +21 -0
- package/dist/mcpProbeCapability.d.ts +84 -0
- package/dist/mcpProbeCapability.js +124 -0
- package/dist/memoryComplianceCapability.d.ts +73 -0
- package/dist/memoryComplianceCapability.js +109 -0
- package/dist/memoryEntriesWire.d.ts +302 -0
- package/dist/memoryEntriesWire.js +595 -0
- package/dist/memoryOriginCapability.d.ts +68 -0
- package/dist/memoryOriginCapability.js +102 -0
- package/dist/peerLaneCapability.d.ts +62 -0
- package/dist/peerLaneCapability.js +100 -0
- package/dist/permissionRulesWriteCapability.d.ts +64 -0
- package/dist/permissionRulesWriteCapability.js +98 -0
- package/dist/selfOrchestrationDenial.js +7 -2
- package/dist/sqlEngineCapability.js +15 -35
- package/dist/webSearchBackendCapability.js +15 -35
- package/dist/writeProtectionCapability.js +15 -35
- package/docs/INTEGRATION-CLIENTS.md +89 -11
- package/package.json +1 -1
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memoryEntriesWire — 记忆**条目面**三个 200 回体的三端共用**窄读口**(0.76.1 CC-97a):
|
|
3
|
+
* ① `GET /v1/memory/export?scope=` 的条目行(`{ scope, exportedAt, entries[] }`);
|
|
4
|
+
* ② `POST /v1/memory/erase` 的**抹除证明**回体;
|
|
5
|
+
* ③ `POST /v1/memory/origin/entries/:entryId/clear` 的**清标回执**。
|
|
6
|
+
*
|
|
7
|
+
* 🔴 本件只有**读口**:一个动词都不调、一个请求都不铸(动词半场另票)。入参一律 `unknown` —— 三个回体在
|
|
8
|
+
* wire 上要么是开放判别式(引擎原样下发、不投影、不复述),要么 SDK 的声明形里就是 `unknown[]`,
|
|
9
|
+
* 所以边界必须**按结构**窄读。
|
|
10
|
+
*
|
|
11
|
+
* ── 逐行窄读的三条边界(与本包既有行读口同律)────────────────────────────────────────────────
|
|
12
|
+
* · **空数组 = 真 0**(引擎明说「一条都没有」);
|
|
13
|
+
* · **非空数组却一行都读不出 ⇒ `malformed`**(这一眼没看成),**绝不**答「present + 空列表」——
|
|
14
|
+
* 那是把「取不到」冒充「一条都没有」;
|
|
15
|
+
* · **半坏 ⇒ `present` + `dropped` 计数**(读得出来的行是真的,丢掉那几条如实交代)。
|
|
16
|
+
* 🔴 「读不懂的行」与「没有行」在本件里**永远不共用一句话**。
|
|
17
|
+
*
|
|
18
|
+
* ── 🔴 「空答不等于本店干净」在代码里的落点 ─────────────────────────────────────────────────
|
|
19
|
+
* 导出读口的答案在两条轴上都不完备,而**两条都不是本包能推翻的**:
|
|
20
|
+
* · **scope 轴**:引擎 v1 没有 scope 枚举读面,一次答案只覆盖调用方点名的那一个 scope ⇒
|
|
21
|
+
* {@link scopeExternalOriginVerdict} 的肯定臂叫 `none_in_this_scope` **并且带着那个 scope 串**,
|
|
22
|
+
* 联合里**没有**任何「整店」臂 —— 想把它复述成全店结论,型上就写不出来。
|
|
23
|
+
* · **围栏轴**:这条读口**缺省不投带外源标的条目**(整条目扣留 + 只在 n>0 时铸一格计数),调用方须在
|
|
24
|
+
* 请求上显式声明认识 origin 键族才放行。回体在两种情形下**形状完全一样** ⇒ 读口光看回体
|
|
25
|
+
* **判不出**围栏开没开 ⇒ {@link scopeExternalOriginVerdict} 的第二个形参是**必填**的
|
|
26
|
+
* `{ originAware }`:没声明过 ⇒ 空答一律 `unknown`(`why: 'fenced'`),一个字的「干净」都不给。
|
|
27
|
+
*
|
|
28
|
+
* ── UNTRUSTED ───────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
* slug / scope / reason / origin 载体都是引擎侧与用户侧的内容,只渲染绝不当代码用;本件不 trim、不截、
|
|
30
|
+
* 不改写任何原文字节。
|
|
31
|
+
*/
|
|
32
|
+
function nonEmptyString(v) {
|
|
33
|
+
return typeof v === 'string' && v !== '';
|
|
34
|
+
}
|
|
35
|
+
function nonNegativeInt(v) {
|
|
36
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0 && Number.isInteger(v);
|
|
37
|
+
}
|
|
38
|
+
/** 对象(不含 `null` / 数组)。三个读口的每一层都用它,免得某一层悄悄把数组当对象读。 */
|
|
39
|
+
function plainObject(v) {
|
|
40
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
41
|
+
}
|
|
42
|
+
/** `hasOwn` 且值非 `undefined`。原型链同名键永远不会被序列化上 wire,读成在场就是凭空造一格。 */
|
|
43
|
+
function ownPresent(o, k) {
|
|
44
|
+
return Object.hasOwn(o, k) && o[k] !== undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 一段数组能窄读的**行数上限**。超过它 ⇒ 判 `too_many_rows`(读不出),🔴 **绝不**静默截断成前 N 行 ——
|
|
48
|
+
* 截断出来的「这一答里没有带标行」正是本件要消灭的那句假话。这个数只表达「本读口一口吃不下这么多」,
|
|
49
|
+
* 不是对引擎的任何限额主张;它同时挡住「回体自报一个天文数字长度」把读口挂住那一形。
|
|
50
|
+
*/
|
|
51
|
+
const MAX_NARROWED_ROWS = 100_000;
|
|
52
|
+
/**
|
|
53
|
+
* 把不可信的数组**一次性快照**成定长普通数组。
|
|
54
|
+
*
|
|
55
|
+
* 🔴 为什么不能直接 `for…of` 原数组(异源对抗复审 R1 [high],已实测复现):
|
|
56
|
+
* ① `for…of` 走的是**调用方的** `Symbol.iterator`,它的陷阱抛出会从循环**外**逸出(循环体内的 try 接不到),
|
|
57
|
+
* 整只读口就抛了 —— 而本件承诺永不抛;
|
|
58
|
+
* ② `length` 会被**反复读**(迭代一次、循环后的「非空全坏」判据再一次):一个首次报 2、之后报 0 的 Proxy
|
|
59
|
+
* 能让循环只走第一格(无标行)而末尾判据看到 0 ⇒ 带标的那一行被丢掉、`dropped` 还是 0 ⇒ 判词口一路走到
|
|
60
|
+
* `none_in_this_scope`。**把「看不见」渲成「没有」**,正是本件的头号禁形。
|
|
61
|
+
* 所以:`length` **只读一次**并验型,再按**固定索引**各读一次;某一格读不出只记 `unreadable`(它在行层计入
|
|
62
|
+
* `dropped`,于是判词口的否定臂当场失去资格),不作废整段、也不外抛。
|
|
63
|
+
* 返回 `null` = 这一段整个读不出(不是数组 / 长度读不出或不是安全非负整数 / 超过 {@link MAX_NARROWED_ROWS})。
|
|
64
|
+
*/
|
|
65
|
+
function snapshotArray(v) {
|
|
66
|
+
if (!Array.isArray(v))
|
|
67
|
+
return null;
|
|
68
|
+
let n;
|
|
69
|
+
try {
|
|
70
|
+
n = v.length;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 0)
|
|
76
|
+
return null;
|
|
77
|
+
if (n > MAX_NARROWED_ROWS)
|
|
78
|
+
return { items: [], unreadable: 0, tooLarge: true };
|
|
79
|
+
const items = [];
|
|
80
|
+
let unreadable = 0;
|
|
81
|
+
for (let i = 0; i < n; i++) {
|
|
82
|
+
try {
|
|
83
|
+
items.push(v[i]);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
unreadable++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { items, unreadable, tooLarge: false };
|
|
90
|
+
}
|
|
91
|
+
/** 数组的**元素个数**(只用于「被钉住的 id 总数」这类纯计数位):读不出 ⇒ `null`,🔴 绝不折 0。 */
|
|
92
|
+
function arrayLengthOrNull(v) {
|
|
93
|
+
const snap = snapshotArray(v);
|
|
94
|
+
if (snap === null || snap.tooLarge)
|
|
95
|
+
return null;
|
|
96
|
+
return snap.items.length + snap.unreadable;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 一行的外源标记三态判定。**每个键只读一次**(变化的 getter 不许让两次读看到两个值)。
|
|
100
|
+
* 载体顺序与保护方向:结构载体命中即 `'marked'`(最强的肯定);`frontmatter` 读不懂 ⇒ 判不出;
|
|
101
|
+
* 第三载体在场 ⇒ 判不出(判官在引擎侧)。
|
|
102
|
+
*/
|
|
103
|
+
function originMarkOf(row) {
|
|
104
|
+
if (ownPresent(row, 'origin'))
|
|
105
|
+
return 'marked';
|
|
106
|
+
const fm = row.frontmatter;
|
|
107
|
+
if (!plainObject(fm))
|
|
108
|
+
return 'undecidable';
|
|
109
|
+
if (ownPresent(fm, 'origin'))
|
|
110
|
+
return 'marked';
|
|
111
|
+
if (ownPresent(fm, 'extra'))
|
|
112
|
+
return 'undecidable';
|
|
113
|
+
return 'unmarked';
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* `GET /v1/memory/export?scope=` 的 200 体 → 条目行读数。四把承重键(`id` / `slug` / `scope` / `rev`)
|
|
117
|
+
* 任一读不出的行丢弃并计数;同 `id` 的重复行留第一条(后面的计入 `dropped`)。永不抛。
|
|
118
|
+
*/
|
|
119
|
+
export function readMemoryExportRows(body) {
|
|
120
|
+
let o;
|
|
121
|
+
let rawEntries;
|
|
122
|
+
let rawScope;
|
|
123
|
+
let rawExportedAt;
|
|
124
|
+
let rawWithheld;
|
|
125
|
+
let hasWithheld;
|
|
126
|
+
let rawTruncated;
|
|
127
|
+
let hasTruncated;
|
|
128
|
+
let rawNextBefore;
|
|
129
|
+
try {
|
|
130
|
+
if (!plainObject(body))
|
|
131
|
+
return { kind: 'malformed', why: 'body_unreadable' };
|
|
132
|
+
o = body;
|
|
133
|
+
// 🔴 每把键**恰读一次**并就地快照:回体可以是带 getter / Proxy 的对象,读两次就能拿到两个不同的答案。
|
|
134
|
+
if (!Object.hasOwn(o, 'entries'))
|
|
135
|
+
return { kind: 'malformed', why: 'envelope_absent' };
|
|
136
|
+
rawEntries = o.entries;
|
|
137
|
+
rawScope = o.scope;
|
|
138
|
+
rawExportedAt = o.exportedAt;
|
|
139
|
+
// 🔴 这两格的在场判据与取值也**分开**(与 `historyUnknown` 同一条采修的族扫):键在场而值读不成时,
|
|
140
|
+
// 折回「键缺席」会在**完备性轴**上给出更宽松的答案(缺席 = 没扣留 / 缺席 = 这就是全量),而那正是
|
|
141
|
+
// 这两格存在的意义 —— 在场读不成必须走各自的 fail-closed 臂。
|
|
142
|
+
hasWithheld = Object.hasOwn(o, 'withheldOriginTagged');
|
|
143
|
+
rawWithheld = hasWithheld ? o.withheldOriginTagged : undefined;
|
|
144
|
+
hasTruncated = Object.hasOwn(o, 'truncated');
|
|
145
|
+
rawTruncated = hasTruncated ? o.truncated : undefined;
|
|
146
|
+
rawNextBefore = o.nextBefore;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// 读回体的 getter / Proxy 陷阱抛出 = 这一眼没看成(与「不是对象」同一处置);绝不外抛。
|
|
150
|
+
return { kind: 'malformed', why: 'body_unreadable' };
|
|
151
|
+
}
|
|
152
|
+
if (rawEntries === undefined)
|
|
153
|
+
return { kind: 'malformed', why: 'envelope_absent' };
|
|
154
|
+
// 🔴 数组**一次快照**(见 {@link snapshotArray} 头注:`for…of` 的迭代器陷阱会从循环外逸出,反复读的 `length`
|
|
155
|
+
// 能让带标行被丢掉而这一答被判成「干净」)。快照本身也在保护里 —— 它读的是不可信对象。
|
|
156
|
+
let snap;
|
|
157
|
+
try {
|
|
158
|
+
snap = snapshotArray(rawEntries);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return { kind: 'malformed', why: 'body_unreadable' };
|
|
162
|
+
}
|
|
163
|
+
if (snap === null)
|
|
164
|
+
return { kind: 'malformed', why: 'envelope_unreadable' };
|
|
165
|
+
if (snap.tooLarge)
|
|
166
|
+
return { kind: 'malformed', why: 'too_many_rows' };
|
|
167
|
+
if (!nonEmptyString(rawScope) || !nonEmptyString(rawExportedAt)) {
|
|
168
|
+
return { kind: 'malformed', why: 'envelope_unreadable' };
|
|
169
|
+
}
|
|
170
|
+
const rows = [];
|
|
171
|
+
const seen = new Set();
|
|
172
|
+
// 读不出的那几格先记进 dropped:它们是**真的丢了行**,判词口的否定臂据此当场失去资格。
|
|
173
|
+
let dropped = snap.unreadable;
|
|
174
|
+
for (const item of snap.items) {
|
|
175
|
+
let row = null;
|
|
176
|
+
try {
|
|
177
|
+
if (!plainObject(item)) {
|
|
178
|
+
dropped++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const id = item.id;
|
|
182
|
+
const slug = item.slug;
|
|
183
|
+
const scope = item.scope;
|
|
184
|
+
const rev = item.rev;
|
|
185
|
+
if (!nonEmptyString(id) || !nonEmptyString(slug) || !nonEmptyString(scope) || !nonEmptyString(rev)) {
|
|
186
|
+
dropped++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (seen.has(id)) {
|
|
190
|
+
dropped++;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
seen.add(id);
|
|
194
|
+
row = { id, slug, scope, rev, originMark: originMarkOf(item) };
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// 单行的抛 getter 只丢这一行(与坏形行同一处置),不作废整段。
|
|
198
|
+
dropped++;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
rows.push(row);
|
|
202
|
+
}
|
|
203
|
+
// 🔴 非空数组、却一行都读不出 ⇒ `malformed`:那不是「这个 scope 一条都没有」。空数组不在本条射程。
|
|
204
|
+
// 判据锚在**快照**上(`snap.items.length + snap.unreadable`),不再回头读原数组的 length。
|
|
205
|
+
const seenRows = snap.items.length + snap.unreadable;
|
|
206
|
+
if (seenRows > 0 && rows.length === 0)
|
|
207
|
+
return { kind: 'malformed', why: 'rows_unreadable' };
|
|
208
|
+
return {
|
|
209
|
+
kind: 'present',
|
|
210
|
+
scope: rawScope,
|
|
211
|
+
exportedAt: rawExportedAt,
|
|
212
|
+
rows,
|
|
213
|
+
dropped,
|
|
214
|
+
withheldOriginTagged: !hasWithheld ? null : nonNegativeInt(rawWithheld) ? rawWithheld : 'unreadable',
|
|
215
|
+
// 完备性轴 fail-closed:只有**键缺席**与**显式 `false`** 算「这就是全量」;键在场而值读不成 ⇒ 当成不是全量。
|
|
216
|
+
truncated: hasTruncated ? rawTruncated !== false : false,
|
|
217
|
+
nextBefore: nonEmptyString(rawNextBefore) ? rawNextBefore : null,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 「这个 scope 里有没有外源标记的条目」的判据单源。
|
|
222
|
+
*
|
|
223
|
+
* 🔴 `asked.originAware` **必填**,且只有它为 `true` 时否定臂才够得着:这条读口缺省围栏掉所有带标条目,
|
|
224
|
+
* 而回体在「围栏开」与「围栏关」两种情形下**形状一样** —— 读口自己判不出,只有发请求的人知道。
|
|
225
|
+
* 没声明 ⇒ 空答一律 `unknown`(`why: 'fenced'`)。这就是「空答绝不读作本店干净」的**代码**落点。
|
|
226
|
+
* 判序(肯定证据先行,否定证据要完备):
|
|
227
|
+
* ① 回体读不懂 ⇒ `unknown`;
|
|
228
|
+
* ② 读得出的带标行 >0,**或**引擎明说扣留了 >0 条 ⇒ `marked`(围栏只扣带标条目,所以那个计数本身就是
|
|
229
|
+
* 肯定证据;此时翻页 / 丢行都不影响这句肯定话);
|
|
230
|
+
* ③ 扣留计数在场但读不懂 ⇒ `unknown`(可能扣了,也可能没扣);
|
|
231
|
+
* ④ 否定臂的三道完备性闸:没声明 `originAware` ⇒ `'fenced'`;`truncated` ⇒ `'truncated'`;
|
|
232
|
+
* 有丢行 ⇒ `'rows_dropped'`;有判不出标记的行 ⇒ `'undecidable_rows'`;
|
|
233
|
+
* ⑤ 全过 ⇒ `none_in_this_scope`(带 scope 串)。
|
|
234
|
+
*/
|
|
235
|
+
export function scopeExternalOriginVerdict(reading, asked) {
|
|
236
|
+
if (reading.kind !== 'present')
|
|
237
|
+
return { kind: 'unknown', why: 'unreadable' };
|
|
238
|
+
const visible = reading.rows.filter((r) => r.originMark === 'marked').length;
|
|
239
|
+
const withheld = reading.withheldOriginTagged;
|
|
240
|
+
if (visible > 0 || (typeof withheld === 'number' && withheld > 0)) {
|
|
241
|
+
return {
|
|
242
|
+
kind: 'marked',
|
|
243
|
+
scope: reading.scope,
|
|
244
|
+
visible,
|
|
245
|
+
withheld: typeof withheld === 'number' ? withheld : null,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (withheld === 'unreadable')
|
|
249
|
+
return { kind: 'unknown', why: 'withheld_unreadable' };
|
|
250
|
+
if (asked.originAware !== true)
|
|
251
|
+
return { kind: 'unknown', why: 'fenced' };
|
|
252
|
+
if (reading.truncated)
|
|
253
|
+
return { kind: 'unknown', why: 'truncated' };
|
|
254
|
+
if (reading.dropped > 0)
|
|
255
|
+
return { kind: 'unknown', why: 'rows_dropped' };
|
|
256
|
+
if (reading.rows.some((r) => r.originMark === 'undecidable')) {
|
|
257
|
+
return { kind: 'unknown', why: 'undecidable_rows' };
|
|
258
|
+
}
|
|
259
|
+
return { kind: 'none_in_this_scope', scope: reading.scope };
|
|
260
|
+
}
|
|
261
|
+
/** 本包认得的抹除证明版本信封(上游明写「读到更高版本必须拒,绝不重解释」)。 */
|
|
262
|
+
const MEMORY_ERASE_RECEIPT_VERSION = 1;
|
|
263
|
+
/**
|
|
264
|
+
* 三张**闭合**词表各自的窄读器 —— 认得的词原样带出,🔴 别的一律 `null`(读不出),绝不塌进已知臂。
|
|
265
|
+
* 写成三只小函数而不是三个 `Set` + 断言:词表与返回型在这里是**同一处**声明,加词只能在这一行加。
|
|
266
|
+
*/
|
|
267
|
+
function eraseStatusOf(v) {
|
|
268
|
+
return v === 'complete' || v === 'partial' ? v : null;
|
|
269
|
+
}
|
|
270
|
+
function eraseEvidenceCapabilityOf(v) {
|
|
271
|
+
return v === 'journal' || v === 'none' ? v : null;
|
|
272
|
+
}
|
|
273
|
+
function eraseCustodyStateOf(v) {
|
|
274
|
+
return v === 'complete' || v === 'damaged' || v === 'capability-absent' ? v : null;
|
|
275
|
+
}
|
|
276
|
+
/** 一行的抹除绑定窄读:两条臂逐字对上才认,别的一律 `null`(读不出,不是 unbound)。 */
|
|
277
|
+
function erasedBindingOf(v) {
|
|
278
|
+
if (!plainObject(v))
|
|
279
|
+
return null;
|
|
280
|
+
const state = v.state;
|
|
281
|
+
if (state === 'unbound')
|
|
282
|
+
return { state: 'unbound' };
|
|
283
|
+
if (state !== 'bound')
|
|
284
|
+
return null;
|
|
285
|
+
const scope = v.scope;
|
|
286
|
+
const slug = v.slug;
|
|
287
|
+
if (!nonEmptyString(scope) || !nonEmptyString(slug))
|
|
288
|
+
return null;
|
|
289
|
+
return { state: 'bound', scope, slug };
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* `POST /v1/memory/erase` 的 200 体 → 抹除证明读数。永不抛。
|
|
293
|
+
* 🔴 三件必须判成**三回事**:「这一次一条都没抹掉」(`present` 且 `erased` 空)/「200 空体」
|
|
294
|
+
* (`malformed` / `envelope_absent`)/「回体读不懂」(`malformed` / `body_unreadable`)。
|
|
295
|
+
*/
|
|
296
|
+
export function readMemoryEraseReceipt(body) {
|
|
297
|
+
let o;
|
|
298
|
+
let rawV;
|
|
299
|
+
let rawRequestId;
|
|
300
|
+
let rawStatus;
|
|
301
|
+
let rawEvidence;
|
|
302
|
+
let rawCustody;
|
|
303
|
+
let rawSelected;
|
|
304
|
+
let rawErased;
|
|
305
|
+
let rawNotFound;
|
|
306
|
+
let rawPreviously;
|
|
307
|
+
let rawConflicts;
|
|
308
|
+
let envelopeSeen;
|
|
309
|
+
try {
|
|
310
|
+
if (!plainObject(body))
|
|
311
|
+
return { kind: 'malformed', why: 'body_unreadable' };
|
|
312
|
+
o = body;
|
|
313
|
+
// 🔴 每把键恰读一次并快照:后面所有判决都对这一份,变化的 getter 换不掉它。
|
|
314
|
+
envelopeSeen = ['v', 'requestId', 'status', 'erased', 'notFound'].some((k) => Object.hasOwn(o, k));
|
|
315
|
+
rawV = Object.hasOwn(o, 'v') ? o.v : undefined;
|
|
316
|
+
rawRequestId = o.requestId;
|
|
317
|
+
rawStatus = o.status;
|
|
318
|
+
rawEvidence = o.evidenceCapability;
|
|
319
|
+
rawCustody = o.custodyState;
|
|
320
|
+
rawSelected = o.resolvedIds;
|
|
321
|
+
rawErased = o.erased;
|
|
322
|
+
rawNotFound = o.notFound;
|
|
323
|
+
rawPreviously = Object.hasOwn(o, 'erasedPreviously') ? o.erasedPreviously : undefined;
|
|
324
|
+
rawConflicts = o.conflicts;
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return { kind: 'malformed', why: 'body_unreadable' };
|
|
328
|
+
}
|
|
329
|
+
// 「200 空体」:一把信封键都没有 ⇒ 这不是一份收执。
|
|
330
|
+
if (!envelopeSeen)
|
|
331
|
+
return { kind: 'malformed', why: 'envelope_absent' };
|
|
332
|
+
if (rawV === undefined)
|
|
333
|
+
return { kind: 'malformed', why: 'version_absent' };
|
|
334
|
+
if (rawV !== MEMORY_ERASE_RECEIPT_VERSION)
|
|
335
|
+
return { kind: 'malformed', why: 'unsupported_version' };
|
|
336
|
+
const status = eraseStatusOf(rawStatus);
|
|
337
|
+
const evidenceCapability = eraseEvidenceCapabilityOf(rawEvidence);
|
|
338
|
+
const custodyState = eraseCustodyStateOf(rawCustody);
|
|
339
|
+
if (!nonEmptyString(rawRequestId) || status === null || evidenceCapability === null || custodyState === null) {
|
|
340
|
+
return { kind: 'malformed', why: 'envelope_unreadable' };
|
|
341
|
+
}
|
|
342
|
+
// 🔴 四段各自**一次快照**(理由见 {@link snapshotArray} 头注:迭代器陷阱会从循环外逸出,反复读的 `length`
|
|
343
|
+
// 能让半段行被静默丢掉而末尾「非空全坏」判据看不见)。快照与「被钉住的 id 总数」那次长度读都在保护里。
|
|
344
|
+
let erasedSnap;
|
|
345
|
+
let notFoundSnap;
|
|
346
|
+
let conflictsSnap;
|
|
347
|
+
let previouslySnap;
|
|
348
|
+
let selectedIds;
|
|
349
|
+
try {
|
|
350
|
+
erasedSnap = snapshotArray(rawErased);
|
|
351
|
+
notFoundSnap = snapshotArray(rawNotFound);
|
|
352
|
+
conflictsSnap = snapshotArray(rawConflicts);
|
|
353
|
+
previouslySnap = rawPreviously === undefined ? null : snapshotArray(rawPreviously);
|
|
354
|
+
selectedIds = arrayLengthOrNull(rawSelected);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
return { kind: 'malformed', why: 'body_unreadable' };
|
|
358
|
+
}
|
|
359
|
+
if (erasedSnap === null || notFoundSnap === null || conflictsSnap === null) {
|
|
360
|
+
return { kind: 'malformed', why: 'envelope_unreadable' };
|
|
361
|
+
}
|
|
362
|
+
if (rawPreviously !== undefined && previouslySnap === null) {
|
|
363
|
+
return { kind: 'malformed', why: 'envelope_unreadable' };
|
|
364
|
+
}
|
|
365
|
+
if (erasedSnap.tooLarge || notFoundSnap.tooLarge || conflictsSnap.tooLarge || previouslySnap?.tooLarge === true) {
|
|
366
|
+
return { kind: 'malformed', why: 'too_many_rows' };
|
|
367
|
+
}
|
|
368
|
+
const erased = [];
|
|
369
|
+
const erasedSeen = new Set();
|
|
370
|
+
let erasedDropped = erasedSnap.unreadable;
|
|
371
|
+
for (const item of erasedSnap.items) {
|
|
372
|
+
try {
|
|
373
|
+
if (!plainObject(item)) {
|
|
374
|
+
erasedDropped++;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const id = item.id;
|
|
378
|
+
const rev = item.rev;
|
|
379
|
+
if (!nonEmptyString(id) || !nonEmptyString(rev) || erasedSeen.has(id)) {
|
|
380
|
+
erasedDropped++;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
erasedSeen.add(id);
|
|
384
|
+
erased.push({ id, rev, binding: erasedBindingOf(item.binding) });
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
erasedDropped++;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (erasedSnap.items.length + erasedSnap.unreadable > 0 && erased.length === 0) {
|
|
391
|
+
return { kind: 'malformed', why: 'erased_rows_unreadable' };
|
|
392
|
+
}
|
|
393
|
+
const notFound = [];
|
|
394
|
+
const notFoundSeen = new Set();
|
|
395
|
+
let notFoundDropped = notFoundSnap.unreadable;
|
|
396
|
+
for (const item of notFoundSnap.items) {
|
|
397
|
+
try {
|
|
398
|
+
if (!plainObject(item)) {
|
|
399
|
+
notFoundDropped++;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const id = item.id;
|
|
403
|
+
if (!nonEmptyString(id) || notFoundSeen.has(id)) {
|
|
404
|
+
notFoundDropped++;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
// 🔴 四态原样(异源对抗复审 R1 [medium] 采修 —— 修前「在场但不是布尔」与「键缺席」同形答 null,
|
|
408
|
+
// 于是一条**读不懂**历史位的行会被历史判词答成 `no_row_at_judgment`,消费端再也看不出这一格没读成):
|
|
409
|
+
// 键缺席 ⇒ `null`(上游今日只在链有洞时铸 true,不铸 false ⇒ 缺席是一句真判断)/ 严格布尔 ⇒ 原值 /
|
|
410
|
+
// 在场但不是布尔 ⇒ `'unreadable'`(行不丢:`id` 是承重键,坏的只是这一格)。
|
|
411
|
+
// 🔴 在场判据与取值**分开各读一次**(异源对抗复审 R2 [medium] 采修):把「键缺席」与「键在场但值是
|
|
412
|
+
// `undefined`」合成一个 `raw === undefined` 判据,会让一个**在场却读不成**的历史位落回 `null`(键缺席),
|
|
413
|
+
// 历史判词随后答出 `no_row_at_judgment` 那句更强的话。`hasOwn` 不读值 ⇒ 值仍恰读一次。
|
|
414
|
+
const hasHistory = Object.hasOwn(item, 'historyUnknown');
|
|
415
|
+
const raw = hasHistory ? item.historyUnknown : undefined;
|
|
416
|
+
notFoundSeen.add(id);
|
|
417
|
+
notFound.push({
|
|
418
|
+
id,
|
|
419
|
+
historyUnknown: !hasHistory ? null : typeof raw === 'boolean' ? raw : 'unreadable',
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
notFoundDropped++;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (notFoundSnap.items.length + notFoundSnap.unreadable > 0 && notFound.length === 0) {
|
|
427
|
+
return { kind: 'malformed', why: 'not_found_rows_unreadable' };
|
|
428
|
+
}
|
|
429
|
+
const conflicts = [];
|
|
430
|
+
let conflictsDropped = conflictsSnap.unreadable;
|
|
431
|
+
for (const item of conflictsSnap.items) {
|
|
432
|
+
try {
|
|
433
|
+
if (!plainObject(item)) {
|
|
434
|
+
conflictsDropped++;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const id = item.id;
|
|
438
|
+
const reason = item.reason;
|
|
439
|
+
if (!nonEmptyString(id) || typeof reason !== 'string') {
|
|
440
|
+
conflictsDropped++;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
conflicts.push({ id, reason });
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
conflictsDropped++;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (conflictsSnap.items.length + conflictsSnap.unreadable > 0 && conflicts.length === 0) {
|
|
450
|
+
return { kind: 'malformed', why: 'conflict_rows_unreadable' };
|
|
451
|
+
}
|
|
452
|
+
let erasedPreviously = null;
|
|
453
|
+
let erasedPreviouslyDropped = 0;
|
|
454
|
+
if (previouslySnap !== null) {
|
|
455
|
+
const rows = [];
|
|
456
|
+
const seen = new Set();
|
|
457
|
+
erasedPreviouslyDropped = previouslySnap.unreadable;
|
|
458
|
+
for (const item of previouslySnap.items) {
|
|
459
|
+
try {
|
|
460
|
+
if (!plainObject(item)) {
|
|
461
|
+
erasedPreviouslyDropped++;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const id = item.id;
|
|
465
|
+
if (!nonEmptyString(id) || seen.has(id)) {
|
|
466
|
+
erasedPreviouslyDropped++;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
seen.add(id);
|
|
470
|
+
rows.push({ id });
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
erasedPreviouslyDropped++;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// 🔴 这一段与另外三段**同律**(异源对抗复审 R1 [medium] 采修 —— 修前这里少了这道闸,
|
|
477
|
+
// `erasedPreviously: [null]` 会被发布成 `present` + 空列表 + dropped 1,即「一条先前抹除都没有」,
|
|
478
|
+
// 而真相是这一段一行都没读成)。
|
|
479
|
+
if (previouslySnap.items.length + previouslySnap.unreadable > 0 && rows.length === 0) {
|
|
480
|
+
return { kind: 'malformed', why: 'erased_previously_rows_unreadable' };
|
|
481
|
+
}
|
|
482
|
+
erasedPreviously = rows;
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
kind: 'present',
|
|
486
|
+
requestId: rawRequestId,
|
|
487
|
+
status,
|
|
488
|
+
evidenceCapability,
|
|
489
|
+
custodyState,
|
|
490
|
+
selectedIds,
|
|
491
|
+
erased,
|
|
492
|
+
erasedDropped,
|
|
493
|
+
notFound,
|
|
494
|
+
notFoundDropped,
|
|
495
|
+
erasedPreviously,
|
|
496
|
+
erasedPreviouslyDropped,
|
|
497
|
+
conflicts,
|
|
498
|
+
conflictsDropped,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* 「这一次抹了几条」的判据单源。
|
|
503
|
+
* 🔴 读不懂 ⇒ `unknown` + 原成因(**绝不**答 0);任何一段有丢行 ⇒ `unknown` / `'rows_dropped'`
|
|
504
|
+
* (读得出的行是真的,但这几个**数**已经不是引擎给的那几个了)。
|
|
505
|
+
*/
|
|
506
|
+
export function memoryEraseCounts(reading) {
|
|
507
|
+
if (reading.kind !== 'present')
|
|
508
|
+
return { kind: 'unknown', why: reading.why };
|
|
509
|
+
if (reading.erasedDropped > 0 ||
|
|
510
|
+
reading.notFoundDropped > 0 ||
|
|
511
|
+
reading.conflictsDropped > 0 ||
|
|
512
|
+
reading.erasedPreviouslyDropped > 0) {
|
|
513
|
+
return { kind: 'unknown', why: 'rows_dropped' };
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
kind: 'counted',
|
|
517
|
+
erased: reading.erased.length,
|
|
518
|
+
notFound: reading.notFound.length,
|
|
519
|
+
conflicts: reading.conflicts.length,
|
|
520
|
+
erasedPreviously: reading.erasedPreviously === null ? null : reading.erasedPreviously.length,
|
|
521
|
+
selectedIds: reading.selectedIds,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* 「这一次是不是一条都没抹掉」的三态判据单源。
|
|
526
|
+
* 🔴 `'yes'` **只**由「数得出来、且已抹除段为空」铸得出(典型形 = 已抹除段空、notFound 段非空 ——
|
|
527
|
+
* 逐 id 交代了为什么一条都没删)。「200 空体」/「回体读不懂」/「有丢行」一律 `'unknown'`,
|
|
528
|
+
* 绝不与 `'yes'` 同形:把取不到冒充「零抹除」,端会据此告诉人「什么都没删」。
|
|
529
|
+
*/
|
|
530
|
+
export function memoryEraseErasedNothing(reading) {
|
|
531
|
+
const counts = memoryEraseCounts(reading);
|
|
532
|
+
if (counts.kind !== 'counted')
|
|
533
|
+
return 'unknown';
|
|
534
|
+
return counts.erased === 0 ? 'yes' : 'no';
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* 一条 notFound 行的历史判词(两态)。🔴 联合里**没有**「从来没存在过」这一臂:上游明说这份证明
|
|
538
|
+
* **不区分**「从没存在过」/「自然删掉了」/「别的请求删的」三义,只有证据链本身分得清。
|
|
539
|
+
* `'unknown'` = 行上明标判不清;`'no_row_at_judgment'` = 判决那一刻没有这条记录 ——
|
|
540
|
+
* 🔴 它也**不**授权任何人说「这条从来不存在」。
|
|
541
|
+
*/
|
|
542
|
+
export function eraseNotFoundHistoryVerdict(row) {
|
|
543
|
+
// 🔴 `'unreadable'` 与 `true` 同处置:这一格没读成就**没有**资格说「判决那刻没有这条记录」那句更强的话。
|
|
544
|
+
return row.historyUnknown === true || row.historyUnknown === 'unreadable' ? 'unknown' : 'no_row_at_judgment';
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* 「同一个 `requestId` 重发这一口安不安全」的判据单源(三态)。
|
|
548
|
+
* · `'idempotent'` 证据腿:重发读回被钉住的 id 集,绝不重新解析;
|
|
549
|
+
* · `'human_decision'` 降级腿:同一个 `requestId` 是**一次新的请求**(重新解析),重发就是第二次真删 ⇒
|
|
550
|
+
* 任何重试都必须是人做的决定,🔴 不许进自动重试策略;
|
|
551
|
+
* · `'unknown'` 收执读不出 —— 读不出时**不**当幂等(那是最贵的一种猜)。
|
|
552
|
+
*/
|
|
553
|
+
export function memoryEraseReplayPosture(reading) {
|
|
554
|
+
if (reading.kind !== 'present')
|
|
555
|
+
return 'unknown';
|
|
556
|
+
return reading.evidenceCapability === 'journal' ? 'idempotent' : 'human_decision';
|
|
557
|
+
}
|
|
558
|
+
/** `POST …/origin/entries/:entryId/clear` 的 200 体 → 清标回执读数。永不抛。 */
|
|
559
|
+
export function readMemoryOriginClearanceReceipt(body) {
|
|
560
|
+
let o;
|
|
561
|
+
let rawEntryId;
|
|
562
|
+
let rawClearanceId;
|
|
563
|
+
let rawLandedSlug;
|
|
564
|
+
let originReported;
|
|
565
|
+
let origin;
|
|
566
|
+
try {
|
|
567
|
+
if (!plainObject(body))
|
|
568
|
+
return { kind: 'malformed', why: 'body_unreadable' };
|
|
569
|
+
o = body;
|
|
570
|
+
if (!['entryId', 'clearanceId', 'landedSlug'].some((k) => Object.hasOwn(o, k))) {
|
|
571
|
+
return { kind: 'malformed', why: 'envelope_absent' };
|
|
572
|
+
}
|
|
573
|
+
rawEntryId = o.entryId;
|
|
574
|
+
rawClearanceId = o.clearanceId;
|
|
575
|
+
rawLandedSlug = o.landedSlug;
|
|
576
|
+
// 🔴 `origin` **恰读一次**:先 `hasOwn` 判键在不在(不读值),再取一次值 —— 用「在场判 + 取值」两次读的写法,
|
|
577
|
+
// 变化的 getter 能让 `originReported` 说在场而 `origin` 交出 `undefined`。
|
|
578
|
+
origin = Object.hasOwn(o, 'origin') ? o.origin : undefined;
|
|
579
|
+
originReported = origin !== undefined;
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
return { kind: 'malformed', why: 'body_unreadable' };
|
|
583
|
+
}
|
|
584
|
+
if (!nonEmptyString(rawEntryId) || !nonEmptyString(rawClearanceId) || !nonEmptyString(rawLandedSlug)) {
|
|
585
|
+
return { kind: 'malformed', why: 'envelope_unreadable' };
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
kind: 'present',
|
|
589
|
+
entryId: rawEntryId,
|
|
590
|
+
clearanceId: rawClearanceId,
|
|
591
|
+
landedSlug: rawLandedSlug,
|
|
592
|
+
originReported,
|
|
593
|
+
origin,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memoryOriginCapability — `GET /v1/capabilities.memoryOrigin` 的三端共用读面(0.76.1 CC-97a;
|
|
3
|
+
* 与 `memoryCompliance` 及其余能力位兄弟同一套四态词汇,四口走共用工厂 {@link createEngineCapReader})。
|
|
4
|
+
*
|
|
5
|
+
* ── 这一位答的是哪一个问题 ────────────────────────────────────────────────────────────────
|
|
6
|
+
* 「**外源标记人面三口**(`GET /v1/memory/origin/external` · `GET /v1/memory/origin/clearances` ·
|
|
7
|
+
* `POST /v1/memory/origin/entries/:entryId/clear`,operator 车道)在这台引擎上在不在」。引擎侧的谓词
|
|
8
|
+
* 是**两项合取**:外源面在挂载期判出后端自带引擎控制面归属(缺则整口不挂),且 operator 名单**非空**
|
|
9
|
+
* (空名单下三口对任何身份恒 403)。
|
|
10
|
+
* ⚠️ 位为真**只**保证「门能过 + 面在场」,**不**保证这一次调用成功:清标账本损坏是**运行期**事实
|
|
11
|
+
* (typed 500)。
|
|
12
|
+
* ⚠️ 与 `memoryCompliance` **同判据不同位**:两族今天查后端的同一个面,但它们是**两个产品面**
|
|
13
|
+
* (合规问询 / 抹除 · 外源审计 / 清标),一个部署可以只想开其中一族 —— 引擎**刻意不合并**,
|
|
14
|
+
* 即使今天两位恒同值。🔴 本包跟着不合并:两只读器各持一张 per-baseUrl 表,喂其中一只畸形回体
|
|
15
|
+
* 只删它自己那一格,{@link import('./memoryComplianceCapability.js')} 的读数一字不变。把两位
|
|
16
|
+
* 折成一只读器 = 在未来的分家上让消费端读到假话。
|
|
17
|
+
* 🔴 位为真**也不**保证 `GET …/external` 的答案**完备**:引擎 v1 没有 scope 枚举读面,答案只覆盖
|
|
18
|
+
* 调用方点名的那些 scope ⇒ **空答绝不读作「本店干净」**。那是**发现链**的事,能力位表达不了它 ——
|
|
19
|
+
* 本包把这句话实现在导出条目面的判词口上(`scopeExternalOriginVerdict`),不是只写在注释里。
|
|
20
|
+
*
|
|
21
|
+
* ── 🔴 读法(与 `memoryCompliance` 逐条同律,但**各自一张表**)─────────────────────────────
|
|
22
|
+
* ① 引擎铸这一位时**恒在场**(一格布尔)⇒ **键缺席 = 老引擎不报这一位** ⇒ `not_reported`(判不出),
|
|
23
|
+
* 绝不折成 `false`;
|
|
24
|
+
* ② `false` 是**明确的否** ⇒ `absent`;成因(后端无控制面归属 / operator 名单为空)在 wire 上不可分,
|
|
25
|
+
* 措辞不推因由;
|
|
26
|
+
* ③ 严格布尔;`null` / 串 / `0` / `1` / 数组 / 对象一律畸形 ⇒ 删格 ⇒ `unobserved`,🔴 绝不折 `absent`;
|
|
27
|
+
* ③b caps 回体本身不是对象(`null` / 数组 / 串 / 数)⇒ 畸形删格,**不是** `not_reported`(那一态只说「这是
|
|
28
|
+
* 一份 caps,但没有这一格」)。
|
|
29
|
+
* ④ 判在场用 `hasOwn`(原型键永不上 wire);
|
|
30
|
+
* ⑤ 「三口能不能用」的判据归包:{@link memoryOriginVerbsAvailable}。
|
|
31
|
+
*/
|
|
32
|
+
/** 四态读数。`unobserved` 由读口在这一格空缺时铸,不由投影铸。 */
|
|
33
|
+
export type MemoryOriginReading = {
|
|
34
|
+
kind: 'unobserved';
|
|
35
|
+
}
|
|
36
|
+
/** 键缺席:老引擎不报这一位 ⇒ 判不出(**不是** `absent`)。 */
|
|
37
|
+
| {
|
|
38
|
+
kind: 'not_reported';
|
|
39
|
+
}
|
|
40
|
+
/** 位在场且为 `false`:这台部署上三口用不了(成因在 wire 上不可分)。 */
|
|
41
|
+
| {
|
|
42
|
+
kind: 'absent';
|
|
43
|
+
}
|
|
44
|
+
/** 位在场且为 `true`:门能过 + 面在场(既不承诺这一次调用成功,也不承诺外源清单完备)。 */
|
|
45
|
+
| {
|
|
46
|
+
kind: 'present';
|
|
47
|
+
};
|
|
48
|
+
/** caps 回体 → 本格读数;畸形一律 `undefined`(= 这一格不写 ⇒ 读口答 `unobserved`)。 */
|
|
49
|
+
export declare function projectMemoryOriginCapability(caps: unknown): MemoryOriginReading | undefined;
|
|
50
|
+
/** 宿主 caps probe 的读面 tee 落点。绝不 throw;畸形 ⇒ 删格;`opts.generation` 关掉旧探测覆盖新读数的竞态。 */
|
|
51
|
+
export declare function noteEngineCapsForMemoryOrigin(baseUrl: string, caps: unknown, opts?: {
|
|
52
|
+
generation?: number;
|
|
53
|
+
}): void;
|
|
54
|
+
/** 本进程观测到的读数;这一格空缺 ⇒ `{kind:'unobserved'}`。 */
|
|
55
|
+
export declare function observedMemoryOrigin(baseUrl?: string | undefined): MemoryOriginReading;
|
|
56
|
+
/**
|
|
57
|
+
* 「外源标记人面三口能不能用」的判据单源(三态):只有引擎明说在场才 `'yes'`;明说不在 ⇒ `'no'`;
|
|
58
|
+
* 老引擎不报 / 从没观测 ⇒ `'unknown'`。
|
|
59
|
+
* 🔴 `'yes'` **只**说三口够得着,对外源清单的**完备性**一个字都没说(那一问归
|
|
60
|
+
* `scopeExternalOriginVerdict`)。
|
|
61
|
+
*/
|
|
62
|
+
export declare function memoryOriginVerbsAvailable(reading: MemoryOriginReading): 'yes' | 'no' | 'unknown';
|
|
63
|
+
/** doctor 那一行的 detail 串,唯一措辞真源。🔴 两种「读不出」的句子都不许暗示「这台没有外源面」。 */
|
|
64
|
+
export declare function memoryOriginDoctorDetail(reading: MemoryOriginReading): string;
|
|
65
|
+
/** 换代失效口(引擎温切成功后调):清成未观测。空串 ⇒ no-op;绝不 throw。 */
|
|
66
|
+
export declare function forgetMemoryOriginReading(baseUrl: string | undefined): void;
|
|
67
|
+
/** 测试钩子。 */
|
|
68
|
+
export declare function __resetMemoryOriginReadingsForTests(): void;
|