@sema-agent/server 7.37.0 → 7.38.0-rc.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/USAGE.md +2 -1
- package/dist/adoption/plan.js +16 -0
- package/dist/boot/budget-tracing.d.ts +1 -1
- package/dist/boot/budget-tracing.js +12 -5
- package/dist/boot/config-center.d.ts +27 -2
- package/dist/boot/config-center.js +148 -15
- package/dist/boot/limit-sync.d.ts +32 -0
- package/dist/boot/limit-sync.js +35 -0
- package/dist/boot/reapers.d.ts +68 -0
- package/dist/boot/reapers.js +63 -4
- package/dist/boot/resolve-spec.js +2 -0
- package/dist/boot/runner-deps.d.ts +1 -1
- package/dist/boot/runner-deps.js +4 -0
- package/dist/boot/stores.js +13 -0
- package/dist/config-center/apply-effective.d.ts +38 -3
- package/dist/config-center/apply-effective.js +75 -5
- package/dist/config-center/apply-ledger.d.ts +67 -0
- package/dist/config-center/apply-ledger.js +83 -0
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/hot-keys-registry.d.ts +62 -0
- package/dist/config-center/hot-keys-registry.js +54 -0
- package/dist/config-center/http-client.js +12 -2
- package/dist/config-center/restart-signal.js +5 -2
- package/dist/config-center/stage-limits.d.ts +59 -0
- package/dist/config-center/stage-limits.js +188 -0
- package/dist/config-center/types.d.ts +60 -0
- package/dist/config-invariants.d.ts +65 -0
- package/dist/config-invariants.js +139 -0
- package/dist/config-provider.js +11 -0
- package/dist/config-types.d.ts +10 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +37 -5
- package/dist/http/routes/admin-config-refresh.d.ts +20 -0
- package/dist/http/routes/admin-config-refresh.js +43 -0
- package/dist/http/routes/diagnostics.js +4 -0
- package/dist/http/server.d.ts +8 -0
- package/dist/http/server.js +23 -0
- package/dist/main.js +49 -6
- package/dist/memory-sync.js +50 -1
- package/dist/observability/cost-quota.d.ts +23 -3
- package/dist/observability/cost-quota.js +27 -4
- package/dist/observability/fail-open.d.ts +8 -0
- package/dist/observability/fail-open.js +8 -0
- package/dist/observability/rate-limit.d.ts +11 -1
- package/dist/observability/rate-limit.js +8 -2
- package/dist/plan-cache-probe.d.ts +5 -7
- package/dist/plan-cache-probe.js +27 -5
- package/dist/plugins/caching-session-store.d.ts +20 -4
- package/dist/plugins/caching-session-store.js +43 -2
- package/dist/plugins/fork-routing-session-store.d.ts +26 -4
- package/dist/plugins/fork-routing-session-store.js +134 -9
- package/dist/plugins/memory-engine-pg.d.ts +1 -0
- package/dist/plugins/memory-engine-pg.js +34 -4
- package/dist/plugins/memory-engine-tidb.d.ts +1 -0
- package/dist/plugins/memory-engine-tidb.js +17 -2
- package/dist/plugins/memory-origin-law.d.ts +69 -0
- package/dist/plugins/memory-origin-law.js +118 -1
- package/dist/plugins/pg-cost-quota.d.ts +7 -2
- package/dist/plugins/pg-cost-quota.js +25 -2
- package/dist/plugins/pg-rate-limiter.d.ts +7 -2
- package/dist/plugins/pg-rate-limiter.js +20 -2
- package/dist/plugins/pg-session-storage.d.ts +30 -7
- package/dist/plugins/pg-session-storage.js +145 -23
- package/dist/plugins/retention-store-sql.js +10 -1
- package/dist/plugins/session-placement.d.ts +113 -0
- package/dist/plugins/session-placement.js +153 -0
- package/dist/plugins/session-store.js +7 -1
- package/dist/plugins/tidb-cost-quota.d.ts +7 -2
- package/dist/plugins/tidb-cost-quota.js +26 -2
- package/dist/plugins/tidb-pool.js +15 -1
- package/dist/plugins/tidb-rate-limiter.d.ts +9 -2
- package/dist/plugins/tidb-rate-limiter.js +22 -2
- package/dist/plugins/tidb-session-storage.js +10 -2
- package/dist/plugins/tidb-session-store.d.ts +55 -7
- package/dist/plugins/tidb-session-store.js +174 -24
- package/dist/plugins/write-behind-counter.d.ts +17 -2
- package/dist/plugins/write-behind-counter.js +19 -1
- package/dist/runs.d.ts +3 -1
- package/dist/runs.js +5 -2
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +24 -6
- package/package.json +3 -3
|
@@ -25,7 +25,7 @@ import { computeEntryRev, serializeEntryFile } from "@sema-agent/core";
|
|
|
25
25
|
import { pgSafeJsonStringify, pgHasUnstorable } from "./pg-safe-json.js";
|
|
26
26
|
import { assertMemoryScopeWidth, memoryEntryKeyWidthRefusal } from "./memory-key-guards.js"; // R5 批γ + #271 件1:scope/slug 写前宽守卫
|
|
27
27
|
// #307 件6(core 5.46.0 design/336 §2.3-2):external-origin 不可变律 —— 两支 SQL 孪生共用**一份**判据。
|
|
28
|
-
import { createBatchOriginBaseline, originWhitewashRefusal, rememberBatchOrigin, unknownPatchOpRefusal } from "./memory-origin-law.js";
|
|
28
|
+
import { ambiguousOriginRefusal, createBatchOriginBaseline, exposureBandOf, exposureCarriage, originMarkedSqlPredicate, originWhitewashRefusal, rememberBatchOrigin, unknownPatchOpRefusal, } from "./memory-origin-law.js";
|
|
29
29
|
/** Table names (single source). Deliberately DISJOINT from the legacy `agent_memory*` tables —
|
|
30
30
|
* the retired MemoryStore plane and this entry plane must never cross-write. */
|
|
31
31
|
export const PG_MEMORY_ENGINE_TABLES = {
|
|
@@ -133,6 +133,9 @@ export class PgMemoryEngineBackend {
|
|
|
133
133
|
mtimeMs: Number(r.mtime_ms),
|
|
134
134
|
rev: r.rev,
|
|
135
135
|
sizeBytes: Number(r.size_bytes),
|
|
136
|
+
// #319(design/336 §5.2):committed external-origin 事实随**每一个**头面(listHeaders + search)。
|
|
137
|
+
// 归一化只有一份(memory-origin-law.exposureCarriage → core 的 committedOriginOf),两支方言同源。
|
|
138
|
+
...exposureCarriage(fm),
|
|
136
139
|
};
|
|
137
140
|
}
|
|
138
141
|
entryFromRow(r) {
|
|
@@ -163,6 +166,11 @@ export class PgMemoryEngineBackend {
|
|
|
163
166
|
return res.rows.map((r) => this.entryFromRow(r)); // unknown ids: silently absent
|
|
164
167
|
}
|
|
165
168
|
async search(query, scopes, opts) {
|
|
169
|
+
// #319(design/336 §5.2):两带序 —— 未标记先、已标记后,**带序在截断之前**(理由见
|
|
170
|
+
// memory-origin-law.exposureBandOf 的顶注)。缺席/false ⇒ 单带序,与本方法 pre-336 形逐字节同。
|
|
171
|
+
const bands = opts?.exposureBands === true;
|
|
172
|
+
/** 契约序:(带号 ‖ 缺席时恒 0) → score 升 → id。三档共用一只比较器,谁都别自己写一份。 */
|
|
173
|
+
const contractOrder = (a, b) => (bands ? exposureBandOf(a) - exposureBandOf(b) : 0) || a.score - b.score || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
166
174
|
// Normalize the limit ONCE for all three rungs — `Math.floor`
|
|
167
175
|
// makes a fractional limit (0.5) behave like File's `slice` (→ 0 → empty), `!(lim > 0)` also
|
|
168
176
|
// rejects NaN (NaN > 0 is false — it previously reached the native rung's interpolated LIMIT as
|
|
@@ -182,12 +190,23 @@ export class PgMemoryEngineBackend {
|
|
|
182
190
|
const qv = await this.embedder.embed(query);
|
|
183
191
|
if (Array.isArray(qv) && qv.length === this.embedder.dimensions) {
|
|
184
192
|
const vecParam = scopes.length + 1;
|
|
193
|
+
// #319:本档的排序**和截断都在 SQL 里**,所以两带序也必须进 ORDER BY —— 在进程里对一页
|
|
194
|
+
// 已截断的结果重排是排不出来的(那正是契约要拒的「干净条目被饿死」)。带号谓词=不可变律
|
|
195
|
+
// 模块里那条 `committedOriginOf` 的下推形(逐元素跑 core 的行首正则,与 JS 判据机器对表;
|
|
196
|
+
// 为什么不能图省事写成 LIKE,见 originMarkedSqlPredicate 的顶注)。
|
|
197
|
+
const bandExpr = bands ? `(CASE WHEN ${originMarkedSqlPredicate("frontmatter")} THEN 1 ELSE 0 END) ASC, ` : "";
|
|
185
198
|
const res = await this.query(`SELECT id, scope, slug, frontmatter, rev, mtime_ms, size_bytes, embedding <=> $${vecParam}::vector AS dist
|
|
186
199
|
FROM ${PG_MEMORY_ENGINE_TABLES.entry}
|
|
187
200
|
WHERE scope IN (${inPlaceholders(1, scopes.length)}) AND embedding IS NOT NULL
|
|
188
|
-
ORDER BY embedding <=> $${vecParam}::vector ASC
|
|
201
|
+
ORDER BY ${bandExpr}embedding <=> $${vecParam}::vector ASC, id ASC
|
|
189
202
|
LIMIT ${Math.min(limit, 100_000)}`, [...scopes, vectorLiteral(qv)]);
|
|
190
|
-
|
|
203
|
+
// ⚠️ `id ASC` 必须写在 SQL 的 ORDER BY 里、也就是 LIMIT **之前**(codex 对抗复审 R1-[medium]):
|
|
204
|
+
// 同分(同一条向量距离)超过 limit 条时,决定「谁被截掉」的是这一句;缺了它,留下哪几条由执行
|
|
205
|
+
// 计划随机决定,页内怎么重排都换不回被截掉的那条。列是 `text COLLATE "C"` ⇒ 字节序,与下面
|
|
206
|
+
// 进程内比较器的定序同向。
|
|
207
|
+
const hits = res.rows.map((r) => ({ ...this.headerFromRow(r), score: Number(r.dist) }));
|
|
208
|
+
// 页内再排一次:契约序的最终裁决在进程侧(单带时是恒等重排)。
|
|
209
|
+
return hits.sort(contractOrder);
|
|
191
210
|
}
|
|
192
211
|
// Wrong-dimension query vector (transient embedder fault) — fall open to the in-process path.
|
|
193
212
|
}
|
|
@@ -214,7 +233,8 @@ export class PgMemoryEngineBackend {
|
|
|
214
233
|
}
|
|
215
234
|
// opus 审 C4: deterministic id tie-break — tied scores must not order by row-arrival accident
|
|
216
235
|
// (the cross-backend equivalence assertion compares ordered projections; File mirrors this).
|
|
217
|
-
|
|
236
|
+
// #319:整档在进程内排完**再**切片 ⇒ 两带序天然落在截断之前(SQL 侧无 LIMIT 可绕过)。
|
|
237
|
+
scored.sort(contractOrder);
|
|
218
238
|
return scored.slice(0, limit);
|
|
219
239
|
}
|
|
220
240
|
async applyPatches(patches) {
|
|
@@ -293,6 +313,16 @@ export class PgMemoryEngineBackend {
|
|
|
293
313
|
return;
|
|
294
314
|
}
|
|
295
315
|
}
|
|
316
|
+
// #319 增量([4768] 勘误 + core `1a5ebaa1`):**歧义 origin 表示**零写拒。判位与 File 的 planOne
|
|
317
|
+
// 逐字同 —— 表示合法性先于状态比较:排在键形守卫之后、add/update 两支的 guard/跨 scope/白洗/CAS
|
|
318
|
+
// 之前,且在任何 I/O 之前。判据用 core 导出的谓词(禁手抄),文案照抄 File(跨后端同一输入同一答复)。
|
|
319
|
+
if (patch.op !== "delete" && patch.entry !== undefined) {
|
|
320
|
+
const refusal = ambiguousOriginRefusal(patch.entry.frontmatter);
|
|
321
|
+
if (refusal !== undefined) {
|
|
322
|
+
report.conflicts.push({ op: patch.op, id: patch.id, reason: refusal });
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
296
326
|
if (patch.op === "add") {
|
|
297
327
|
const rawEntry = patch.entry;
|
|
298
328
|
if (!rawEntry) {
|
|
@@ -51,6 +51,7 @@ export declare class TiDBMemoryEngineBackend implements MemoryBackend {
|
|
|
51
51
|
getByIds(ids: readonly string[]): Promise<MemoryEntry[]>;
|
|
52
52
|
search(query: string, scopes: readonly string[], opts?: {
|
|
53
53
|
limit?: number;
|
|
54
|
+
exposureBands?: boolean;
|
|
54
55
|
}): Promise<ScoredMemoryEntry[]>;
|
|
55
56
|
applyPatches(patches: readonly NotePatch[]): Promise<PatchReport>;
|
|
56
57
|
private applyOne;
|
|
@@ -26,7 +26,7 @@ import { computeEntryRev, serializeEntryFile } from "@sema-agent/core";
|
|
|
26
26
|
import { pgHasUnstorable } from "./pg-safe-json.js";
|
|
27
27
|
import { assertMemoryScopeWidth, memoryEntryKeyWidthRefusal } from "./memory-key-guards.js"; // R5 批γ + #271 件1:scope/slug 写前宽守卫
|
|
28
28
|
// #307 件6(core 5.46.0 design/336 §2.3-2):external-origin 不可变律 —— 与 Pg 孪生共用**一份**判据。
|
|
29
|
-
import { createBatchOriginBaseline, originWhitewashRefusal, rememberBatchOrigin, unknownPatchOpRefusal } from "./memory-origin-law.js";
|
|
29
|
+
import { ambiguousOriginRefusal, createBatchOriginBaseline, exposureBandOf, exposureCarriage, originWhitewashRefusal, rememberBatchOrigin, unknownPatchOpRefusal, } from "./memory-origin-law.js";
|
|
30
30
|
import { isMysqlDupKeyError } from "./sql-errors.js";
|
|
31
31
|
/** Table names (single source) — SAME names as PG_MEMORY_ENGINE_TABLES (the two dialects never share
|
|
32
32
|
* one database), deliberately DISJOINT from the legacy `agent_memory*` MemoryStore plane. */
|
|
@@ -117,6 +117,8 @@ export class TiDBMemoryEngineBackend {
|
|
|
117
117
|
mtimeMs: Number(r.mtime_ms),
|
|
118
118
|
rev: r.rev,
|
|
119
119
|
sizeBytes: Number(r.size_bytes),
|
|
120
|
+
// #319(design/336 §5.2,Pg 孪生同注):committed external-origin 事实随每一个头面;归一化同源。
|
|
121
|
+
...exposureCarriage(fm),
|
|
120
122
|
};
|
|
121
123
|
}
|
|
122
124
|
entryFromRow(r) {
|
|
@@ -146,6 +148,9 @@ export class TiDBMemoryEngineBackend {
|
|
|
146
148
|
return rows.map((r) => this.entryFromRow(r)); // unknown ids: silently absent
|
|
147
149
|
}
|
|
148
150
|
async search(query, scopes, opts) {
|
|
151
|
+
// #319(design/336 §5.2,Pg 孪生同注):两带序 —— 未标记先,**带序在截断之前**。本方言只有词面档,
|
|
152
|
+
// 整档在进程内排完再切片,所以「带序先于截断」天然成立(无 SQL LIMIT 可绕过)。
|
|
153
|
+
const bands = opts?.exposureBands === true;
|
|
149
154
|
// Limit normalization mirrors the Pg twin verbatim (floor semantics; NaN rejected; Infinity = no cap).
|
|
150
155
|
const rawLimit = opts?.limit ?? 20;
|
|
151
156
|
const limit = Number.isFinite(rawLimit) ? Math.floor(rawLimit) : rawLimit;
|
|
@@ -168,7 +173,7 @@ export class TiDBMemoryEngineBackend {
|
|
|
168
173
|
}
|
|
169
174
|
// Deterministic id tie-break (opus 审 C4,Pg 版同注): tied scores must not order by row-arrival
|
|
170
175
|
// accident — the cross-backend equivalence assertion compares ordered projections.
|
|
171
|
-
scored.sort((a, b) => a.score - b.score || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
176
|
+
scored.sort((a, b) => (bands ? exposureBandOf(a) - exposureBandOf(b) : 0) || a.score - b.score || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
172
177
|
return scored.slice(0, limit);
|
|
173
178
|
}
|
|
174
179
|
async applyPatches(patches) {
|
|
@@ -242,6 +247,16 @@ export class TiDBMemoryEngineBackend {
|
|
|
242
247
|
return;
|
|
243
248
|
}
|
|
244
249
|
}
|
|
250
|
+
// #319 增量([4768] 勘误 + core `1a5ebaa1`,Pg 孪生同注):**歧义 origin 表示**零写拒。判位与
|
|
251
|
+
// File 的 planOne 逐字同(表示合法性先于状态比较,且在任何 I/O 之前);判据用 core 导出的谓词
|
|
252
|
+
// (禁手抄——解 extra 里的 origin 形块那只解析器一旦解得比 core 宽松,就是给洗白开后门)。
|
|
253
|
+
if (patch.op !== "delete" && patch.entry !== undefined) {
|
|
254
|
+
const refusal = ambiguousOriginRefusal(patch.entry.frontmatter);
|
|
255
|
+
if (refusal !== undefined) {
|
|
256
|
+
report.conflicts.push({ op: patch.op, id: patch.id, reason: refusal });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
245
260
|
if (patch.op === "add") {
|
|
246
261
|
const entry = patch.entry;
|
|
247
262
|
if (!entry) {
|
|
@@ -37,6 +37,75 @@
|
|
|
37
37
|
* (core 契约逐字要求的那条)才成立。
|
|
38
38
|
*/
|
|
39
39
|
import { type MemoryEntryFrontmatter, type MemoryEntryOrigin } from "@sema-agent/core";
|
|
40
|
+
/**
|
|
41
|
+
* #319 增量(sema-comms [4768] 勘误 + core 仓 `1a5ebaa1`,conformance 30→31)—— **歧义 origin 表示**
|
|
42
|
+
* 的拒绝门,与不可变律同批、同一个写路径上。
|
|
43
|
+
*
|
|
44
|
+
* ## 病
|
|
45
|
+
*
|
|
46
|
+
* 同一条目**同时**携带 typed `frontmatter.origin` 与一个**冲突的** origin 形 `extra` 块(或多个彼此
|
|
47
|
+
* 冲突的 extra 块)时:不可变律的比较器走 `committedOriginOf`,而它在 typed 在场时优先读 typed ——
|
|
48
|
+
* 于是那枚 typed 满足「标记原样带下去」,冲突的块却照样进了存储。任何一次**重解析**(File 的投影
|
|
49
|
+
* 往返 / sync 对拍 / bundle 校验)都可能答出**另一枚**标记 ⇒ 成员改写的载具。不可变律本身没漏,
|
|
50
|
+
* 漏的是「根本不该收下这种表示」这一步。
|
|
51
|
+
*
|
|
52
|
+
* ## 钥匙在 DISAGREEMENT,不在载体数
|
|
53
|
+
*
|
|
54
|
+
* 载体彼此**一致**的多载形(typed 旁边一个逐成员相等的 extra 形)是合法的 legacy 承载,必须照收 ——
|
|
55
|
+
* 那正是序列化闭合(同一条记录在 File 投影里往返一圈就长这样)。「见到 extra 就拒」会把正常的
|
|
56
|
+
* legacy 条目全部打死。
|
|
57
|
+
*
|
|
58
|
+
* ## 判据禁手抄
|
|
59
|
+
*
|
|
60
|
+
* 谓词一律用 core 导出的 {@link ambiguousOriginRepresentation}:它要解 `extra` 里的 origin 形块
|
|
61
|
+
* (跨块字段组合、解析期块重排都算歧义),自己写一份近似的等于给洗白开后门 —— 这条在 #307 就写死过。
|
|
62
|
+
* core 5.47.0 起该谓词进公开导出面([4698] 请托兑现),本仓的「撤除条件钉」也因此在 5.47 提货当天
|
|
63
|
+
* 准时翻红,本批就是那条钉指定的兑现动作。
|
|
64
|
+
*
|
|
65
|
+
* ## 文案与 File 逐字同
|
|
66
|
+
*
|
|
67
|
+
* 拒绝文案照抄 core `file-backend.ts` 的 `planOne`(亲读 `1a5ebaa1` 前后两版确认未变),因为
|
|
68
|
+
* 契约按 `/representation conflict|malformed patch refused/` 匹配,而**跨后端同一输入必须同一答复**
|
|
69
|
+
* —— 消费方按 reason 分类处置,两支方言各写各的文案就等于让同一件事在不同部署上分叉。
|
|
70
|
+
*
|
|
71
|
+
* ## 判位
|
|
72
|
+
*
|
|
73
|
+
* 与 File 同precedence:**表示合法性先于状态比较** —— 排在 id/键形守卫之后、add/update 分支
|
|
74
|
+
* (guard 算术、跨 scope 拒、白洗判据、rev CAS)之前,且在**任何 I/O 之前**(零写拒)。
|
|
75
|
+
*/
|
|
76
|
+
export declare function ambiguousOriginRefusal(fm: MemoryEntryFrontmatter): string | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* #319(core 5.47/5.48 契约冻结面,design/336 §5.2)—— **头面 exposure 事实**的同一份归一化。
|
|
79
|
+
*
|
|
80
|
+
* 契约逐字:`listHeaders` 与 `search` 两个头面都要携带 `exposure`,且**同一条归一化**
|
|
81
|
+
* (`committedOriginOf`:typed `frontmatter.origin` 与 legacy 的 `extra` origin 形字节块两种承载,
|
|
82
|
+
* 一次判、两面同答)。这是**数据事实**不是模式:后端恒报库里存着什么,读侧怎么处置(不透明句柄/
|
|
83
|
+
* 横幅/两带序)归引擎层的 provenance 模式。缺席 ⇔ 没标记(不等于「证明干净」)。
|
|
84
|
+
*
|
|
85
|
+
* 为什么与不可变律同住一个模块:两者读的是**同一个** committed origin。判据(白洗拒)与事实
|
|
86
|
+
* (exposure 携带)分家写,迟早在某一支方言上一边认得 extra 形、另一边只认 typed 形 —— 那时
|
|
87
|
+
* 「洗不掉的标记」在检索面**看不见**,而两支后端谁都不会报错。
|
|
88
|
+
*/
|
|
89
|
+
export declare function exposureCarriage(fm: MemoryEntryFrontmatter): {
|
|
90
|
+
exposure?: "external";
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* 两带序的带号:未标记 = 0(**先**),已标记 = 1。
|
|
94
|
+
*
|
|
95
|
+
* 契约的载荷理由(core r1-9,逐字转述):两带序必须落在**截断之前** —— 一页 limit 全是带标条目时,
|
|
96
|
+
* 排在 limit+1 位的干净条目会被饿死,而工具层**无法**从一页已截断的结果里把它捞回来。
|
|
97
|
+
* `exposureBands` 缺席/false ⇒ 单带序,与 pre-336 逐字节同(`provenance:"off"` 的读面)。
|
|
98
|
+
*/
|
|
99
|
+
export declare function exposureBandOf(header: {
|
|
100
|
+
exposure?: "external";
|
|
101
|
+
}): 0 | 1;
|
|
102
|
+
/** core `/^\s*origin\s*:/`(`extraOriginCarriers` 的开块判据)的 SQL 孪生。 */
|
|
103
|
+
export declare const ORIGIN_CARRIER_LINE_SQL_REGEX = "^[\\u0009\\u000a\\u000b\\u000c\\u000d\\u0020\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]*origin[\\u0009\\u000a\\u000b\\u000c\\u000d\\u0020\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]*:";
|
|
104
|
+
/**
|
|
105
|
+
* 「这一行带 committed origin 标记」的 SQL 布尔式(两种承载形同判,与 {@link exposureCarriage} 同源)。
|
|
106
|
+
* `column` = 该表 frontmatter 列的表达式(调用方自己保证它是列名/限定名,**不接受外来串**)。
|
|
107
|
+
*/
|
|
108
|
+
export declare function originMarkedSqlPredicate(column: string): string;
|
|
40
109
|
/** 本批次的「已提交 origin」基线:id → 本批**第一次**触碰它时库里那一份(无行 ⇒ `undefined`)。 */
|
|
41
110
|
export type BatchOriginBaseline = Map<string, MemoryEntryOrigin | undefined>;
|
|
42
111
|
/** 新建一只批次基线(每次 `applyPatches` 一只;跨批次**不得**复用 —— 已提交的墓碑正是靠「换批清零」放行)。 */
|
|
@@ -36,7 +36,124 @@
|
|
|
36
36
|
* 之后同批的任何 op 都按那一份判。⇒「同批 delete+re-add 拒 / 已提交墓碑后 add 放」这条分界
|
|
37
37
|
* (core 契约逐字要求的那条)才成立。
|
|
38
38
|
*/
|
|
39
|
-
import { committedOriginOf, originEquals } from "@sema-agent/core";
|
|
39
|
+
import { ambiguousOriginRepresentation, committedOriginOf, originEquals } from "@sema-agent/core";
|
|
40
|
+
/**
|
|
41
|
+
* #319 增量(sema-comms [4768] 勘误 + core 仓 `1a5ebaa1`,conformance 30→31)—— **歧义 origin 表示**
|
|
42
|
+
* 的拒绝门,与不可变律同批、同一个写路径上。
|
|
43
|
+
*
|
|
44
|
+
* ## 病
|
|
45
|
+
*
|
|
46
|
+
* 同一条目**同时**携带 typed `frontmatter.origin` 与一个**冲突的** origin 形 `extra` 块(或多个彼此
|
|
47
|
+
* 冲突的 extra 块)时:不可变律的比较器走 `committedOriginOf`,而它在 typed 在场时优先读 typed ——
|
|
48
|
+
* 于是那枚 typed 满足「标记原样带下去」,冲突的块却照样进了存储。任何一次**重解析**(File 的投影
|
|
49
|
+
* 往返 / sync 对拍 / bundle 校验)都可能答出**另一枚**标记 ⇒ 成员改写的载具。不可变律本身没漏,
|
|
50
|
+
* 漏的是「根本不该收下这种表示」这一步。
|
|
51
|
+
*
|
|
52
|
+
* ## 钥匙在 DISAGREEMENT,不在载体数
|
|
53
|
+
*
|
|
54
|
+
* 载体彼此**一致**的多载形(typed 旁边一个逐成员相等的 extra 形)是合法的 legacy 承载,必须照收 ——
|
|
55
|
+
* 那正是序列化闭合(同一条记录在 File 投影里往返一圈就长这样)。「见到 extra 就拒」会把正常的
|
|
56
|
+
* legacy 条目全部打死。
|
|
57
|
+
*
|
|
58
|
+
* ## 判据禁手抄
|
|
59
|
+
*
|
|
60
|
+
* 谓词一律用 core 导出的 {@link ambiguousOriginRepresentation}:它要解 `extra` 里的 origin 形块
|
|
61
|
+
* (跨块字段组合、解析期块重排都算歧义),自己写一份近似的等于给洗白开后门 —— 这条在 #307 就写死过。
|
|
62
|
+
* core 5.47.0 起该谓词进公开导出面([4698] 请托兑现),本仓的「撤除条件钉」也因此在 5.47 提货当天
|
|
63
|
+
* 准时翻红,本批就是那条钉指定的兑现动作。
|
|
64
|
+
*
|
|
65
|
+
* ## 文案与 File 逐字同
|
|
66
|
+
*
|
|
67
|
+
* 拒绝文案照抄 core `file-backend.ts` 的 `planOne`(亲读 `1a5ebaa1` 前后两版确认未变),因为
|
|
68
|
+
* 契约按 `/representation conflict|malformed patch refused/` 匹配,而**跨后端同一输入必须同一答复**
|
|
69
|
+
* —— 消费方按 reason 分类处置,两支方言各写各的文案就等于让同一件事在不同部署上分叉。
|
|
70
|
+
*
|
|
71
|
+
* ## 判位
|
|
72
|
+
*
|
|
73
|
+
* 与 File 同precedence:**表示合法性先于状态比较** —— 排在 id/键形守卫之后、add/update 分支
|
|
74
|
+
* (guard 算术、跨 scope 拒、白洗判据、rev CAS)之前,且在**任何 I/O 之前**(零写拒)。
|
|
75
|
+
*/
|
|
76
|
+
export function ambiguousOriginRefusal(fm) {
|
|
77
|
+
if (!ambiguousOriginRepresentation(fm))
|
|
78
|
+
return undefined;
|
|
79
|
+
return "external-origin marker representation conflict: the entry carries conflicting or duplicated origin representations (malformed patch refused)";
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* #319(core 5.47/5.48 契约冻结面,design/336 §5.2)—— **头面 exposure 事实**的同一份归一化。
|
|
83
|
+
*
|
|
84
|
+
* 契约逐字:`listHeaders` 与 `search` 两个头面都要携带 `exposure`,且**同一条归一化**
|
|
85
|
+
* (`committedOriginOf`:typed `frontmatter.origin` 与 legacy 的 `extra` origin 形字节块两种承载,
|
|
86
|
+
* 一次判、两面同答)。这是**数据事实**不是模式:后端恒报库里存着什么,读侧怎么处置(不透明句柄/
|
|
87
|
+
* 横幅/两带序)归引擎层的 provenance 模式。缺席 ⇔ 没标记(不等于「证明干净」)。
|
|
88
|
+
*
|
|
89
|
+
* 为什么与不可变律同住一个模块:两者读的是**同一个** committed origin。判据(白洗拒)与事实
|
|
90
|
+
* (exposure 携带)分家写,迟早在某一支方言上一边认得 extra 形、另一边只认 typed 形 —— 那时
|
|
91
|
+
* 「洗不掉的标记」在检索面**看不见**,而两支后端谁都不会报错。
|
|
92
|
+
*/
|
|
93
|
+
export function exposureCarriage(fm) {
|
|
94
|
+
return committedOriginOf(fm) !== undefined ? { exposure: "external" } : {};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 两带序的带号:未标记 = 0(**先**),已标记 = 1。
|
|
98
|
+
*
|
|
99
|
+
* 契约的载荷理由(core r1-9,逐字转述):两带序必须落在**截断之前** —— 一页 limit 全是带标条目时,
|
|
100
|
+
* 排在 limit+1 位的干净条目会被饿死,而工具层**无法**从一页已截断的结果里把它捞回来。
|
|
101
|
+
* `exposureBands` 缺席/false ⇒ 单带序,与 pre-336 逐字节同(`provenance:"off"` 的读面)。
|
|
102
|
+
*/
|
|
103
|
+
export function exposureBandOf(header) {
|
|
104
|
+
return header.exposure === "external" ? 1 : 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* `committedOriginOf` 的**下推形**:同一条判据的 SQL 写法,给「排序与截断都发生在 SQL 里」的那一档
|
|
108
|
+
* (Pg 的原生 pgvector 档)用。
|
|
109
|
+
*
|
|
110
|
+
* ## 为什么非要下推(不能只在进程里排)
|
|
111
|
+
*
|
|
112
|
+
* 原生档 `ORDER BY … LIMIT n` 是**库里**排完就截断的:进程拿到的已经是一页 n 行,被截掉的那条干净
|
|
113
|
+
* 条目在页里根本不存在,重排救不回来 —— 而「带序必须先于截断」正是这条契约的载荷所在。所以带号
|
|
114
|
+
* 必须进 ORDER BY,也就必须有一份 SQL 侧的判据。
|
|
115
|
+
*
|
|
116
|
+
* ## 为什么写成逐元素的行首正则(codex 对抗复审 R1-[high] 的直接产物)
|
|
117
|
+
*
|
|
118
|
+
* 初版图省事写的是 `frontmatter->>'extra' LIKE '%origin:%'`,自称「只多认不漏认」。**错了**,两个方向
|
|
119
|
+
* 都不成立:`['origin :']`(冒号前有空格)是 core 认的合法承载形,而 LIKE 漏掉它 —— 一条**带标记**的
|
|
120
|
+
* 条目被排进干净带,占掉的正是干净条目的位置;反过来 `['prefix origin: suffix']` 被 LIKE 误认,一条
|
|
121
|
+
* **干净**条目沉进后带、可能被截断饿死。两个方向的结局是同一个:干净条目被饿死,而这正是本条款要拒的。
|
|
122
|
+
*
|
|
123
|
+
* 现形逐元素跑 core 那条行首正则(`/^\s*origin\s*:/`)。真库实测 10 种拼写(裸 / 冒号前空格 / 前导
|
|
124
|
+
* 空格 / 制表 / 全角空格 U+3000 / 行中出现 / originx: / 非 origin 行 …)与 JS 判据**逐条同答**。
|
|
125
|
+
*
|
|
126
|
+
* ## 这份「第二实现」的同步义务
|
|
127
|
+
*
|
|
128
|
+
* 两份实现同表一个语义 = 本仓明令的危险形,所以它带**机器对表**:
|
|
129
|
+
* `test/memory-engine-db-integration.test.ts` 的钉6 拿同一组拼写在**真 PG** 上让本谓词与
|
|
130
|
+
* `committedOriginOf` 逐条对答,不一致当场红(离线格做不了这件事:被测的正是 PG 自己的正则引擎)。
|
|
131
|
+
* core 改那条正则时,红的是对表格 —— 不是某台部署上悄悄跑偏的排序。
|
|
132
|
+
*
|
|
133
|
+
* ## 字符类为什么是**穷举码点**而不是 `[[:space:]]`(codex 第二轮 R2-[medium],采纳)
|
|
134
|
+
*
|
|
135
|
+
* PG 的 POSIX 字符类**跟着数据库 LC_CTYPE 走**:同一条 SQL 在 C locale 与某个 UTF-8 locale 下认的
|
|
136
|
+
* 空白集合可以不一样(例如 U+0085 NEL —— JS 的 `\s` **不**含它)。而本谓词的正确性判据是「与 JS 的
|
|
137
|
+
* `\s` 逐字同」,一条依赖部署 locale 的判据等于把同一条记忆在两台机器上排进不同的带 —— 而带号错了
|
|
138
|
+
* 就会在 LIMIT 那一刀上不可恢复地截掉本该入页的干净条目。所以这里**不用**任何 locale 敏感构造,把
|
|
139
|
+
* ECMAScript `\s` 的码点逐个写死(PG 的 ARE 支持 `\uXXXX` 转义,已在真库上验过);对表格里同时钉了
|
|
140
|
+
* U+0085 这条**反例**(JS 不认 ⇒ SQL 也必须不认),locale 一旦重新混进来它当场红。
|
|
141
|
+
*/
|
|
142
|
+
/** ⚠️ 本常量**只准写 ASCII**:那些 unicode 空白必须以 `\\uXXXX` 的**字面反斜杠形**交给 PG 的 ARE
|
|
143
|
+
* 自己解析(已在真库上验过)。把真字符敲进源码会同时踩两条线 —— 源码卫生门(不可见字节)与
|
|
144
|
+
* U+2028/U+2029(它们在 JS 源码里就是换行,会把这一行劈开)。 */
|
|
145
|
+
const SQL_JS_WHITESPACE_CLASS = "[\\u0009\\u000a\\u000b\\u000c\\u000d\\u0020\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]";
|
|
146
|
+
/** core `/^\s*origin\s*:/`(`extraOriginCarriers` 的开块判据)的 SQL 孪生。 */
|
|
147
|
+
export const ORIGIN_CARRIER_LINE_SQL_REGEX = `^${SQL_JS_WHITESPACE_CLASS}*origin${SQL_JS_WHITESPACE_CLASS}*:`;
|
|
148
|
+
/**
|
|
149
|
+
* 「这一行带 committed origin 标记」的 SQL 布尔式(两种承载形同判,与 {@link exposureCarriage} 同源)。
|
|
150
|
+
* `column` = 该表 frontmatter 列的表达式(调用方自己保证它是列名/限定名,**不接受外来串**)。
|
|
151
|
+
*/
|
|
152
|
+
export function originMarkedSqlPredicate(column) {
|
|
153
|
+
return (`(${column}->'origin' IS NOT NULL OR (jsonb_typeof(${column}->'extra') = 'array' AND EXISTS (` +
|
|
154
|
+
`SELECT 1 FROM jsonb_array_elements_text(${column}->'extra') AS origin_line(line) ` +
|
|
155
|
+
`WHERE origin_line.line ~ '${ORIGIN_CARRIER_LINE_SQL_REGEX}')))`);
|
|
156
|
+
}
|
|
40
157
|
/** 新建一只批次基线(每次 `applyPatches` 一只;跨批次**不得**复用 —— 已提交的墓碑正是靠「换批清零」放行)。 */
|
|
41
158
|
export function createBatchOriginBaseline() {
|
|
42
159
|
return new Map();
|
|
@@ -12,12 +12,17 @@
|
|
|
12
12
|
* and branched THERE. Only the table binding lives here.
|
|
13
13
|
*/
|
|
14
14
|
import type { Pool, PoolClient } from "pg";
|
|
15
|
-
import type { QuotaDecision, QuotaTracker } from "../observability/cost-quota.js";
|
|
15
|
+
import type { QuotaDecision, QuotaLimits, QuotaTracker } from "../observability/cost-quota.js";
|
|
16
16
|
import { type CounterDegradeHook } from "./write-behind-counter.js";
|
|
17
17
|
export declare class PgCostQuota implements QuotaTracker {
|
|
18
|
-
private readonly limitMicroUsd;
|
|
19
18
|
private readonly counter;
|
|
19
|
+
/** #322:与 TiDB 孪生逐字同语义(限额=比较参数;关断哨兵零 SQL;窗长换代=换桶)。 */
|
|
20
|
+
private limitMicroUsd;
|
|
21
|
+
private refreshRequested;
|
|
22
|
+
private refreshIntervalMs;
|
|
20
23
|
constructor(pool: Pool, limitMicroUsd: number, windowMs: number, now?: () => number, onDegraded?: CounterDegradeHook, queryTimeoutMs?: number);
|
|
24
|
+
setLimits(next: QuotaLimits): void;
|
|
25
|
+
private syncRefresh;
|
|
21
26
|
check(scopeKey: string): QuotaDecision;
|
|
22
27
|
add(scopeKey: string, micro: number): void;
|
|
23
28
|
flush(): Promise<void>;
|
|
@@ -2,18 +2,38 @@ import { PgWriteBehindCounter } from "./write-behind-counter.js";
|
|
|
2
2
|
/** The counter's table binding for the cumulative cost quota (mirror of the TiDB twin's binding). */
|
|
3
3
|
const COST_QUOTA_TABLE = { table: "cost_quota", keyCol: "scope_key", valCol: "micro_usd" };
|
|
4
4
|
export class PgCostQuota {
|
|
5
|
-
limitMicroUsd;
|
|
6
5
|
counter;
|
|
6
|
+
/** #322:与 TiDB 孪生逐字同语义(限额=比较参数;关断哨兵零 SQL;窗长换代=换桶)。 */
|
|
7
|
+
limitMicroUsd;
|
|
8
|
+
refreshRequested = false;
|
|
9
|
+
refreshIntervalMs;
|
|
7
10
|
constructor(pool, limitMicroUsd, windowMs, now, onDegraded, queryTimeoutMs) {
|
|
8
11
|
this.limitMicroUsd = limitMicroUsd;
|
|
9
12
|
this.counter = new PgWriteBehindCounter(pool, COST_QUOTA_TABLE, windowMs, now, onDegraded, queryTimeoutMs);
|
|
10
13
|
}
|
|
14
|
+
setLimits(next) {
|
|
15
|
+
this.limitMicroUsd = next.limitMicroUsd;
|
|
16
|
+
this.counter.setWindowMs(next.windowMs);
|
|
17
|
+
this.syncRefresh();
|
|
18
|
+
}
|
|
19
|
+
syncRefresh() {
|
|
20
|
+
if (!this.refreshRequested)
|
|
21
|
+
return;
|
|
22
|
+
if (this.limitMicroUsd > 0)
|
|
23
|
+
this.counter.startRefresh(this.refreshIntervalMs);
|
|
24
|
+
else
|
|
25
|
+
this.counter.stop();
|
|
26
|
+
}
|
|
11
27
|
check(scopeKey) {
|
|
28
|
+
if (this.limitMicroUsd <= 0)
|
|
29
|
+
return { allowed: true, usedMicroUsd: 0, limitMicroUsd: 0, retryAfterSec: 0 };
|
|
12
30
|
const used = this.counter.used(scopeKey);
|
|
13
31
|
const allowed = used < this.limitMicroUsd;
|
|
14
32
|
return { allowed, usedMicroUsd: used, limitMicroUsd: this.limitMicroUsd, retryAfterSec: allowed ? 0 : this.counter.retryAfterSec() };
|
|
15
33
|
}
|
|
16
34
|
add(scopeKey, micro) {
|
|
35
|
+
if (this.limitMicroUsd <= 0)
|
|
36
|
+
return;
|
|
17
37
|
this.counter.addLocal(scopeKey, micro);
|
|
18
38
|
}
|
|
19
39
|
// write-behind lifecycle (delegated; exposed for the reaper/tests)
|
|
@@ -27,10 +47,13 @@ export class PgCostQuota {
|
|
|
27
47
|
return this.counter.reap();
|
|
28
48
|
}
|
|
29
49
|
startRefresh(intervalMs) {
|
|
30
|
-
this.
|
|
50
|
+
this.refreshRequested = true;
|
|
51
|
+
this.refreshIntervalMs = intervalMs;
|
|
52
|
+
this.syncRefresh();
|
|
31
53
|
return this;
|
|
32
54
|
}
|
|
33
55
|
stop() {
|
|
56
|
+
this.refreshRequested = false;
|
|
34
57
|
this.counter.stop();
|
|
35
58
|
}
|
|
36
59
|
}
|
|
@@ -14,12 +14,17 @@
|
|
|
14
14
|
* self-contained {@link PG_RATE_LIMITER_SCHEMA} + {@link ensureSchema} is safe (no shadowing).
|
|
15
15
|
*/
|
|
16
16
|
import type { Pool, PoolClient } from "pg";
|
|
17
|
-
import type { RateDecision, RateGate } from "../observability/rate-limit.js";
|
|
17
|
+
import type { RateDecision, RateGate, RateLimits } from "../observability/rate-limit.js";
|
|
18
18
|
import { type CounterDegradeHook } from "./write-behind-counter.js";
|
|
19
19
|
export declare class PgRateLimiter implements RateGate {
|
|
20
|
-
private readonly limit;
|
|
21
20
|
private readonly counter;
|
|
21
|
+
/** #322:与 TiDB 孪生逐字同语义(限额=比较参数;关断哨兵停/起刷新环)。 */
|
|
22
|
+
private limit;
|
|
23
|
+
private refreshRequested;
|
|
24
|
+
private refreshIntervalMs;
|
|
22
25
|
constructor(pool: Pool, limit: number, windowMs?: number, now?: () => number, onDegraded?: CounterDegradeHook, queryTimeoutMs?: number);
|
|
26
|
+
setLimits(next: RateLimits): void;
|
|
27
|
+
private syncRefresh;
|
|
23
28
|
check(key: string): RateDecision;
|
|
24
29
|
/** The flush loop self-reaps old windows; the periodic sweeper has nothing to do for this backend. */
|
|
25
30
|
sweep(): void;
|
|
@@ -2,12 +2,27 @@ import { PgWriteBehindCounter } from "./write-behind-counter.js";
|
|
|
2
2
|
/** The counter's table binding for the per-key request rate limit (mirror of the TiDB twin's binding). */
|
|
3
3
|
const RATE_LIMIT_TABLE = { table: "rate_limit", keyCol: "limit_key", valCol: "hits" };
|
|
4
4
|
export class PgRateLimiter {
|
|
5
|
-
limit;
|
|
6
5
|
counter;
|
|
6
|
+
/** #322:与 TiDB 孪生逐字同语义(限额=比较参数;关断哨兵停/起刷新环)。 */
|
|
7
|
+
limit;
|
|
8
|
+
refreshRequested = false;
|
|
9
|
+
refreshIntervalMs;
|
|
7
10
|
constructor(pool, limit, windowMs = 60_000, now, onDegraded, queryTimeoutMs) {
|
|
8
11
|
this.limit = limit;
|
|
9
12
|
this.counter = new PgWriteBehindCounter(pool, RATE_LIMIT_TABLE, windowMs, now, onDegraded, queryTimeoutMs);
|
|
10
13
|
}
|
|
14
|
+
setLimits(next) {
|
|
15
|
+
this.limit = next.rateLimitPerMin;
|
|
16
|
+
this.syncRefresh();
|
|
17
|
+
}
|
|
18
|
+
syncRefresh() {
|
|
19
|
+
if (!this.refreshRequested)
|
|
20
|
+
return;
|
|
21
|
+
if (this.limit > 0)
|
|
22
|
+
this.counter.startRefresh(this.refreshIntervalMs);
|
|
23
|
+
else
|
|
24
|
+
this.counter.stop();
|
|
25
|
+
}
|
|
11
26
|
check(key) {
|
|
12
27
|
if (this.limit <= 0)
|
|
13
28
|
return { allowed: true, retryAfterSec: 0, remaining: Number.POSITIVE_INFINITY };
|
|
@@ -29,10 +44,13 @@ export class PgRateLimiter {
|
|
|
29
44
|
return this.counter.refresh();
|
|
30
45
|
}
|
|
31
46
|
startRefresh(intervalMs) {
|
|
32
|
-
this.
|
|
47
|
+
this.refreshRequested = true;
|
|
48
|
+
this.refreshIntervalMs = intervalMs;
|
|
49
|
+
this.syncRefresh();
|
|
33
50
|
return this;
|
|
34
51
|
}
|
|
35
52
|
stop() {
|
|
53
|
+
this.refreshRequested = false;
|
|
36
54
|
this.counter.stop();
|
|
37
55
|
}
|
|
38
56
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { BaseSessionStorage, type PromptEpochDescriptor, type AcquiredSession, type SessionMetadata, type SessionStore, type SessionTreeEntry, type SessionWriteOptions } from "@sema-agent/core";
|
|
1
|
+
import { BaseSessionStorage, type PromptEpochDescriptor, type AcquiredSession, type PlacedSessionRow, type SessionMetadata, type SessionPlacement, type SessionPlacementRecord, type SessionStore, type SessionTreeEntry, type SessionWriteOptions } from "@sema-agent/core";
|
|
2
2
|
import type { Pool, PoolClient } from "pg";
|
|
3
3
|
import type { SessionSummary } from "./store-contracts.js";
|
|
4
|
+
import { type PlacementAcquireOpts } from "./session-placement.js";
|
|
4
5
|
import { type StagingHandle } from "../session-sync-kernel.js";
|
|
5
6
|
/**
|
|
6
7
|
* PG-dialect DDL for the L1 session log, translated from tidb-pool.ts SCHEMA_STATEMENTS:
|
|
@@ -50,15 +51,29 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
50
51
|
private readonly pool;
|
|
51
52
|
/** #270 车1:托管留存声明(TiDB 孪生同格)。读法见 `retention-store-sql.ts` 的 {@link MANAGED_RETENTION}。 */
|
|
52
53
|
readonly retention: import("@sema-agent/core").RetentionDeclaration;
|
|
54
|
+
/** #292 C18(core 5.48.0 design/323):placement 声明(TiDB 孪生同格,义务逐条见
|
|
55
|
+
* `src/plugins/session-placement.ts` 顶注)。 */
|
|
56
|
+
readonly placements: {
|
|
57
|
+
readonly subagent: {
|
|
58
|
+
readonly durability: "durable";
|
|
59
|
+
};
|
|
60
|
+
};
|
|
53
61
|
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one. */
|
|
54
62
|
private readonly pending;
|
|
55
63
|
constructor(pool: Pool);
|
|
56
|
-
acquire(sessionId?: string, opts?:
|
|
57
|
-
requireExisting?: boolean;
|
|
58
|
-
}): Promise<AcquiredSession>;
|
|
64
|
+
acquire(sessionId?: string, opts?: PlacementAcquireOpts): Promise<AcquiredSession>;
|
|
59
65
|
private load;
|
|
60
|
-
/** Idempotently create a session_meta row with an owner. Existing owner is never overwritten.
|
|
61
|
-
|
|
66
|
+
/** Idempotently create a session_meta row with an owner. Existing owner is never overwritten.
|
|
67
|
+
* #292:`placement` 与行同一条 INSERT;`DO NOTHING` 臂 ⇒ 已存在行的元组永不被重写(first-write 不可变)。 */
|
|
68
|
+
register(sessionId: string, owner: string | null, placement?: SessionPlacement): Promise<void>;
|
|
69
|
+
/** #292 义务 6 的廉价探针(TiDB 孪生 {@link TiDBSessionStore.placementOf} 同义)。 */
|
|
70
|
+
placementOf(sessionId: string): Promise<SessionPlacementRecord | undefined>;
|
|
71
|
+
/** #292 义务 5 —— placed 分区枚举(年龄锚/上界/tupleIncomplete 三条读法与 TiDB 孪生逐字同义,
|
|
72
|
+
* 见 {@link TiDBSessionStore.listPlaced})。 */
|
|
73
|
+
listPlaced(kind: "subagent", opts?: {
|
|
74
|
+
olderThanMs?: number;
|
|
75
|
+
scope?: string;
|
|
76
|
+
}): Promise<PlacedSessionRow[]>;
|
|
62
77
|
/** Owner principal of a session, `null` if registered ownerless, `undefined` if no such session. */
|
|
63
78
|
ownerOf(sessionId: string): Promise<string | null | undefined>;
|
|
64
79
|
touch(sessionId: string): Promise<void>;
|
|
@@ -70,8 +85,16 @@ export declare class PgSessionStore implements SessionStore {
|
|
|
70
85
|
owner: string | null;
|
|
71
86
|
leafId: string | null;
|
|
72
87
|
} | undefined>;
|
|
73
|
-
/** Durable history is retained; just drop any in-flight cache entry.
|
|
88
|
+
/** Durable history is retained; just drop any in-flight cache entry.
|
|
89
|
+
* 🔴 #292 义务 4:**placed 会话例外 —— release 是真删除**(理由/ABA 条件删/失败上浮三条读法逐字见
|
|
90
|
+
* {@link TiDBSessionStore.release},本腿是它的 PG 孪生)。 */
|
|
74
91
|
release(sessionId: string): Promise<void>;
|
|
92
|
+
/** @see TiDBSessionStore.forget — 丢引用不动 durable 史(preflight 纪律的落点;PG 孪生)。 */
|
|
93
|
+
forget(sessionId: string): void;
|
|
94
|
+
/** @see TiDBSessionStore.deletePlacedSession — PG 孪生(条件删门 + meta 先删 + 失败上浮同义)。 */
|
|
95
|
+
private deletePlacedSession;
|
|
96
|
+
/** #292:placed id 拒绝 session-sync 的整段换装(TiDB 孪生 {@link TiDBSessionStore.assertNotPlaced} 同义)。 */
|
|
97
|
+
private assertNotPlaced;
|
|
75
98
|
/** @see TiDBSessionStore.listSessions — session identity from session_meta (owner-filtered), task_run LEFT JOIN for preview. */
|
|
76
99
|
listSessions(opts: {
|
|
77
100
|
owner?: string;
|