@sema-agent/server 7.20.0 → 7.22.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 +31 -3
- package/dist/adoption/runner.js +4 -5
- package/dist/approval-card.d.ts +52 -0
- package/dist/approval-card.js +108 -0
- package/dist/boot/governance-seams.d.ts +112 -0
- package/dist/boot/governance-seams.js +151 -0
- package/dist/boot/leader.d.ts +3 -0
- package/dist/boot/leader.js +7 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +10 -2
- package/dist/boot/runner-deps.d.ts +6 -2
- package/dist/boot/runner-deps.js +13 -0
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +24 -5
- package/dist/config-types.d.ts +50 -4
- package/dist/config.js +129 -3
- package/dist/fleet/fleet-bus.d.ts +23 -2
- package/dist/fleet/fleet-bus.js +41 -2
- package/dist/fleet/subagent-tail-bus.d.ts +59 -0
- package/dist/fleet/subagent-tail-bus.js +60 -0
- package/dist/http/routes/capabilities.js +27 -0
- package/dist/http/routes/diagnostics.js +6 -0
- package/dist/http/routes/runs.js +10 -1
- package/dist/http/routes/tasks.js +15 -4
- package/dist/http/server.d.ts +5 -0
- package/dist/http/server.js +130 -7
- package/dist/leader/wire.d.ts +7 -1
- package/dist/leader/wire.js +12 -5
- package/dist/main.js +33 -8
- package/dist/memory-posture.d.ts +80 -0
- package/dist/memory-posture.js +40 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +2 -1
- package/dist/parked-decide.js +9 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +3 -1
- package/dist/plugins/approval-ask-store-sql.js +5 -6
- package/dist/plugins/background-agent-store-sql.js +4 -1
- package/dist/plugins/checkpoint-store-sql.d.ts +2 -0
- package/dist/plugins/checkpoint-store-sql.js +4 -5
- package/dist/plugins/file-run-store.d.ts +2 -1
- package/dist/plugins/image-bake-store-sql.js +5 -5
- package/dist/plugins/memory-embedder-fingerprint.d.ts +23 -0
- package/dist/plugins/memory-embedder-fingerprint.js +28 -1
- package/dist/plugins/memory-engine-tidb.js +3 -5
- package/dist/plugins/memory-engine-vector-util.d.ts +0 -5
- package/dist/plugins/memory-engine-vector-util.js +10 -4
- package/dist/plugins/memory-run-store.d.ts +2 -1
- package/dist/plugins/pg-session-storage.js +3 -3
- package/dist/plugins/run-store-sql.d.ts +12 -3
- package/dist/plugins/run-store-sql.js +4 -3
- package/dist/plugins/session-policy-store-sql.d.ts +1 -0
- package/dist/plugins/session-policy-store-sql.js +3 -3
- package/dist/plugins/shared-memory-store-sql.js +4 -10
- package/dist/plugins/sql-driver.d.ts +7 -5
- package/dist/plugins/sql-errors.d.ts +41 -0
- package/dist/plugins/sql-errors.js +25 -0
- package/dist/plugins/tidb-session-storage.js +3 -3
- package/dist/plugins/workflow-run-store-sql.js +4 -4
- package/dist/run-local.js +15 -0
- package/dist/runs.js +106 -38
- package/dist/task-mcp.d.ts +22 -1
- package/dist/task-mcp.js +71 -1
- package/dist/tool-approval.d.ts +35 -0
- package/dist/tool-approval.js +4 -1
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/ledger-events.d.ts +43 -0
- package/dist/trace/ledger-events.js +2 -0
- package/dist/trace/ledger-sink.d.ts +21 -2
- package/dist/trace/ledger-sink.js +5 -1
- package/dist/trace/project.d.ts +4 -1
- package/dist/trace/project.js +15 -0
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -53,6 +53,7 @@ import { createLiveCoordinators } from "./boot/coordinators.js";
|
|
|
53
53
|
import { createRuntimeCaps } from "./boot/runtime-caps.js";
|
|
54
54
|
import { createRunnerDeps, createRunnerDepsOnAsk, createSharedRunnerDeps } from "./boot/runner-deps.js";
|
|
55
55
|
import { createOrgMemoryAdmissionWiring } from "./boot/org-memory.js";
|
|
56
|
+
import { createGovernanceSeams } from "./boot/governance-seams.js";
|
|
56
57
|
import { createSharedMemoryScopeAuthorizer } from "./shared-memory-scope-authorizer.js";
|
|
57
58
|
import { createSessionFaces } from "./boot/session-faces.js";
|
|
58
59
|
import { createLeaderFace } from "./boot/leader.js";
|
|
@@ -171,7 +172,7 @@ async function main() {
|
|
|
171
172
|
await configCenter.applyLocalRemoteExec();
|
|
172
173
|
// design/158 A10:持久层装配搬到 src/boot/stores.ts(逐字)。⚠️ 该段就地归一 `config.sessionBackend`
|
|
173
174
|
// 且承载三条 fail-loud 拒启断言 —— 位置即契约,理由见该文件头注。
|
|
174
|
-
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, } = await openStores({ config, logger, metrics, localRoot });
|
|
175
|
+
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, } = await openStores({ config, logger, metrics, localRoot });
|
|
175
176
|
// design/183 I6(server 同族):**每副本 boot 必查**收编日志的在飞行 —— 续跑或响亮留痕,禁静默跳过。
|
|
176
177
|
// 位置:store 开完之后(要 backend)、任何路由装配之前(半迁移状态绝不带进服务期)。判据与「为什么
|
|
177
178
|
// 是续跑而不是拒启」见 boot/adoption.ts 顶注。
|
|
@@ -263,6 +264,30 @@ async function main() {
|
|
|
263
264
|
// 非 brand 部署维持 core 新缺省(不署)。判据逐字不变,变的只是它现在**也**喂给 subRunner ——
|
|
264
265
|
// 一个被委派的子代提交进的是同一个仓、代表同一个部署,它的 commit 少一行 trailer 没有理由。
|
|
265
266
|
const commitHands = config.configProvider === "local" ? { commitCoAuthor: "Sema <noreply@vivi-ai.com>" } : undefined;
|
|
267
|
+
// checkpointStore 构造(只依赖 backend/config——顺序安全;DURABLE_APPROVAL 仍是唯一门,ALL three
|
|
268
|
+
// backends carry checkpoint(),local = core FileCheckpointStore + service half)。四个消费点同一实例:
|
|
269
|
+
// 主 runner 的 per-task spec 面(resolveSpec durableEnabled 分支)、scenarioDeps(subagent spawn 面
|
|
270
|
+
// forwardDurableApproval)、subRunner deps(子代执行面 suspendAsk——[1584] 接线,裁定见 subRunner 构造
|
|
271
|
+
// 内注),以及紧随其下的 design/170 件D 留存能力校验(它要读这只店的 `retention` 声明位)。
|
|
272
|
+
// ⚠️ 位置即契约:必须早于 `createGovernanceSeams`(件D 校验的对象之一)与 `createRunnerDeps`。
|
|
273
|
+
const checkpointStore = backend?.checkpoint && config.durableApproval ? backend.checkpoint(logger) : undefined;
|
|
274
|
+
// design/170 件B/C/D(#252 件1):三个部署治理座席 + 两条拒启判据(锁的装配相容性、留存能力校验)。
|
|
275
|
+
// ⚠️ 位置即契约:必须在 store 三件都构造完之后(件D 校验读它们的 `retention` 声明)、`new Runner` 之前
|
|
276
|
+
// (座席进 RunnerDeps);拒启在这里发生 = 还没开始服务。
|
|
277
|
+
const governanceSeams = createGovernanceSeams({
|
|
278
|
+
config,
|
|
279
|
+
logger,
|
|
280
|
+
// 件D 的首批治理对象(design/170 §5.1 点名的三族)。**喂真身**:这道校验的全部价值就是读真店上的
|
|
281
|
+
// 声明位,喂一个"我以为装了什么"的替身等于把校验变成自证。`store: undefined` 的行由 core 跳过。
|
|
282
|
+
retentionStores: [
|
|
283
|
+
{ name: "sessionStore", store: sessionStore },
|
|
284
|
+
{ name: "checkpointStore", store: checkpointStore },
|
|
285
|
+
// 🔴 R2-F3(codex 对抗复审,验真后修):喂 **Runner 真用的那一只**(`runnerOffloadStore`),不是
|
|
286
|
+
// `toolResultStore` —— 后者缺席时 Runner 拿的是本文件铸的 InMemory 兜底,而那只店同样受留存策略管辖。
|
|
287
|
+
// 喂错实例 = 校验漏掉了真正在跑的那一个,正是「校验读的不是真身」这类假绿。
|
|
288
|
+
{ name: "toolResultStore", store: runnerOffloadStore },
|
|
289
|
+
],
|
|
290
|
+
});
|
|
266
291
|
// design/158 A10:RunnerDeps 装配段搬到 src/boot/runner-deps.ts(逐字;runStore 晚绑改取值,见该文件头注)。
|
|
267
292
|
const runnerDeps = createRunnerDeps({
|
|
268
293
|
hands: commitHands,
|
|
@@ -272,6 +297,7 @@ async function main() {
|
|
|
272
297
|
permissionRuleStore: permissionRuleStores?.provider,
|
|
273
298
|
executionEnvFactory, lspManager, fleetBus, deploymentHooks, workflowRunStore, workflowJournalStore,
|
|
274
299
|
workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion, orgMemoryAdmission,
|
|
300
|
+
governanceSeams,
|
|
275
301
|
sharedMemoryStores: sharedMemoryStore ? sharedMemoryStore : undefined,
|
|
276
302
|
getRunStore: () => runStore,
|
|
277
303
|
});
|
|
@@ -427,12 +453,8 @@ async function main() {
|
|
|
427
453
|
// fork 子代跨 wake 保 offload 引用;fork 形(有 sessionId)会话规则可达。
|
|
428
454
|
// 1877 注释的「Deliberately NOT inherited」清单(selfOrchestration/scheduler/memory/fileSnapshot/
|
|
429
455
|
// runtimeCaps)不变——本批只补漏配,不动裁定。
|
|
430
|
-
// checkpointStore
|
|
431
|
-
//
|
|
432
|
-
// 三个消费点同一实例:主 runner 的 per-task spec 面(resolveSpec durableEnabled 分支)、scenarioDeps
|
|
433
|
-
// (subagent spawn 面 forwardDurableApproval)、subRunner deps(子代执行面 suspendAsk——[1584] 接线,
|
|
434
|
-
// 裁定见下方 subRunner 构造内注)。
|
|
435
|
-
const checkpointStore = backend?.checkpoint && config.durableApproval ? backend.checkpoint(logger) : undefined;
|
|
456
|
+
// (checkpointStore 的构造已再上移到 RunnerDeps 装配之前 —— design/170 件D 的留存能力校验要把它与
|
|
457
|
+
// session/tool-result 一起交给 core 的 `assertRetentionCapability`,而那道校验必须在拒启窗内跑完。)
|
|
436
458
|
// [1593] 提名成变量:parked 子代的 ensureChildSessionDurable 晋升半场要拿它做 promoteToHost
|
|
437
459
|
// (子会话住私有 TTL 店,park 前整树拷进 host durable 店——只查不迁的 1.263 形在拆店生产形下恒
|
|
438
460
|
// 否决 park,cli 真机 14ms~2s expired 即此)。
|
|
@@ -459,6 +481,7 @@ async function main() {
|
|
|
459
481
|
sessionPolicyStore,
|
|
460
482
|
usageWindowStore,
|
|
461
483
|
orgMemoryAdmission,
|
|
484
|
+
governanceSeams, // design/170 件B/C/D:与主 runner 同一份座席(子代平面同受档位/锁约束)
|
|
462
485
|
sharedMemoryStores: sharedMemoryStore ? sharedMemoryStore : undefined, // design/177:与主 runner 同实例
|
|
463
486
|
}),
|
|
464
487
|
// ── 以下为 subRunner 差异键(不在共享基座;逐个有因)──────────────────────────────────────
|
|
@@ -823,7 +846,7 @@ async function main() {
|
|
|
823
846
|
workflowCompletionInbox,
|
|
824
847
|
});
|
|
825
848
|
// design/158 A10:leader 段搬到 src/boot/leader.ts(逐字)。
|
|
826
|
-
const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore });
|
|
849
|
+
const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams });
|
|
827
850
|
// Graceful drain: shared mutable state — SIGTERM flips `draining` (shutdown below), createHttpServer
|
|
828
851
|
// assigns `inflight` (live leg count on this instance), /health + the submit 503 gate read it.
|
|
829
852
|
const drainState = { draining: false };
|
|
@@ -1007,6 +1030,7 @@ async function main() {
|
|
|
1007
1030
|
scenarioDetails, // 场景详情只读面(内建+center 同判定源)
|
|
1008
1031
|
workflowsCapable, // ① core split: capabilities.workflows = engine-can (workflowsCapability); workflowsList = store (below)
|
|
1009
1032
|
staticWiring, // #154 件三:GET /v1/diagnostics/wiring 的 static 半场(与上面的拒启自检同一份产物)
|
|
1033
|
+
memoryPosture, // #252 件2/件3:记忆面姿态(boot/stores.ts 的单一推导点;诊断面全量 + 能力位窄投影)
|
|
1010
1034
|
instanceId,
|
|
1011
1035
|
// Capability discovery: the deployment facts only main knows. Store-derived booleans
|
|
1012
1036
|
// (asyncRuns/artifacts/approvals/leader) are computed inside the server from the deps themselves.
|
|
@@ -1065,6 +1089,7 @@ async function main() {
|
|
|
1065
1089
|
selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore,
|
|
1066
1090
|
singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage,
|
|
1067
1091
|
sessionEnvSelection,
|
|
1092
|
+
lockedKeys: governanceSeams.lockedKeys, // design/170 件C:同步拒面判据(与 RunnerDeps.lockedConfig 同源)
|
|
1068
1093
|
liveQuestionFace: question, // #152:活体问答面在场 ⇒ durable question 门按活流分腿(见 ResolveSpecCtx 注)
|
|
1069
1094
|
});
|
|
1070
1095
|
// #196:执行点选路。resolveSpec 已按场景判别位登记 lane;`full` 恒回主 runner(修前行为逐字不变)。
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #252 件2/件3 —— 记忆面**姿态**的单一推导点。
|
|
3
|
+
*
|
|
4
|
+
* ## 病
|
|
5
|
+
*
|
|
6
|
+
* 「这台 worker 的记忆点亮没有 / 跑在哪个后端 / 检索在哪一档」这三件事此前只活在启动日志
|
|
7
|
+
* (`memory_engine_enabled`)里。日志不是接口:不可查询、跨副本不可聚合、也没有任何门保证它还在。
|
|
8
|
+
* 于是消费端(cli 的记忆 UX)与运维只能读日志、或者按 env 自己推 —— 而 env 推不出来:`MEMORY_ENGINE=on`
|
|
9
|
+
* 的部署在多租户 file 底座上是**暗的**,`MEMORY_ENGINE_BACKEND=pg` 的部署检索档还要看 embedder 在不在。
|
|
10
|
+
*
|
|
11
|
+
* ## 药
|
|
12
|
+
*
|
|
13
|
+
* 一次推导,两个读面:operator 面(`GET /v1/diagnostics/wiring` 的 `memoryPosture` 段,全量)与消费端面
|
|
14
|
+
* (`GET /v1/capabilities` 的 `memoryEngine` 位,窄投影)。两面同源是本件的全部意义 —— 「诊断页说点亮、
|
|
15
|
+
* 能力位说没有」这种自相矛盾在结构上不可能发生。
|
|
16
|
+
*
|
|
17
|
+
* ## 两条诚实纪律
|
|
18
|
+
*
|
|
19
|
+
* ① **档位取实例真值,不按后端推断**([3590] 裁①/[3606]①的延续):`vectorMode` 是「后端上限 × embedder
|
|
20
|
+
* 在场」的推断结果,两支 SQL 后端各自的实例 getter 才是真值;file 引擎**根本没有**这个指示面
|
|
21
|
+
* (core 的 `MemoryBackend` 契约里没有 `vectorMode`,亲验),所以它报 `null` —— 「没有这个指示面」与
|
|
22
|
+
* 「档位是词面」是两件事,折成一件就是编一个数出来。
|
|
23
|
+
* ② **dark 成因是闭集词**:散文归日志(它的读者是人),机器读面要词(它的读者是 cli 的分支)。
|
|
24
|
+
*/
|
|
25
|
+
import type { ServiceConfig } from "./config-types.js";
|
|
26
|
+
/** 检索档位(两支 SQL 后端的实例 getter 值域;file 腿无此面)。 */
|
|
27
|
+
export type MemoryVectorRung = "native" | "portable" | "lexical";
|
|
28
|
+
/** 记忆面**暗**的成因(闭集;三条各对应装配期的一道门)。 */
|
|
29
|
+
export type MemoryDarkReason =
|
|
30
|
+
/** operator 显式关停(`MEMORY_ENGINE=off`)。 */
|
|
31
|
+
"engine_off"
|
|
32
|
+
/** 多租户 + file 底座:file 基座无租户隔离,fail-closed 不点亮(改 `MEMORY_ENGINE_BACKEND=pg|tidb`)。 */
|
|
33
|
+
| "multi_tenant_file_backend"
|
|
34
|
+
/** 平面分裂车道:模型的文件工具在沙箱 fs、引擎在 worker fs,harvest 看不到沙箱写入 ⇒ fail-closed。 */
|
|
35
|
+
| "remote_lane_split";
|
|
36
|
+
/** 部署的记忆面姿态(operator 面全量下发;能力面取其窄投影)。 */
|
|
37
|
+
export interface MemoryPosture {
|
|
38
|
+
/** 引擎**真的**接上了吗(= `RunnerDeps.memoryBackend` 在场),不是「配置里写了什么」。 */
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
/** 声明的后端方言。与点亮态**正交**:配了 pg 而引擎暗时这一位仍是 pg(配置事实与装配事实分开报)。 */
|
|
41
|
+
backend: ServiceConfig["memoryEngineBackend"];
|
|
42
|
+
/** 检索档位;`null` = 这条腿没有档位指示面(file 引擎),**不是**「档位是 lexical」。 */
|
|
43
|
+
vectorMode: MemoryVectorRung | null;
|
|
44
|
+
/** 暗的成因;点亮时恒 `null`(两者同时在场即自相矛盾)。 */
|
|
45
|
+
darkReason: MemoryDarkReason | null;
|
|
46
|
+
/** 多租户姿态(file 底座恒暗那条判据的输入,也是消费端判「这台的记忆是不是按租户分区」的依据)。 */
|
|
47
|
+
multiTenant: boolean;
|
|
48
|
+
/** embedder 身份(pg 向量档才可能在场)。**只报 model/dimensions**:端点与 key 是凭证面,不进读面。 */
|
|
49
|
+
embedder: {
|
|
50
|
+
model: string;
|
|
51
|
+
dimensions: number;
|
|
52
|
+
} | null;
|
|
53
|
+
}
|
|
54
|
+
/** 推导入参 = 装配现场的真事实(不在本函数里重算任何一件)。 */
|
|
55
|
+
export interface MemoryPostureFacts {
|
|
56
|
+
config: Pick<ServiceConfig, "memoryEngineEnabled" | "memoryEngineBackend" | "requirePrincipal" | "memoryEmbedder">;
|
|
57
|
+
/** 引擎这一腿真的接上了吗(`memoryEngine !== undefined`)—— 装配结果。 */
|
|
58
|
+
engineWired: boolean;
|
|
59
|
+
/** 两支 SQL 后端从**实例 getter** 读到的档位;file 腿传 `undefined`(无此面)。 */
|
|
60
|
+
vectorMode: MemoryVectorRung | undefined;
|
|
61
|
+
/** 车道门判 dark 了吗(`memoryEngineRemoteLanePosture(config)?.posture === "dark"`)。 */
|
|
62
|
+
remoteLaneDark: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 纯推导(`build*`:产纯数据)。成因的判序是**显式关停 > 租户隔离 > 车道分裂**:
|
|
66
|
+
* operator 自己关的那一形必须报成 `engine_off`,报成隔离/车道会把人指向一个改了也没用的旋钮。
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildMemoryPosture(facts: MemoryPostureFacts): MemoryPosture;
|
|
69
|
+
/**
|
|
70
|
+
* 能力面(`GET /v1/capabilities` 的 `memoryEngine` 位)的**窄投影**:点亮 ⇒ 后端形 + 档位;
|
|
71
|
+
* 暗 ⇒ `false`(位在场且假 = 「这台认识这一位、本部署没点亮」,与位整个缺席 = 老 server 两回事)。
|
|
72
|
+
*
|
|
73
|
+
* 刻意**不**外泄 `darkReason` / `multiTenant` / embedder 身份:能力面是任何持凭证调用方都能读的面,
|
|
74
|
+
* 它只需要回答「我能不能用记忆、用的是哪一档」;成因与身份是运维诊断料,归 operator-only 那一面。
|
|
75
|
+
*/
|
|
76
|
+
export declare function projectMemoryEngineCapability(posture: MemoryPosture | undefined): false | {
|
|
77
|
+
backend: MemoryPosture["backend"];
|
|
78
|
+
vectorMode: MemoryVectorRung | null;
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=memory-posture.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 纯推导(`build*`:产纯数据)。成因的判序是**显式关停 > 租户隔离 > 车道分裂**:
|
|
3
|
+
* operator 自己关的那一形必须报成 `engine_off`,报成隔离/车道会把人指向一个改了也没用的旋钮。
|
|
4
|
+
*/
|
|
5
|
+
export function buildMemoryPosture(facts) {
|
|
6
|
+
const { config, engineWired, vectorMode, remoteLaneDark } = facts;
|
|
7
|
+
const embedder = config.memoryEmbedder;
|
|
8
|
+
const darkReason = engineWired
|
|
9
|
+
? null
|
|
10
|
+
: !config.memoryEngineEnabled
|
|
11
|
+
? "engine_off"
|
|
12
|
+
: config.memoryEngineBackend === "file" && config.requirePrincipal === true
|
|
13
|
+
? "multi_tenant_file_backend"
|
|
14
|
+
: remoteLaneDark
|
|
15
|
+
? "remote_lane_split"
|
|
16
|
+
: // 到不了的第四形只可能是「装配拒启路径上」(DB 腿半配 fail-loud 已 throw),那时这个对象根本
|
|
17
|
+
// 不会被建出来。仍然给一个词而不是 null:点亮态为假时 `darkReason` 必须有值,才谈得上机器可读。
|
|
18
|
+
"engine_off";
|
|
19
|
+
return {
|
|
20
|
+
enabled: engineWired,
|
|
21
|
+
backend: config.memoryEngineBackend,
|
|
22
|
+
vectorMode: vectorMode ?? null,
|
|
23
|
+
darkReason,
|
|
24
|
+
multiTenant: config.requirePrincipal === true,
|
|
25
|
+
embedder: embedder !== undefined ? { model: embedder.model, dimensions: embedder.dimensions } : null,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 能力面(`GET /v1/capabilities` 的 `memoryEngine` 位)的**窄投影**:点亮 ⇒ 后端形 + 档位;
|
|
30
|
+
* 暗 ⇒ `false`(位在场且假 = 「这台认识这一位、本部署没点亮」,与位整个缺席 = 老 server 两回事)。
|
|
31
|
+
*
|
|
32
|
+
* 刻意**不**外泄 `darkReason` / `multiTenant` / embedder 身份:能力面是任何持凭证调用方都能读的面,
|
|
33
|
+
* 它只需要回答「我能不能用记忆、用的是哪一档」;成因与身份是运维诊断料,归 operator-only 那一面。
|
|
34
|
+
*/
|
|
35
|
+
export function projectMemoryEngineCapability(posture) {
|
|
36
|
+
if (posture === undefined || !posture.enabled)
|
|
37
|
+
return false;
|
|
38
|
+
return { backend: posture.backend, vectorMode: posture.vectorMode };
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=memory-posture.js.map
|
|
@@ -69,6 +69,10 @@ export declare const FAIL_OPEN_TAGS: {
|
|
|
69
69
|
readonly cls: "F";
|
|
70
70
|
readonly note: "fleet bus 某订阅回调抛错 ⇒ 该回调本帧作废,其余订阅方与发布方不受影响。隔离是承重的:扇出同步,修前异常会传回发布方 put/update 投影点,core 持久化 catch{} 且不推进 storeRev ⇒ durable 行冻在 running 而 notify 已 ack(#183 复审 R3 HIGH)。丢的只是一个消费方的一帧渲染,故 F 类;但必须留痕——静默吞掉等于订阅方病灶永不显形。";
|
|
71
71
|
};
|
|
72
|
+
readonly "server.fleet.run-status-unmapped": {
|
|
73
|
+
readonly cls: "F";
|
|
74
|
+
readonly note: "A-032 P1-④:`runStatusToFleet` 收到**词表外**的 run 状态词(闭集签名之外的唯一来路 = `rowToRun` 把无约束的 status 文本列裸 cast,滚动升级里更新的副本写的新词被旧副本读到)⇒ 折成 `idle`。放行的最坏后果 = 该行在 fleet 面板上**永久滞留**(removal 集 {completed,failed,killed} 收不到 idle),纯展示面、不影响 run 本身与任何执法判据,故 F 类。方向刻意不改(把 miss 折成终局会把一条还在跑的任务从面板上摘掉,误摘比多一行更伤)。留痕是承重的:2026-06-28 on-box e2e 逮到的 `blocked` 泄漏当年只能靠人眼在面板上发现——计数让下一次词表漂移在遥测里当场显形。编译期一侧已由闭集入参 + `never` 臂执法。";
|
|
75
|
+
};
|
|
72
76
|
readonly "server.run-store.stale-claim-quarantine-unremoved": {
|
|
73
77
|
readonly cls: "F";
|
|
74
78
|
readonly note: "#213 陈旧 claim 接管:claim 已 rename 进 tmp/ 隔离名(接管本体已完成、正确性不受影响),但隔离件 rmSync 失败留在 tmp。放行的最坏后果 = tmp 里多一个死文件(不在 activeDir,hydrate 永不把它当 claim 复活);故 F 类。必须留痕:反复删不掉 = tmp 权限/fs 病灶,静默吞掉会让 tmp 无声膨胀。";
|
|
@@ -92,6 +92,10 @@ export const FAIL_OPEN_TAGS = {
|
|
|
92
92
|
cls: "F",
|
|
93
93
|
note: "fleet bus 某订阅回调抛错 ⇒ 该回调本帧作废,其余订阅方与发布方不受影响。隔离是承重的:扇出同步,修前异常会传回发布方 put/update 投影点,core 持久化 catch{} 且不推进 storeRev ⇒ durable 行冻在 running 而 notify 已 ack(#183 复审 R3 HIGH)。丢的只是一个消费方的一帧渲染,故 F 类;但必须留痕——静默吞掉等于订阅方病灶永不显形。",
|
|
94
94
|
},
|
|
95
|
+
"server.fleet.run-status-unmapped": {
|
|
96
|
+
cls: "F",
|
|
97
|
+
note: "A-032 P1-④:`runStatusToFleet` 收到**词表外**的 run 状态词(闭集签名之外的唯一来路 = `rowToRun` 把无约束的 status 文本列裸 cast,滚动升级里更新的副本写的新词被旧副本读到)⇒ 折成 `idle`。放行的最坏后果 = 该行在 fleet 面板上**永久滞留**(removal 集 {completed,failed,killed} 收不到 idle),纯展示面、不影响 run 本身与任何执法判据,故 F 类。方向刻意不改(把 miss 折成终局会把一条还在跑的任务从面板上摘掉,误摘比多一行更伤)。留痕是承重的:2026-06-28 on-box e2e 逮到的 `blocked` 泄漏当年只能靠人眼在面板上发现——计数让下一次词表漂移在遥测里当场显形。编译期一侧已由闭集入参 + `never` 臂执法。",
|
|
98
|
+
},
|
|
95
99
|
"server.run-store.stale-claim-quarantine-unremoved": {
|
|
96
100
|
cls: "F",
|
|
97
101
|
note: "#213 陈旧 claim 接管:claim 已 rename 进 tmp/ 隔离名(接管本体已完成、正确性不受影响),但隔离件 rmSync 失败留在 tmp。放行的最坏后果 = tmp 里多一个死文件(不在 activeDir,hydrate 永不把它当 claim 复活);故 F 类。必须留痕:反复删不掉 = tmp 权限/fs 病灶,静默吞掉会让 tmp 无声膨胀。",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
1
2
|
/** One pending completion, scoped to the session that must be told about it. `summary` is ALREADY
|
|
2
3
|
* redacted + length-bounded by core's notifier seam (see {@link WorkflowCompletionPayload}). */
|
|
3
4
|
export interface WorkflowCompletionInboxEntry {
|
|
@@ -211,7 +212,7 @@ export declare class FileWorkflowCompletionInbox extends InMemoryWorkflowComplet
|
|
|
211
212
|
* wedges the queue.
|
|
212
213
|
*/
|
|
213
214
|
export declare function emitPendingWorkflowCompletions(inbox: WorkflowCompletionInbox | undefined, sessionId: string | undefined, callerPrincipal: string | null, emit: (frame: {
|
|
214
|
-
type:
|
|
215
|
+
type: LedgerEventType;
|
|
215
216
|
}) => void | Promise<void>,
|
|
216
217
|
/** Emit-TARGET diagnostics — WHICH leg drained (route) onto WHAT kind of connection
|
|
217
218
|
* (live SSE write vs durable event-log append), plus the enqueue→emit lag, so a "workflow completed but the
|
package/dist/parked-decide.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
1
2
|
/**
|
|
2
3
|
* pending checkpoint → parked bg 行匹配。
|
|
3
4
|
*
|
|
@@ -192,6 +193,14 @@ export async function decideParkedAgent(deps, req) {
|
|
|
192
193
|
return "retry";
|
|
193
194
|
}
|
|
194
195
|
};
|
|
196
|
+
// [3911] per-agent tail 的**内容帧供给形**必须随赎回周期作废(codex 复审 round3 high)。
|
|
197
|
+
// 本车道是第四条腿:上面这只 ctx 是**裸的**(只有 toolCallId + reviveClaim),没有宿主
|
|
198
|
+
// forward / notification sink —— 于是 ①park 是非终态,三条 run 腿的终态撤销手够不到它;
|
|
199
|
+
// ②赎回周期是否有帧供给取决于**进程内 retain 条目还在不在**(在 ⇒ core 用 spawn 期的
|
|
200
|
+
// internalsSnapshot 继续转发;不在 ⇒ 这一周期结构上一帧都不会有)。两种都不是本处能断言的事,
|
|
201
|
+
// 所以把上一周期的答案作废成「不知道」:新周期真有帧时首帧会重新登记真相,没有帧时 meta 也不会
|
|
202
|
+
// 拿旧周期的 `on` 骗订阅方去等一个永远不来的转录(那正是本批要消灭的静默形)。
|
|
203
|
+
defaultSubagentTailBus.forgetHandleContentMode(match.handle);
|
|
195
204
|
let receipt;
|
|
196
205
|
try {
|
|
197
206
|
receipt = await deps.reviveTool.execute(mintReviveArgs(row, deps.knownAgentTypes ?? new Set()), ctx);
|
|
@@ -343,7 +343,9 @@ export declare class SqlApprovalAskStore implements ApprovalAskStore {
|
|
|
343
343
|
constructor(db: SqlDriver);
|
|
344
344
|
private q;
|
|
345
345
|
private json;
|
|
346
|
-
/** 唯一键冲突判别
|
|
346
|
+
/** 唯一键冲突判别 —— 判据属主 = `sql-errors.ts`(A-032 P1-①)。`decideAsk` 用它把 `(task_id,
|
|
347
|
+
* idempotency_key)` 撞键翻成 typed 的 `idempotency_conflict` 臂,不让驱动错误对象漏给消费层;
|
|
348
|
+
* 旧形 tidb 臂只认 errno,只带 `code` 的驱动错误会漏判成"真障碍"。 */
|
|
347
349
|
private isDupKey;
|
|
348
350
|
/**
|
|
349
351
|
* 🔴 失败臂的回读**必须走当前这条 `conn`**,不许调 `this.getAsk`/`this.getBatch`(codex 复审 F1,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
2
2
|
import { canAskTransition } from "../approval-ask-machine.js";
|
|
3
|
+
import { isDupKeyError } from "./sql-errors.js";
|
|
3
4
|
/**
|
|
4
5
|
* 幂等键的入店卫生门(属主兜底轮 F2,三 twin 同判——SQL 双方言与 InMemory 都从这里过)。
|
|
5
6
|
*
|
|
@@ -233,10 +234,6 @@ const ASK_COLS = "ask_id, task_id, source_task_id, session_id, owner, batch_id,
|
|
|
233
234
|
"gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, schema_version, expires_at_ms, " +
|
|
234
235
|
"created_at_ms, updated_at_ms";
|
|
235
236
|
const BATCH_COLS = "batch_id, task_id, state, bound_ask_id, rev, created_at_ms, updated_at_ms";
|
|
236
|
-
/** 唯一键冲突的方言判别(checkpoint-store-sql.ts 同一对常量)——`decideAsk` 用它把 `(task_id,
|
|
237
|
-
* idempotency_key)` 撞键翻成 typed 的 `idempotency_conflict` 臂,不让驱动错误对象漏给消费层。 */
|
|
238
|
-
const DUP_ENTRY = 1062; // MySQL/TiDB ER_DUP_ENTRY
|
|
239
|
-
const PG_UNIQUE_VIOLATION = "23505";
|
|
240
237
|
/** JSON 列的读出:**本层只做存取,不解释**——返回类型即 `unknown`,把「这是什么形」的责任留给消费方。
|
|
241
238
|
* 两个 JSON 列的 schema 属主都不在本车:`decision_actor` = design/171 ActorAssertion 形(车4 回决端点
|
|
242
239
|
* 消费,届时按宪法 [2704] 立 schema 后 safeParse);`card_json` = 172 §3.1 呈卡帧载荷(车3 wire 面属主)。
|
|
@@ -338,9 +335,11 @@ export class SqlApprovalAskStore {
|
|
|
338
335
|
json(value) {
|
|
339
336
|
return dialectJsonEncoder(this.db.dialect)(value);
|
|
340
337
|
}
|
|
341
|
-
/** 唯一键冲突判别
|
|
338
|
+
/** 唯一键冲突判别 —— 判据属主 = `sql-errors.ts`(A-032 P1-①)。`decideAsk` 用它把 `(task_id,
|
|
339
|
+
* idempotency_key)` 撞键翻成 typed 的 `idempotency_conflict` 臂,不让驱动错误对象漏给消费层;
|
|
340
|
+
* 旧形 tidb 臂只认 errno,只带 `code` 的驱动错误会漏判成"真障碍"。 */
|
|
342
341
|
isDupKey(e) {
|
|
343
|
-
return this.db.dialect
|
|
342
|
+
return isDupKeyError(this.db.dialect, e);
|
|
344
343
|
}
|
|
345
344
|
/**
|
|
346
345
|
* 🔴 失败臂的回读**必须走当前这条 `conn`**,不许调 `this.getAsk`/`this.getBatch`(codex 复审 F1,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BackgroundAgentStoreError, STALE_RUNNING_REAP_ATTRIBUTION, } from "@sema-agent/core";
|
|
2
2
|
import { pgSafeJsonStringify, pgSanitizeText } from "./pg-safe-json.js";
|
|
3
|
+
import { isMysqlDupKeyError } from "./sql-errors.js";
|
|
3
4
|
export const BACKGROUND_AGENT_TABLE = "background_agent";
|
|
4
5
|
const SUMMARY_COLS = "handle, owner, session_scoped, session_id, parent_session_id, root_session_id, name, agent_type, status, spawned_at_ms, updated_at_ms, settled_at_ms, usage_json";
|
|
5
6
|
/** put/update 共用的投影列(scope_key/handle 键列另拼;record_json 恒最后)。core 1.383([1565] 提货单)
|
|
@@ -238,7 +239,9 @@ export class TiDBBackgroundAgentStore {
|
|
|
238
239
|
await this.pool.query(`INSERT INTO ${BACKGROUND_AGENT_TABLE} (scope_key, handle, ${PROJ_COLS}) VALUES (${Array.from({ length: PROJ_N + 2 }, () => "?").join(", ")})`, [stored.scope, stored.handle, ...projParams(stored, JSON.stringify, (t) => t)]);
|
|
239
240
|
}
|
|
240
241
|
catch (e) {
|
|
241
|
-
|
|
242
|
+
// 判据属主 = `sql-errors.ts`(A-032 P1-①:旧形只认 `code`,只带 errno 的驱动错误会让
|
|
243
|
+
// `agent_record.already_exists` 这条 typed 分流腿静默失效)。
|
|
244
|
+
if (isMysqlDupKeyError(e)) {
|
|
242
245
|
throw new BackgroundAgentStoreError("agent_record.already_exists", "agent record already exists");
|
|
243
246
|
}
|
|
244
247
|
throw e;
|
|
@@ -248,6 +248,8 @@ export declare class SqlCheckpointStore implements CheckpointStore {
|
|
|
248
248
|
* scrubbing the byte and storing the scrubbed row is the unacceptable arm; refusing loudly is the chosen one.
|
|
249
249
|
* That refusal is why {@link SqlCheckpointStore.fidelity} carries a documented residual (see it). */
|
|
250
250
|
private json;
|
|
251
|
+
/** 判据属主 = `sql-errors.ts`(A-032 P1-①;旧形 tidb 臂只认 errno ⇒ 只带 `code` 的 dup 会把
|
|
252
|
+
* `checkpoint.already_exists` 这条 typed 契约错误退化成裸驱动对象上抛)。 */
|
|
251
253
|
private isDupKey;
|
|
252
254
|
/** Create-once. core mints the token (`mintCheckpointToken`) and calls this during suspend. */
|
|
253
255
|
put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
|
|
@@ -34,6 +34,7 @@ import { MAX_RULE_SUGGESTIONS, RuleSuggestionRawSchema } from "../approval-card.
|
|
|
34
34
|
import { parseJsonStrict as parseJson } from "./sql-row-helpers.js";
|
|
35
35
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
36
36
|
import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.js";
|
|
37
|
+
import { isDupKeyError } from "./sql-errors.js";
|
|
37
38
|
/** Cap the persisted/served pending-approval args so a pathological tool payload can't bloat the checkpoint row
|
|
38
39
|
* or the operator-queue response (the BFF renders this in an approval card; a few KB is plenty). */
|
|
39
40
|
const MAX_TOOL_INPUT_CHARS = 8192;
|
|
@@ -243,8 +244,6 @@ function pendingActionToolCallId(blob) {
|
|
|
243
244
|
return { readable: false };
|
|
244
245
|
return { readable: true, toolCallId: id, boundInputHash: bih, kind, sourceTaskId };
|
|
245
246
|
}
|
|
246
|
-
const DUP_ENTRY = 1062; // MySQL/TiDB ER_DUP_ENTRY
|
|
247
|
-
const PG_UNIQUE_VIOLATION = "23505";
|
|
248
247
|
function readActorAssertion(v) {
|
|
249
248
|
if (typeof v !== "object" || v === null)
|
|
250
249
|
return undefined;
|
|
@@ -412,10 +411,10 @@ export class SqlCheckpointStore {
|
|
|
412
411
|
json(value, label) {
|
|
413
412
|
return dialectProtocolJsonEncoder(this.db.dialect)(value, label);
|
|
414
413
|
}
|
|
414
|
+
/** 判据属主 = `sql-errors.ts`(A-032 P1-①;旧形 tidb 臂只认 errno ⇒ 只带 `code` 的 dup 会把
|
|
415
|
+
* `checkpoint.already_exists` 这条 typed 契约错误退化成裸驱动对象上抛)。 */
|
|
415
416
|
isDupKey(e) {
|
|
416
|
-
return this.db.dialect
|
|
417
|
-
? e?.errno === DUP_ENTRY
|
|
418
|
-
: e?.code === PG_UNIQUE_VIOLATION;
|
|
417
|
+
return isDupKeyError(this.db.dialect, e);
|
|
419
418
|
}
|
|
420
419
|
/** Create-once. core mints the token (`mintCheckpointToken`) and calls this during suspend. */
|
|
421
420
|
async put(token, cp) {
|
|
@@ -33,6 +33,7 @@ import { type UsageRow } from "../usage-analytics.js";
|
|
|
33
33
|
import type { TaskResult, TaskStatus } from "@sema-agent/core";
|
|
34
34
|
import type { RunRecord, SessionSummary, RunEvent } from "./store-contracts.js";
|
|
35
35
|
import type { RunStoreCheckpointProbe } from "./memory-run-store.js";
|
|
36
|
+
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
36
37
|
export declare class FileRunStore {
|
|
37
38
|
private readonly runsDir;
|
|
38
39
|
private readonly activeDir;
|
|
@@ -87,7 +88,7 @@ export declare class FileRunStore {
|
|
|
87
88
|
requestPreempt(taskId: string, owner: string | null): Promise<boolean>;
|
|
88
89
|
isPreemptRequested(taskId: string, owner: string | null): Promise<boolean>;
|
|
89
90
|
heartbeat(taskId: string, owner: string | null): Promise<void>;
|
|
90
|
-
appendEvent(taskId: string, seq: number, type:
|
|
91
|
+
appendEvent(taskId: string, seq: number, type: LedgerEventType, data: unknown): Promise<void>;
|
|
91
92
|
maxSeq(taskId: string): Promise<number>;
|
|
92
93
|
retainedFrom(taskId: string): Promise<number>;
|
|
93
94
|
getEvents(taskId: string, afterSeq: number): Promise<RunEvent[]>;
|
|
@@ -41,6 +41,7 @@ import { pgSanitizeText, pgHasUnstorable } from "./pg-safe-json.js";
|
|
|
41
41
|
import { parseJsonOr as parseJson, toIso as iso } from "./sql-row-helpers.js";
|
|
42
42
|
import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
43
43
|
import { mapBakeRow as mapRow, BAKE_SELECT_COLS as SELECT_COLS, } from "./store-contracts.js";
|
|
44
|
+
import { isDupKeyError } from "./sql-errors.js";
|
|
44
45
|
/** PG translation of the image_bake / image_bake_event / image_bake_lease DDL in tidb-pool.ts SCHEMA_STATEMENTS
|
|
45
46
|
* (JSON→JSONB, TINYINT→SMALLINT, DATETIME(3)→TIMESTAMPTZ(3), inline UNIQUE/KEY→named CONSTRAINT + CREATE INDEX).
|
|
46
47
|
* TINYINT(1) 归一(clay 批 2026-07-26):布尔列的 PG 映射统一 SMALLINT 0/1(此前本家族用 BOOLEAN)——
|
|
@@ -115,11 +116,10 @@ export async function ensurePgImageBakeSchema(pool, poolName = "standard") {
|
|
|
115
116
|
// Seed the always-present admission sentinel (idempotent) so createBakeIfIdle can FOR UPDATE-lock it.
|
|
116
117
|
await pool.query("INSERT INTO image_bake_admit (pool) VALUES ($1) ON CONFLICT (pool) DO NOTHING", [poolName]);
|
|
117
118
|
}
|
|
118
|
-
/** Dup-key detection —
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
119
|
+
/** Dup-key detection — the PREDICATE has a single owner (`sql-errors.ts`); only the ATTRIBUTION
|
|
120
|
+
* (which UNIQUE collided — `dupKeyName` below) is store-specific. A-032 P1-①: the old local form
|
|
121
|
+
* recognized only mysql2's string `code`, so an error carrying just `errno` fell through. */
|
|
122
|
+
const isDupKey = isDupKeyError;
|
|
123
123
|
/** Which UNIQUE key did the dup collide on? The ATTRIBUTION CHANNEL differs by engine: mysql2 names the key in
|
|
124
124
|
* `sqlMessage` ("Duplicate entry '…' for key 'image_bake_event.uq_image_bake_event_line'" vs "…'image_bake_event.PRIMARY'");
|
|
125
125
|
* PG names the violated constraint in `err.constraint` (the PK is auto-named `image_bake_event_pkey`, the line
|
|
@@ -49,6 +49,19 @@
|
|
|
49
49
|
* 3. **门自身失败 = 抛(拒启)**。吞掉 = 元表停留旧值 = 下次 boot 再清一次,把一次性代价变成每次 boot
|
|
50
50
|
* 的代价;且「门在场但没跑成」静默 = fail-open。
|
|
51
51
|
*
|
|
52
|
+
* ## 两条已知残余(codex 对抗复审 R1-F1/R1-F3,均判为真;处置=成文,不落码面)
|
|
53
|
+
*
|
|
54
|
+
* - **跨门回滚**(F1):指纹门**之前**的版本(7.14.x/7.15.x:有 embedder、无本门)照写 `embedding` 列却
|
|
55
|
+
* 不认识元表 ⇒「装本版(元表=B)→ 回滚旧版跑一段(写 A 空间向量)→ 升回本版仍配 B」走完,元表与配置
|
|
56
|
+
* 都说 B 而列里是 A 空间 ⇒ 此后恒判 B 态。**与滚动窗是同一族**(第二写者不维护指纹),码面根治只有
|
|
57
|
+
* 行级记账列 —— 稿已列出界(触主表删库重建窗)。纪律面处置与滚动窗同:回滚前后手动清列,或删掉
|
|
58
|
+
* 元表那一行让下次 boot 按 D 态保守清。USAGE 已成文。
|
|
59
|
+
* - **崩溃丢审计行**(F3):「清过就必响亮」以**进程活到 emitOutcome** 为界。清列与写元表是两条各自
|
|
60
|
+
* autocommit 的语句,日志在回读之后才发 ⇒ 清完即崩 ⇒ 下次 boot 元表无行、向量已 NULL ⇒ 走 E 态报
|
|
61
|
+
* 「没有向量可清」;写完元表即崩 ⇒ 下次 boot 判 B 态、**一条日志都不发**。**数据面安全**(次序钉买的
|
|
62
|
+
* 正是这个:绝不残留旧空间),丢的只有那一次的 cleared 计数。刻意**不做**两阶段 pending marker ——
|
|
63
|
+
* 为纯审计价值给 boot 路加一张表 + 两阶段协议,代价与收益不成比例;承诺句在 USAGE 已标出这条边界。
|
|
64
|
+
*
|
|
52
65
|
* 计数取法:`PgQueryResult` 刻意无 `rowCount` ⇒ 用 CTE `WITH c AS (UPDATE … RETURNING 1) SELECT
|
|
53
66
|
* count(*)::int`(禁 `RETURNING id` 全表拉回)。计数**按整次调用累计**、原因锚在第一次真清的那一轮 ——
|
|
54
67
|
* 「清过就必响亮」对终局判 B 的那一支同样成立(codex R2 finding 2)。
|
|
@@ -57,6 +70,16 @@
|
|
|
57
70
|
*
|
|
58
71
|
* 主动回填件(清空后不回填,行被 patch 时自然重嵌)/ 行级 embedder 记账列(结构性根治滚动窗)/
|
|
59
72
|
* pgvector 接线件的列 dim 校验 / TiDB 向量面(v1 lexical only)。
|
|
73
|
+
*
|
|
74
|
+
* 🔴 **不回填的真代价(#234 定界车 2026-08-14 实测更正,稿 §4/§V3 说轻了一档)**:稿把代价写成「那些行
|
|
75
|
+
* 长期停留 lexical 档」,读起来像**排序退化**;真码是**可达性悬崖**——`memory-engine-pg.ts` 的进程内腿
|
|
76
|
+
* 对无向量行落 `jaccardDistance`,core 的该函数在**零词交集**时返回 `null`,`search` 当场 `continue`,
|
|
77
|
+
* 行被**丢出结果集**;仍有向量的行则走余弦、不被这道门丢(前提是查询侧也嵌得出来——embedder 故障那一
|
|
78
|
+
* 刻查询拿不到向量,整轮退词面档,零交集的行一起丢)。即清空之后、重嵌之前,那些行对改述/同义/跨
|
|
79
|
+
* 语言查询**检索不到**——而那恰恰是向量档存在的理由。真库端到端实证:db-integration 套件「清空后的
|
|
80
|
+
* 代价」一钉(清空前 "zulu" 命中该行,清空后同查询恒空,有字面交集的查询仍命中)。
|
|
81
|
+
* ⚠️ 这条只更正**成文口径**,不改裁定:主动回填仍在射程外(clay 已否);但将来复裁该件时,输入应当是
|
|
82
|
+
* 「可达性悬崖」而不是「排序退化」。
|
|
60
83
|
*/
|
|
61
84
|
import { z } from "zod";
|
|
62
85
|
import type { Logger } from "../observability/logger.js";
|
|
@@ -49,6 +49,19 @@
|
|
|
49
49
|
* 3. **门自身失败 = 抛(拒启)**。吞掉 = 元表停留旧值 = 下次 boot 再清一次,把一次性代价变成每次 boot
|
|
50
50
|
* 的代价;且「门在场但没跑成」静默 = fail-open。
|
|
51
51
|
*
|
|
52
|
+
* ## 两条已知残余(codex 对抗复审 R1-F1/R1-F3,均判为真;处置=成文,不落码面)
|
|
53
|
+
*
|
|
54
|
+
* - **跨门回滚**(F1):指纹门**之前**的版本(7.14.x/7.15.x:有 embedder、无本门)照写 `embedding` 列却
|
|
55
|
+
* 不认识元表 ⇒「装本版(元表=B)→ 回滚旧版跑一段(写 A 空间向量)→ 升回本版仍配 B」走完,元表与配置
|
|
56
|
+
* 都说 B 而列里是 A 空间 ⇒ 此后恒判 B 态。**与滚动窗是同一族**(第二写者不维护指纹),码面根治只有
|
|
57
|
+
* 行级记账列 —— 稿已列出界(触主表删库重建窗)。纪律面处置与滚动窗同:回滚前后手动清列,或删掉
|
|
58
|
+
* 元表那一行让下次 boot 按 D 态保守清。USAGE 已成文。
|
|
59
|
+
* - **崩溃丢审计行**(F3):「清过就必响亮」以**进程活到 emitOutcome** 为界。清列与写元表是两条各自
|
|
60
|
+
* autocommit 的语句,日志在回读之后才发 ⇒ 清完即崩 ⇒ 下次 boot 元表无行、向量已 NULL ⇒ 走 E 态报
|
|
61
|
+
* 「没有向量可清」;写完元表即崩 ⇒ 下次 boot 判 B 态、**一条日志都不发**。**数据面安全**(次序钉买的
|
|
62
|
+
* 正是这个:绝不残留旧空间),丢的只有那一次的 cleared 计数。刻意**不做**两阶段 pending marker ——
|
|
63
|
+
* 为纯审计价值给 boot 路加一张表 + 两阶段协议,代价与收益不成比例;承诺句在 USAGE 已标出这条边界。
|
|
64
|
+
*
|
|
52
65
|
* 计数取法:`PgQueryResult` 刻意无 `rowCount` ⇒ 用 CTE `WITH c AS (UPDATE … RETURNING 1) SELECT
|
|
53
66
|
* count(*)::int`(禁 `RETURNING id` 全表拉回)。计数**按整次调用累计**、原因锚在第一次真清的那一轮 ——
|
|
54
67
|
* 「清过就必响亮」对终局判 B 的那一支同样成立(codex R2 finding 2)。
|
|
@@ -57,6 +70,16 @@
|
|
|
57
70
|
*
|
|
58
71
|
* 主动回填件(清空后不回填,行被 patch 时自然重嵌)/ 行级 embedder 记账列(结构性根治滚动窗)/
|
|
59
72
|
* pgvector 接线件的列 dim 校验 / TiDB 向量面(v1 lexical only)。
|
|
73
|
+
*
|
|
74
|
+
* 🔴 **不回填的真代价(#234 定界车 2026-08-14 实测更正,稿 §4/§V3 说轻了一档)**:稿把代价写成「那些行
|
|
75
|
+
* 长期停留 lexical 档」,读起来像**排序退化**;真码是**可达性悬崖**——`memory-engine-pg.ts` 的进程内腿
|
|
76
|
+
* 对无向量行落 `jaccardDistance`,core 的该函数在**零词交集**时返回 `null`,`search` 当场 `continue`,
|
|
77
|
+
* 行被**丢出结果集**;仍有向量的行则走余弦、不被这道门丢(前提是查询侧也嵌得出来——embedder 故障那一
|
|
78
|
+
* 刻查询拿不到向量,整轮退词面档,零交集的行一起丢)。即清空之后、重嵌之前,那些行对改述/同义/跨
|
|
79
|
+
* 语言查询**检索不到**——而那恰恰是向量档存在的理由。真库端到端实证:db-integration 套件「清空后的
|
|
80
|
+
* 代价」一钉(清空前 "zulu" 命中该行,清空后同查询恒空,有字面交集的查询仍命中)。
|
|
81
|
+
* ⚠️ 这条只更正**成文口径**,不改裁定:主动回填仍在射程外(clay 已否);但将来复裁该件时,输入应当是
|
|
82
|
+
* 「可达性悬崖」而不是「排序退化」。
|
|
60
83
|
*/
|
|
61
84
|
import { z } from "zod";
|
|
62
85
|
import { PG_MEMORY_ENGINE_TABLES } from "./memory-engine-pg.js";
|
|
@@ -187,7 +210,11 @@ export async function reconcileEmbedderFingerprint(query, identity, opts = {}) {
|
|
|
187
210
|
reason: firstClear?.reason ?? firstChange?.reason ?? terminalState, // "changed" | "unattributed" | "unreadable"
|
|
188
211
|
rounds,
|
|
189
212
|
elapsedMs,
|
|
190
|
-
|
|
213
|
+
// 🔴 代价按真码说(#234 定界车实测,真库端到端钉在 db-integration 的「清空后的代价」):
|
|
214
|
+
// 「退回 lexical」是**说轻了**——词面档对与查询**零词交集**的行返回「无定义」,检索当场把该行
|
|
215
|
+
// 跳过,于是清空之后、重嵌之前,那些行对改述/同义/跨语言查询是**不可达**的(带向量的行则恒有
|
|
216
|
+
// 余弦读数、永不被丢)。operator 看见这条日志的那一刻就该知道丢的是可达性,不是名次。
|
|
217
|
+
note: `${SCALE_ZERO_NOTE}; cleared vectors are NOT backfilled — rows re-embed only as they are next written, and until then each cleared row is reachable ONLY by a query sharing a literal term with it (the lexical floor drops a row with zero term overlap, whereas a row that still has a vector is scored by cosine as long as the query embeds too) — rewrite the corpus to restore vector reach immediately`,
|
|
191
218
|
});
|
|
192
219
|
return;
|
|
193
220
|
}
|
|
@@ -25,6 +25,7 @@ import { jaccardDistance, termSet } from "@sema-agent/core";
|
|
|
25
25
|
import { computeEntryRev, serializeEntryFile } from "@sema-agent/core";
|
|
26
26
|
import { pgHasUnstorable } from "./pg-safe-json.js";
|
|
27
27
|
import { assertSlugWidth } from "./memory-key-guards.js"; // R5 批γ:slug 写前宽守卫
|
|
28
|
+
import { isMysqlDupKeyError } from "./sql-errors.js";
|
|
28
29
|
/** Table names (single source) — SAME names as PG_MEMORY_ENGINE_TABLES (the two dialects never share
|
|
29
30
|
* one database), deliberately DISJOINT from the legacy `agent_memory*` MemoryStore plane. */
|
|
30
31
|
export const TIDB_MEMORY_ENGINE_TABLES = {
|
|
@@ -58,11 +59,8 @@ export async function ensureTiDBMemoryEngineSchema(pool) {
|
|
|
58
59
|
PRIMARY KEY (scope)
|
|
59
60
|
) COLLATE utf8mb4_bin`);
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return false;
|
|
64
|
-
return err.code === "ER_DUP_ENTRY" || err.errno === 1062;
|
|
65
|
-
}
|
|
62
|
+
/** 判据属主 = `sql-errors.ts`(A-032 P1-①;本站点旧形已是并集形,收编后语义不变)。 */
|
|
63
|
+
const isDupEntry = isMysqlDupKeyError;
|
|
66
64
|
/** ER_DUP_ENTRY messages name the violated key (`… for key 'tbl.PRIMARY'` / `… for key 'tbl.uq_agent_memory_engine_entry_scope_slug'`)
|
|
67
65
|
* — the add path uses this to tell "id already exists (→ overwrite leg)" from "slug race (→ -n retry)". */
|
|
68
66
|
function isPrimaryKeyDup(err) {
|
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
/** memory-engine 方言实现共用的纯工具函数。三个距离/词集函数(termSet/jaccardDistance/cosineDistance)
|
|
2
|
-
* 在 core(1.268.0 起公开导出,单一语义源)——消费者直引 core([2354] 兼容面全清:此处转口已删)。
|
|
3
|
-
* `isUniqueViolation` 重新内联:它当年随 core 的 pg **示例适配器**导出面走,core 1.424 按 clay 包卫生令
|
|
4
|
-
* 把整个 examples 面退出 npm 包([1803] BREAKING)⇒ re-export 断源。逐字抄自 core 1.423
|
|
5
|
-
* `examples/adapters/pg-adapter.js`(纯谓词,无 byte-equal 契约约束)。 */
|
|
6
1
|
export declare function isUniqueViolation(err: unknown): boolean;
|
|
7
2
|
//# sourceMappingURL=memory-engine-vector-util.d.ts.map
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
/** memory-engine 方言实现共用的纯工具函数。三个距离/词集函数(termSet/jaccardDistance/cosineDistance)
|
|
2
2
|
* 在 core(1.268.0 起公开导出,单一语义源)——消费者直引 core([2354] 兼容面全清:此处转口已删)。
|
|
3
3
|
* `isUniqueViolation` 重新内联:它当年随 core 的 pg **示例适配器**导出面走,core 1.424 按 clay 包卫生令
|
|
4
|
-
* 把整个 examples 面退出 npm 包([1803] BREAKING)⇒ re-export
|
|
5
|
-
* `examples/adapters/pg-adapter.js`(纯谓词,无 byte-equal 契约约束)
|
|
4
|
+
* 把整个 examples 面退出 npm 包([1803] BREAKING)⇒ re-export 断源。原为逐字抄自 core 1.423
|
|
5
|
+
* `examples/adapters/pg-adapter.js`(纯谓词,无 byte-equal 契约约束);A-032 P1-① 后**码面判据**
|
|
6
|
+
* 改走属主 `sql-errors.ts`,只留 message 补充臂(理由在函数体内)。 */
|
|
7
|
+
import { isPgUniqueViolation } from "./sql-errors.js";
|
|
6
8
|
export function isUniqueViolation(err) {
|
|
9
|
+
// 码面判据 = 属主谓词(A-032 P1-①,`plugins/sql-errors.ts`)。
|
|
10
|
+
if (isPgUniqueViolation(err))
|
|
11
|
+
return true;
|
|
12
|
+
// 🔴 message 正则臂是**本站点的显式补充**,不上提属主:memory-engine 的 pg 腿吃的是**裸 PgQueryFn**
|
|
13
|
+
// (调用方自带的连接封装),错误对象未必是 node-pg 的原生错误类——中间层可能只把 message 传下来。
|
|
14
|
+
// 其余各店都攥着自己的驱动/池,拿得到带 SQLSTATE 的原错误,不需要也不该按文案分支(#90 门②)。
|
|
7
15
|
if (typeof err !== "object" || err === null)
|
|
8
16
|
return false;
|
|
9
|
-
if (err.code === "23505")
|
|
10
|
-
return true;
|
|
11
17
|
return /duplicate key|unique constraint|already exists/i.test(String(err.message ?? ""));
|
|
12
18
|
}
|
|
13
19
|
//# sourceMappingURL=memory-engine-vector-util.js.map
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { type UsageRow } from "../usage-analytics.js";
|
|
14
14
|
import type { TaskResult, TaskStatus } from "@sema-agent/core";
|
|
15
15
|
import type { RunRecord, SessionSummary, RunEvent } from "./store-contracts.js";
|
|
16
|
+
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
16
17
|
/**
|
|
17
18
|
* [868] P0 — the local lane's stand-in for the SQL twins' checkpoint-table JOIN. The file/memory run stores
|
|
18
19
|
* cannot JOIN `checkpoint` (separate store, separate files), so the suspended-run reapers were NO-OPs — which
|
|
@@ -59,7 +60,7 @@ export declare class MemoryRunStore {
|
|
|
59
60
|
requestPreempt(taskId: string, owner: string | null): Promise<boolean>;
|
|
60
61
|
isPreemptRequested(taskId: string, owner: string | null): Promise<boolean>;
|
|
61
62
|
heartbeat(taskId: string, owner: string | null): Promise<void>;
|
|
62
|
-
appendEvent(taskId: string, seq: number, type:
|
|
63
|
+
appendEvent(taskId: string, seq: number, type: LedgerEventType, data: unknown): Promise<void>;
|
|
63
64
|
maxSeq(taskId: string): Promise<number>;
|
|
64
65
|
retainedFrom(taskId: string): Promise<number>;
|
|
65
66
|
getEvents(taskId: string, afterSeq: number): Promise<RunEvent[]>;
|