@sema-agent/server 7.143.0 → 7.144.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/MIGRATION.md +166 -0
  2. package/USAGE.md +24 -14
  3. package/dist/approval-card.d.ts +31 -14
  4. package/dist/approval-card.js +8 -8
  5. package/dist/boot/execution-env.js +1 -1
  6. package/dist/boot/runner-deps.d.ts +10 -5
  7. package/dist/boot/runner-deps.js +5 -4
  8. package/dist/boot/shutdown.d.ts +1 -1
  9. package/dist/boot/shutdown.js +4 -2
  10. package/dist/boot/stage-01-config.js +2 -0
  11. package/dist/boot/stage-07-capability-layer.js +1 -1
  12. package/dist/boot/stage-10-http-server.js +2 -7
  13. package/dist/capabilities/scenarios.d.ts +14 -18
  14. package/dist/capabilities/scenarios.js +1 -2
  15. package/dist/config-d-migration.d.ts +2 -0
  16. package/dist/delegated-session-owner.d.ts +33 -151
  17. package/dist/delegated-session-owner.js +1 -83
  18. package/dist/entry-mode-clamp-notice.d.ts +1 -1
  19. package/dist/execution-placement.d.ts +7 -4
  20. package/dist/hooks/classic-stations.d.ts +3 -1
  21. package/dist/hooks/classic-stations.js +2 -0
  22. package/dist/http/principal-gate.d.ts +1 -1
  23. package/dist/http/refusal-body.d.ts +7 -3
  24. package/dist/http/refusal-body.js +1 -0
  25. package/dist/http/resume-legs.js +11 -4
  26. package/dist/http/routes/approvals-assistant.js +1 -0
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.js +1 -0
  29. package/dist/lane-home-probe.d.ts +15 -0
  30. package/dist/lane-home-probe.js +4 -0
  31. package/dist/observability/corrupt-read-seat.d.ts +5 -9
  32. package/dist/observability/corrupt-read-seat.js +1 -2
  33. package/dist/observability/fail-open.d.ts +12 -8
  34. package/dist/observability/fail-open.js +12 -8
  35. package/dist/observability/run-terminal.d.ts +1 -1
  36. package/dist/observability/run-terminal.js +2 -2
  37. package/dist/parked-revive-surface.d.ts +6 -7
  38. package/dist/peer-inbound-face.d.ts +29 -34
  39. package/dist/peer-inbound-face.js +28 -63
  40. package/dist/plugins/caching-session-store.d.ts +0 -3
  41. package/dist/plugins/caching-session-store.js +0 -3
  42. package/dist/plugins/checkpoint-store-sql.d.ts +41 -34
  43. package/dist/plugins/checkpoint-store-sql.js +99 -38
  44. package/dist/plugins/fork-routing-session-store.d.ts +8 -1
  45. package/dist/plugins/fork-routing-session-store.js +6 -0
  46. package/dist/plugins/local-checkpoint-store.d.ts +6 -8
  47. package/dist/plugins/local-checkpoint-store.js +3 -2
  48. package/dist/plugins/local-session-store.d.ts +64 -29
  49. package/dist/plugins/local-session-store.js +126 -56
  50. package/dist/plugins/local-session-window.d.ts +4 -2
  51. package/dist/plugins/one-time-migrations.d.ts +12 -12
  52. package/dist/plugins/one-time-migrations.js +12 -12
  53. package/dist/plugins/pg-session-storage.d.ts +1 -9
  54. package/dist/plugins/pg-session-storage.js +2 -19
  55. package/dist/plugins/remote-env-host.d.ts +8 -4
  56. package/dist/plugins/remote-env-host.js +7 -9
  57. package/dist/plugins/remote-shell.js +12 -2
  58. package/dist/plugins/schema-consistency.d.ts +5 -1
  59. package/dist/plugins/session-fork.d.ts +1 -1
  60. package/dist/plugins/session-ownership.d.ts +9 -0
  61. package/dist/plugins/session-ownership.js +6 -0
  62. package/dist/plugins/session-store.d.ts +13 -16
  63. package/dist/plugins/session-store.js +9 -10
  64. package/dist/plugins/sql-json-column.d.ts +24 -2
  65. package/dist/plugins/sql-json-column.js +38 -3
  66. package/dist/plugins/store-backend.js +3 -2
  67. package/dist/plugins/tidb-session-store.d.ts +2 -14
  68. package/dist/plugins/tidb-session-store.js +2 -20
  69. package/dist/run-local.js +2 -1
  70. package/dist/runs.js +4 -0
  71. package/dist/security.d.ts +0 -4
  72. package/dist/session-error-wire.d.ts +24 -25
  73. package/dist/session-error-wire.js +4 -5
  74. package/dist/session-incarnation.d.ts +2 -2
  75. package/dist/session-notify-door.d.ts +2 -2
  76. package/dist/session-notify-door.js +5 -1
  77. package/dist/tool-approval.d.ts +3 -1
  78. package/dist/tool-approval.js +47 -18
  79. package/dist/trace/core-keyset-guard.d.ts +1 -1
  80. package/dist/trace/engine-notice-wire.d.ts +1 -1
  81. package/dist/trace/engine-notice-wire.js +1 -1
  82. package/dist/trace/ledger-sink.js +12 -4
  83. package/dist/trace/model-segment.d.ts +25 -6
  84. package/dist/trace/model-segment.js +47 -6
  85. package/dist/trace/project.d.ts +8 -2
  86. package/dist/trace/project.js +6 -4
  87. package/dist/trace/redact.d.ts +11 -0
  88. package/dist/trace/redact.js +19 -3
  89. package/dist/trace/self-minted-wire.d.ts +88 -9
  90. package/dist/trace/self-minted-wire.js +25 -3
  91. package/package.json +2 -2
  92. package/wire-changes/server@7.144.0.json +291 -0
  93. package/dist/session-records-skipped-notice.d.ts +0 -21
  94. package/dist/session-records-skipped-notice.js +0 -27
@@ -23,33 +23,28 @@
23
23
  * 成因会在重判时移动,列 / 决每次都读卡上**当下**的成因。
24
24
  * · **一个调用方、一张卡、一个答案**:决断口只认列表会列出的卡(`decision === "held"` ∧ 可审成因);已批待投(`approved`)/ 已交接(`delivered`)的卡不列、不收。
25
25
  *
26
- * ## 索引(🚧 待 core 的枚举口到货即退役)
27
- * core 的队列注册表是模块私有的,只能按 (scope, 会话, 箱) 查 —— 宿主拿不到「这个进程里有哪些队列」。本面因此记一张**坐标表**:
28
- * (会话 id, 车道) —— 只是「去哪儿查」,不是卡,不含任何卡的事实。喂它的是 core 自己的两条通告(经部署级通告转发席,
29
- * `boot/runner-deps.ts` 的 `createEngineNoticeForwarder`):`peer.inbound_disposition{disposition:"held"}`(成因可审)进表,
30
- * `peer.held_settled{heldCount:0}` 出表。读的时候每一行都回 core 的队列取当下的卡,表里多一行 = 多查一次空队列,少一行 = 那张卡不列
31
- * (信仍按 core 的期限带回执过期 —— 安全默认)。退役条件:core 导出进程内 held 队列的枚举(连同各队列的 scope)。
32
- * 🚧 **坐标不带 scope**(通告 detail 里没有):同一会话 id 在本进程里被两个主体各排空出一只队列时(无主会话行 / 别的主体跑了这只会话),
33
- * 一只队列清空的那条 `heldCount:0` 把坐标带出表,另一只队列里的卡就不再列出,直到它的下一条 `held`(失败方向 = 上一句的安全默认)。
34
- * 钉 = `test/peer-inbound-approvals.test.ts` 的 🚧 R2 特征化格;core 请托 = 两条通告的 detail 带队列 scope(或上面的枚举口)。
26
+ * ## 枚举(core 7.62.0 `listPeerHeldQueues`)
27
+ * 列与关停都读 core 进程注册表的快照(`listPeerHeldQueues()`:每只队列的 scope / 打开它的会话 / 卡来自哪只箱 / 同一时刻的卡),本面不记任何表 ——
28
+ * 一张卡在不在、属于哪个 scope、哪只箱,全是那一读。决断口按行 id 的 (会话, 车道, seq) 回 `peerHeldQueueIfAny(调用方 scope, …)` 取同一只队列。
29
+ * 箱只有两种(会话自己的箱 / 它也排空的 principal 收件箱,core 的快照注逐字);读到第三种 ⇒ 那只队列的卡不列(没有车道就拼不出决断口 id)+ F 类
30
+ * `server.peer-inbound.queue-box-unknown` 留痕 —— 信仍被扣、按 core 的期限带回执过期(安全默认)。
31
+ *
32
+ * ## 到期
33
+ * 卡上的到期时刻是 core 的 `PeerHeldEntry.deadlineAt`(到期计时武装时在场:可审成因 ∧ 有限 `dialogExpiry` ∧ 卡仍 `held`;成因一动 core 重武装或清掉它)。
34
+ * 行的 `deadline` 就是它的原值,本仓不按 `heldAt + dialogExpiry` 自算(成因一动就是第二份、错的读法)。
35
35
  *
36
36
  * ## 关停
37
37
  * CC `HBe` 在宿主:core 不调 `PeerHeldQueue.shutdown()`(全库零生产调用点)。{@link PeerInboundFace.shutdown} 在有序关停的审批收尾那一拍
38
- * (`boot/shutdown.ts`,关池之前)对坐标表里每只队列调它 —— 仍被扣的信按 core 的规则带 `expired` 回执结清(reason `shutdown`)。
39
- * scope 读自会话行属主(`owner ?? "default"`,与 `boot/session-faces.ts` 信箱腿同读),调用方给读口。
40
- * 🚧 core 的队列 scope 是排空那条 run 的主体(`spec.principal ?? "default"`):无主会话被具名主体跑过时两者不同,关停找不到那只队列
41
- * (失败方向同崩溃:信仍被扣在箱里,下一位消费者重新领它)。钉 = 🚧 R4 特征化格;core 请托同上。
42
- * 🚧 core 的结清回执是投出即返(排空汇点 `void routePeerDeliveryReceipt(…)`):`shutdown()` 兑现时回执可能还没落箱,关池在它之后 ⇒ 那一条回执可能丢
43
- * (信本身已结清)。宿主没有可等的句柄;core 请托 = `shutdown()` 等回执落箱再兑现。
44
- */
38
+ * (`boot/shutdown.ts`,关池之前)对注册表里**每只**队列调它(进程的队列都是本进程的;不求 scope)—— 仍被扣的信按 core 的规则带 `expired` 回执结清
39
+ * (reason `shutdown`)。core 的 `shutdown()` 等它发出的每条回执落箱再兑现,没落的点名 seq 拒(卡照样全结);各队列互不等,一只拒不挡别的
40
+ * (`Promise.allSettled`),拒句进读数 `receiptFaults`,由关停链写一行 warn。
41
+ */
45
42
  import { type PeerHeldEntry, type PeerHeldSettleOutcome } from "@sema-agent/core";
46
43
  import { type PeerInboundLane } from "./org-delivery-gate.js";
47
44
  /**
48
- * 一个 principal(或会话行属主)的信箱 scope —— core 写侧 `taskScope = spec.principal ?? "default"` 的同一条读法(无主 ⇒ `"default"`)。
49
- * 列 / 决用调用方的 principal,关停用会话行属主;两者在属主围栏下同值(`resolve-spec.ts` 的 `fenceSessionOwner`),
50
- * 无主存量会话被具名壳续聊的混属主形是既有残余(`boot/session-faces.ts` 信箱腿同一条登记)。
45
+ * 调用方 principal 的信箱 scope —— core 写侧 `taskScope = spec.principal ?? "default"` 的同一条读法(无主 ⇒ `"default"`)。列 / 决都按它找队列。
51
46
  */
52
- export declare function peerBoxScope(principalOrOwner: string | null | undefined): string;
47
+ export declare function peerBoxScope(principal: string | null | undefined): string;
53
48
  /** 决断口 id 的坐标(id 是它的唯一拼法,见 {@link peerInboundIdOf})。 */
54
49
  export interface PeerInboundCoord {
55
50
  readonly sessionId: string;
@@ -68,9 +63,9 @@ export declare function parsePeerInboundId(id: string): PeerInboundCoord | undef
68
63
  * · `cause` —— core 的扣信成因(`PeerHeldEntry.cause`,`CrossSessionHoldCause` 闭集);
69
64
  * · `content` / `fromName` —— 信的存储正文(`[summary] message`,core 的规范存储形)与发信方的显示名,**过凭据脱敏**(显示面;与停驻行的工具入参同一条);
70
65
  * · `fromSession` / `fromScope` —— 发信会话 / 跨 principal 投递的发信 scope(core 的 typed 归属锚),在场才带;
71
- * · `sentAt` / `heldAt` —— epoch ms。
72
- * 🚧 **没有 `deadline`**:到期计时在 core 队列里(在扣信、成因变动、换绑时各自起表),卡上不带它;本仓若按 `heldAt + dialogExpiry` 自算,
73
- * 成因一动就是一个错的截止时刻(第二份读法)。core 给卡加上到期时刻即补上(请托见 [ref] 收车档)。
66
+ * · `sentAt` / `heldAt` —— epoch ms;
67
+ * · `deadline` —— core 卡上的到期时刻 `deadlineAt` 原值(绝对 epoch ms,与停驻行 `deadline` 同名同义;壳用 `deadline − serverNowMs` 渲倒计时),
68
+ * 到期计时没武装(`dialogExpiry` 为 `null`)⇒ 缺席。
74
69
  */
75
70
  export interface PeerInboundPendingRow {
76
71
  readonly kind: "peer_inbound";
@@ -81,6 +76,7 @@ export interface PeerInboundPendingRow {
81
76
  readonly content: string;
82
77
  readonly sentAt: number;
83
78
  readonly heldAt: number;
79
+ readonly deadline?: number;
84
80
  readonly fromSession?: string;
85
81
  readonly fromName?: string;
86
82
  readonly fromScope?: string;
@@ -103,25 +99,24 @@ export type PeerInboundDecideResult = {
103
99
  readonly kind: "unrecorded";
104
100
  readonly error: unknown;
105
101
  };
106
- /** 关停读数:结清了几只队列、几只会话的 scope 读不出(那几只的卡随进程离开 —— 信仍在箱里被扣着,下一位消费者重新领它)。 */
102
+ /**
103
+ * 关停读数:`queues` = 关了几只队列(注册表里的每一只,含空的);`cards` = 关停那一刻快照里的卡数;`receiptFaults` = 各只拒了的队列的拒句
104
+ * (core 点名回执没落箱的 seq —— 卡已结清,只是发信方可能没收到那条 `expired`)。
105
+ */
107
106
  export interface PeerInboundShutdownReading {
108
107
  readonly queues: number;
109
- readonly scopeUnresolved: number;
108
+ readonly cards: number;
109
+ readonly receiptFaults: readonly string[];
110
110
  }
111
111
  export interface PeerInboundFace {
112
- /** 部署级通告转发席的观察口:只读两条码的坐标,其余一概不看。永不抛(转发席不许把异常回抛给引擎)。 */
113
- observe(notice: {
114
- readonly code: string;
115
- readonly detail?: Record<string, unknown>;
116
- }): void;
117
- /** `scope` 里此刻等人审的信({@link awaitsReview} 的卡),按 `heldAt` 升序(同刻按 id)。 */
112
+ /** `scope` 里此刻等人审的信({@link awaitsReview} 的卡;读 core 的队列枚举),按 `heldAt` 升序(同刻按 id)。 */
118
113
  list(scope: string): PeerInboundPendingRow[];
119
114
  /** 决断转发:只认 `scope` 里此刻等人审的那张卡(与 {@link list} 同一只判据);`settle` 抛(店拒了 CAS)⇒ `unrecorded`。 */
120
115
  decide(scope: string, coord: PeerInboundCoord, decision: PeerInboundDecision): Promise<PeerInboundDecideResult>;
121
- /** 有序关停:坐标表里每只队列 `shutdown()`(core 的规则:仍 held 的卡带 `expired` 回执结清,reason `shutdown`)。 */
122
- shutdown(scopeOfSession: (sessionId: string) => Promise<string | undefined>): Promise<PeerInboundShutdownReading>;
116
+ /** 有序关停:core 注册表里每只队列 `shutdown()`(core 的规则:仍 held 的卡带 `expired` 回执结清,reason `shutdown`;等回执落箱)。永不抛。 */
117
+ shutdown(): Promise<PeerInboundShutdownReading>;
123
118
  }
124
119
  export declare function createPeerInboundFace(): PeerInboundFace;
125
- /** 进程单例 —— 与 core 的 held 队列注册表同一个寿命(进程级);部署级通告转发席与审批路由读的是同一只。 */
120
+ /** 进程单例 —— 决断尾(一张卡一次只过一只决断)是进程级的;审批路由与关停链读的是同一只。 */
126
121
  export declare const defaultPeerInboundFace: PeerInboundFace;
127
122
  //# sourceMappingURL=peer-inbound-face.d.ts.map
@@ -1,10 +1,10 @@
1
- import { PEER_HELD_REVIEW_CAUSES, isPeerSessionId, peerHeldQueueIfAny, } from "@sema-agent/core";
1
+ import { PEER_HELD_REVIEW_CAUSES, isPeerSessionId, listPeerHeldQueues, peerHeldQueueIfAny, } from "@sema-agent/core";
2
2
  import { PEER_INBOUND_LANES, boxOfLane, laneOfBox } from "./org-delivery-gate.js";
3
3
  import { PEER_INBOUND_ID_PREFIX } from "./approval-queue-kind.js";
4
4
  import { recordFailOpen } from "./observability/fail-open.js";
5
5
  import { seatText } from "./trace/exit-seat.js";
6
- export function peerBoxScope(principalOrOwner) {
7
- return principalOrOwner ?? "default";
6
+ export function peerBoxScope(principal) {
7
+ return principal ?? "default";
8
8
  }
9
9
  export function peerInboundIdOf(c) {
10
10
  return `${PEER_INBOUND_ID_PREFIX}${c.sessionId}:${c.lane}:${c.seq}`;
@@ -39,23 +39,18 @@ function rowOf(sessionId, lane, card) {
39
39
  content: seatText("peer-inbound-card-text", card.content),
40
40
  sentAt: card.sentAt,
41
41
  heldAt: card.heldAt,
42
+ ...(card.deadlineAt !== undefined ? { deadline: card.deadlineAt } : {}),
42
43
  ...(fromSession !== undefined ? { fromSession } : {}),
43
44
  ...(card.from !== undefined ? { fromName: seatText("peer-inbound-card-text", card.from) } : {}),
44
45
  ...(fromScope !== undefined ? { fromScope } : {}),
45
46
  };
46
47
  }
47
- function coordinatesOf(code, detail) {
48
- const sessionId = detail?.sessionId;
49
- const box = detail?.box;
50
- if (typeof sessionId === "string" && typeof box === "string" && isPeerSessionId(sessionId)) {
51
- const lane = laneOfBox(box, sessionId);
52
- if (lane !== undefined)
53
- return { sessionId, lane };
54
- }
55
- recordFailOpen("server.peer-inbound.notice-coordinates-unreadable", `code=${code}`);
56
- return undefined;
48
+ function laneOfQueue(q) {
49
+ const lane = laneOfBox(q.box, q.sessionId);
50
+ if (lane === undefined)
51
+ recordFailOpen("server.peer-inbound.queue-box-unknown", `cards=${q.size}`);
52
+ return lane;
57
53
  }
58
- const indexKey = (e) => JSON.stringify([e.lane, e.sessionId.toLowerCase()]);
59
54
  function awaitsReview(card) {
60
55
  return card.decision === "held" && isReviewCause(card.cause);
61
56
  }
@@ -66,7 +61,6 @@ function queueOf(scope, e) {
66
61
  return peerHeldQueueIfAny(scope, e.sessionId, boxOfLane(e.lane));
67
62
  }
68
63
  export function createPeerInboundFace() {
69
- const index = new Map();
70
64
  const tails = new WeakMap();
71
65
  const serially = (q, seq, step) => {
72
66
  const perQueue = tails.get(q) ?? new Map();
@@ -80,37 +74,19 @@ export function createPeerInboundFace() {
80
74
  });
81
75
  };
82
76
  return {
83
- observe(notice) {
84
- switch (notice.code) {
85
- case "peer.inbound_disposition": {
86
- if (notice.detail?.disposition !== "held" || !isReviewCause(notice.detail.cause))
87
- return;
88
- const e = coordinatesOf(notice.code, notice.detail);
89
- if (e !== undefined && !index.has(indexKey(e)))
90
- index.set(indexKey(e), e);
91
- return;
92
- }
93
- case "peer.held_settled": {
94
- if (notice.detail?.heldCount !== 0)
95
- return;
96
- const e = coordinatesOf(notice.code, notice.detail);
97
- if (e !== undefined)
98
- index.delete(indexKey(e));
99
- return;
100
- }
101
- default:
102
- return;
103
- }
104
- },
105
77
  list(scope) {
106
78
  const rows = [];
107
- for (const e of index.values()) {
108
- const q = queueOf(scope, e);
109
- if (q === undefined)
79
+ for (const q of listPeerHeldQueues()) {
80
+ if (q.scope !== scope)
81
+ continue;
82
+ const reviewable = q.entries.filter(awaitsReview);
83
+ if (reviewable.length === 0)
84
+ continue;
85
+ const lane = laneOfQueue(q);
86
+ if (lane === undefined)
110
87
  continue;
111
- for (const card of q.list())
112
- if (awaitsReview(card))
113
- rows.push(rowOf(e.sessionId, e.lane, card));
88
+ for (const card of reviewable)
89
+ rows.push(rowOf(q.sessionId, lane, card));
114
90
  }
115
91
  return rows.sort((a, b) => a.heldAt - b.heldAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
116
92
  },
@@ -131,26 +107,15 @@ export function createPeerInboundFace() {
131
107
  return outcome === "gone" ? { kind: "not_found" } : { kind: "settled", outcome };
132
108
  });
133
109
  },
134
- async shutdown(scopeOfSession) {
135
- let queues = 0;
136
- let scopeUnresolved = 0;
137
- const scopes = new Map();
138
- for (const e of [...index.values()]) {
139
- const sid = e.sessionId.toLowerCase();
140
- if (!scopes.has(sid))
141
- scopes.set(sid, await scopeOfSession(e.sessionId).catch(() => undefined));
142
- const scope = scopes.get(sid);
143
- if (scope === undefined) {
144
- scopeUnresolved += 1;
145
- continue;
146
- }
147
- const q = queueOf(scope, e);
148
- if (q === undefined)
149
- continue;
150
- await q.shutdown();
151
- queues += 1;
152
- }
153
- return { queues, scopeUnresolved };
110
+ async shutdown() {
111
+ const snapshots = listPeerHeldQueues();
112
+ const queues = snapshots.flatMap((s) => {
113
+ const q = peerHeldQueueIfAny(s.scope, s.sessionId, s.box);
114
+ return q === undefined ? [] : [q];
115
+ });
116
+ const settled = await Promise.allSettled(queues.map((q) => q.shutdown()));
117
+ const receiptFaults = settled.flatMap((r) => (r.status === "rejected" ? [r.reason instanceof Error ? r.reason.message : String(r.reason)] : []));
118
+ return { queues: queues.length, cards: snapshots.reduce((n, s) => n + s.size, 0), receiptFaults };
154
119
  },
155
120
  };
156
121
  }
@@ -4,7 +4,6 @@ import type { StagingHandle } from "../session-sync-kernel.js";
4
4
  import { type PlacementAcquireOpts } from "./session-placement.js";
5
5
  import type { InstructionSourceStore } from "../instruction-source-notice.js";
6
6
  import type { SessionWorkspaceStore } from "../session-workspace.js";
7
- import type { OwnerlessClaim } from "../delegated-session-owner.js";
8
7
  export interface CachingOptions {
9
8
  /** Idle seconds a woken session is kept warm. 0 disables caching (pure passthrough). */
10
9
  ttlSec: number;
@@ -57,8 +56,6 @@ export declare class CachingSessionStore implements SessionStore {
57
56
  * k8s 工作区空间 / SSE 探针 / resume-at 读的都是本类。 */
58
57
  readonly headOf: SessionHeadReader["headOf"];
59
58
  readonly register?: (sessionId: string, owner: string | null) => Promise<void>;
60
- /** [ref] 委派继承律的认领口,纯转发(写的是行上的属主那一格 —— 只 CAS 子行自己,不碰暖缓存里的会话;停驻挂点在默认部署上读的就是本类 —— 与 `placementOf` 同转发同缺:内店记录委派树 ⇒ 两只都在)。 */
61
- readonly claimOwnerless?: OwnerlessClaim;
62
59
  /** Delegated SessionRepo seam (§0.5) — list/fork/delete operate on the DURABLE tables directly (they bypass the
63
60
  * warm cache by design; a fork/delete must read/write durable state, not a possibly-stale cached view). */
64
61
  readonly listSessions?: (opts: {
@@ -9,7 +9,6 @@ export class CachingSessionStore {
9
9
  onStaleHit;
10
10
  headOf;
11
11
  register;
12
- claimOwnerless;
13
12
  listSessions;
14
13
  fork;
15
14
  forkTo;
@@ -56,8 +55,6 @@ export class CachingSessionStore {
56
55
  const ownerAware = inner;
57
56
  if (ownerAware.register)
58
57
  this.register = ownerAware.register.bind(inner);
59
- if (ownerAware.claimOwnerless)
60
- this.claimOwnerless = ownerAware.claimOwnerless.bind(inner);
61
58
  if (ownerAware.listSessions)
62
59
  this.listSessions = ownerAware.listSessions.bind(inner);
63
60
  if (ownerAware.sessionSummaryOf)
@@ -1,6 +1,6 @@
1
1
  import type { Pool as MySqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
- import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ResumeOutcome, type ResolvedOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor, type StoreDurability, type StoreFidelity, type ToolApprovalCard } from "@sema-agent/core";
3
+ import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, type CheckpointGate, type CheckpointRowVerdict, type CheckpointState, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ResumeOutcome, type ResolvedOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor, type StoreDurability, type StoreFidelity, type ToolApprovalCard } from "@sema-agent/core";
4
4
  import { type ParkRowFacts, type RuleOfferProjection, type SkillCardFacts } from "../approval-card.js";
5
5
  import type { KindlessBatchRowSource, KindlessBatchScan } from "../approval-batch-census.js";
6
6
  import { type SqlDialect, type SqlDriver } from "./sql-driver.js";
@@ -174,6 +174,14 @@ export interface PendingCheckpoint extends ParkRowFacts, SkillCardFacts {
174
174
  * `LivePendingRow.approvalCard`。展示/分诊用,永不参与 resume / gate / CAS。(此前此处是按 `tool_name` 列派生的 `contentKind`,随 [ref] 删。)
175
175
  */
176
176
  approvalCard?: ToolApprovalCard;
177
+ /**
178
+ * 这一行**本引擎续不续得了** —— core 判官 `judgeCheckpointRow` 对这一行盘上字节的判决(core 7.62.0 [ref];与助手收件箱行的 `rowVerdict`、
179
+ * 续跑在第一级读到的拒同一个读者、同一答),逐字投、本服务不重算不筛。判官要整行:SQL 孪生按 (库内算的 `checkpoint` 文本 sha256, 引擎构建)
180
+ * 记忆({@link StoredRowProjectionMemo}),未命中才按令牌批读整行;LOCAL 孪生取 core 店摘要上的同一位。
181
+ * **在场 ⇔ 这一次列表判过这一行**:缺席只在 SQL 孪生上出现 —— 整行解不出(行照列,计 `server.approvals.park-row-blob-unreadable`,续跑面
182
+ * 对同一行响亮拒)、两次读之间行已离开待决,或两次读之间行的字节变了(判过的是另一份,不拼到这一行上;下一次列表即带)。缺席**不是** `ok`。非 `ok` 的行照列(人要看到它停在哪)。
183
+ */
184
+ rowVerdict?: CheckpointRowVerdict;
177
185
  }
178
186
  /**
179
187
  * [ref]([ref] / DEBTS [ref]):session 键读口(`findPendingTokenBySession` / `peekPendingScope`)的**行绑定**。
@@ -422,24 +430,8 @@ export declare const DECIDED_REPLAY_SCAN_CAP = 8;
422
430
  * (plan_review / dry_run_review)的判词,那些门的决议入口不是本腿,回放它们等于跨门作答。
423
431
  */
424
432
  export declare function approvalDecisionOfWinner(winner: ResolvedOutcome | undefined, boundCallId: string): "approve" | "deny" | null;
425
- /**
426
- * checkpoint blob → `pendingAction.toolCallId`。
427
- *
428
- * 🔴 三态、**不是**两态(真双库实跑抓到的缺陷,2026-08-06):「读不出」与「读出来了、但这条 park 本来就
429
- * 没有 toolCallId」是两件事,压成同一个 `undefined` 会把每一条 `plan_review`/`task_done`/`resource_limit`
430
- * 腿(core 的 `PendingAction` 联合里三个成员结构上就没有 toolCallId)都误报成坏行。前者应标 `unparseable`
431
- * 交给收敛器当「不确定」,后者是**确定的不匹配**——干净地不是候选。
432
- * - `{ readable: false }` —— JSON 坏 / 形状根本不是对象:真的读不出。
433
- * - `{ readable: true, toolCallId: null }` —— 读出来了,这条 park 无工具动作:确定不匹配。
434
- * - `{ readable: true, toolCallId: "…" }` —— 读出来了,拿去比。
435
- *
436
- * 逐层 `typeof` 收窄,不做裸 `as` 断言:blob 是持久层读回来的 `unknown`,是信任边界(宪法 [ref])。
437
- *
438
- * 🔴 [ref]:入参是**已经解过码的体**,不是一格列文本 —— 本函数不再自己 `JSON.parse`(SQL 车道的解码
439
- * 唯一属主是 `sql-json-column.ts`,见 {@link pendingActionOfColumn};local 孪生本来就递一只对象进来)。
440
- * 于是「串就再解一次」这条读法在这里按构造消失,两条车道递进来的东西同形。
441
- */
442
- export declare function pendingActionToolCallId(body: unknown): {
433
+ /** 收敛器读口对一行的身份判词(三态 + 读不出;读法见 {@link pendingActionToolCallId} / {@link candidateIdentityOf})。 */
434
+ export type CandidateIdentity = {
443
435
  readable: true;
444
436
  toolCallId: string | null;
445
437
  boundInputHash: string | null;
@@ -448,6 +440,14 @@ export declare function pendingActionToolCallId(body: unknown): {
448
440
  } | {
449
441
  readable: false;
450
442
  };
443
+ /**
444
+ * 收敛器读口的**身份判词**(两只孪生同一只;`findCheckpointCandidatesForAsk` 的 SQL / LOCAL 两形都只经这里):先问 core 判官 `judgeCheckpointRow`
445
+ * (core 7.62.0 [ref],[ref])—— 判 `lacking`(更新的引擎能续它、本引擎缺读法:格式版本超上限,或要求表点名了本引擎不认的读法词)⇒ `{ readable: false }`:
446
+ * 本副本读不准这一行,交给收敛器当「不确定」,留给能续它的副本去绑(修前 SQL 孪生按 `version` 列比 `MAX_SUPPORTED_CHECKPOINT_VERSION`,
447
+ * 射程只有版本一轴,LOCAL 孪生没有这一臂)。`damaged` **不是**这一臂:没有哪个副本续得了它,身份读得出就照常判 —— 绑上之后续跑由判官给那句固定的拒,
448
+ * 比让 ask 停在 PARKING 诚实。其余三态见 {@link pendingActionToolCallId}。
449
+ */
450
+ export declare function candidateIdentityOf(cp: Checkpoint): CandidateIdentity;
451
451
  /** `pending_steer_queue` 列承载的 CheckpointState 字段(= core `appendPendingSteer` / `readPendingSteerQueue`
452
452
  * 的入参形)。列是**唯一**权威(suspend 时写的 blob 从不带它)。 */
453
453
  type SteerColumnState = Pick<CheckpointState, "pendingSteerQueue">;
@@ -536,6 +536,8 @@ export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatc
536
536
  /** ctx 改写的认领栅栏({@link SqlCheckpointStore.rewriteCtxWhileClaimed}):后端装配绑**同一只池**上的 run 店(`plugins/store-backend.ts` 的 `checkpoint()`)。
537
537
  * 缺席 ⇒ 那一口响亮抛(装配缺陷),其余口不读它。 */
538
538
  private readonly claims;
539
+ /** 停驻行投影记忆([ref] ④;运维队列与调度视图共用,见 {@link StoredRowProjectionMemo})。进程内、按店实例。 */
540
+ private readonly storedRowMemo;
539
541
  constructor(db: SqlDriver, logger?: {
540
542
  info?(msg: string, meta?: unknown): void;
541
543
  } | undefined, opts?: SqlCheckpointStoreOptions);
@@ -711,21 +713,26 @@ export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatc
711
713
  */
712
714
  listPending(scope?: string): Promise<PendingCheckpoint[]>;
713
715
  /**
714
- * 🔴 [ref] —— 运维队列行的**技能卡两位**({@link SkillCardFacts}:清单 / 成因词)。值与收件箱行同源:core `summarizeCheckpoint` 对同一行投的摘要,经 {@link skillCardFacts} 这一只投影。
715
- * (授予绑定那一臂 —— 摘要 / 缺席词 —— 不在这里:[ref] 起由 {@link parkRowFacts} 读子树投出,本读口失败时它照在。)
716
+ * 运维队列 / 调度视图要的**整行事实**({@link StoredRowProjection}:判官的判决 + 技能卡两位),按令牌交回。
716
717
  *
717
- * 为什么不像 {@link parkRowFacts} 那几位一样直接读 `pendingAction` 子树原值:**清单** `boundGrants` 在寄存行上是未经哈希校验的原值,core 只在摘要里「哈希等于摘要」
718
- * 才发布它(「never a list beside a digest that binds another」);校验读口与哈希函数都不在 core 的公开导出面上,本仓既算不出也不该再写一份 ——
719
- * 于是要拿到校验过的清单,只有对这一行跑 core 的 `summarizeCheckpoint`,而它吃的是整只 {@link Checkpoint}(不是子树)。
718
+ * 为什么要整行:判官 `judgeCheckpointRow` 读要求表要整只用(词集 + 摘要重算读整个 `state` 的表键路径,core [ref] ③);技能卡**清单**
719
+ * `boundGrants` 在寄存行上是未经哈希校验的原值,core 只在摘要里「哈希等于摘要」才发布它,校验读口不在 core 的公开导出面上 —— 两者都只能对整只
720
+ * {@link Checkpoint} 跑 core 的函数(摘要 `summarizeCheckpoint` 一次给两样)。
720
721
  *
721
- * 读放大(如实):**只对行上可能带两位之一的行**(摘要是串,或成因词键在场 —— {@link parkRowCarriesSkillCardFacts})多读该行一次整只快照;普通卡(绝大多数行)零额外读,
722
- * 主 SELECT 仍只取 `pendingAction` 子树(NP-3 不退)。这是一个**只增不改的代价**:带两位之一的待决行数 × 读次数(`/stream` 每连接每 3s 一拍)。
723
- * 🚧 清单校验读口成为 core 公开导出的那一版,本读口与门一并退役,两位改走 `parkRowFacts` 读原值经 core 的读口 —— 零额外读。
722
+ * 读放大(如实):记忆命中零整行读;未命中的行按令牌**批**读(`IN (…)`,每批 ≤ {@link STORED_ROW_READ_BATCH}),每份字节每个进程一次 ——
723
+ * 起服后第一次列表把全部待决行读一遍,此后只有字节变了 / 被挤出记忆的行再读(修前:普通卡零整行读,技能卡每次列表都读;`/stream` 每连接每 3 s 一拍)。
724
+ * 判过的按**读回的那份字节**的摘要入记忆;两次读之间字节变了(批读的摘要 ≠ 主读交来的那一份)⇒ 判过的那一份照入记忆,但**不交回**给这一行 ——
725
+ * 交回的事实只许出自主读读到的同一份字节(一行一个字节版本;修前按令牌拼,B 的判决与清单配 A 的行),这一行本次不带判决与清单(与「离开待决」同一种缺席),
726
+ * 下一次列表主读读到新字节即命中记忆。
724
727
  *
725
- * 失败方向与 {@link readParkRowActionCell} 逐字同一条判据(展示/分诊材料、行级容忍 + 响亮、同一个 tag):整只快照在场却不是一只合法的检查点(读回的体坏了,或 core 的摘要读不动它)
726
- * ⇒ 只丢**这一行**的两位(授予绑定那一臂照在);行已不在待决(两次读之间被决/过期)⇒ 两位缺席;`not-text`(读腿漏了投影)原样抛。
728
+ * 失败方向(展示 / 分诊材料、行级容忍 + 响亮,与 {@link readParkRowActionCell} 同一条判据同一个 tag):整行解不出 ⇒ 这一行不在返回里(调用方行照列、
729
+ * 不带判决与清单),计 `server.approvals.park-row-blob-unreadable`;体是对象但 core 的摘要读不动(缺 `pendingAction` / `gate` 的手改行)⇒ 判决照给
730
+ * (判官是全函数),清单与成因词丢、同一个 tag 计数;两种都不进记忆(下一次列表重读、再计)。行已离开待决(两次读之间被决 / 过期)⇒ 不在返回里。
731
+ * `not-text`(读腿漏了投影)与摘要格形不对原样抛(码坏)。授予绑定那一臂(摘要 / 缺席词)不在这里:由 {@link parkRowFacts} 读子树投出,本读口失败时它照在。
727
732
  */
728
- private readParkedSkillCardFacts;
733
+ private storedRowProjections;
734
+ /** 一行整行读回 → 它的 {@link StoredRowProjection}(完整的入记忆;失败方向见 {@link SqlCheckpointStore.storedRowProjections})。 */
735
+ private projectStoredRow;
729
736
  /**
730
737
  * [ref] #2 (scheduler seam, core 1.105): a scope's PENDING checkpoints as lightweight
731
738
  * {@link CheckpointSummary} rows in ONE query — the cross-task triage view a supervisor scheduler needs
@@ -772,14 +779,14 @@ export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatc
772
779
  * 若只看 pending 就会把「park 成功过」这个既成事实读成「没 park 过」⇒ 又是一条假阴性 DENIED
773
780
  * (对账三约束③)。park 成功是事实,gate 的活性由 `status` 表达,交给收敛器自己判。
774
781
  * - **单行读不出 ⇒ 标记返回,不抛**:`unparseable` 的行由收敛器视同「不匹配」,一条坏行不许打断整段
775
- * 扫描(§8 C-6)。什么算读不出:blob 的 `version` 超出本 build 支持(`get()` 那条前向兼容门在这里
776
- * 不能 throw,否则一条超前行会让整个 session 的对账永久卡死)、或 blob JSON 坏。
782
+ * 扫描(§8 C-6)。什么算读不出:core 判官对这一行答 `lacking`(更新的引擎能续它 —— 本副本不绑,见 {@link candidateIdentityOf};
783
+ * 这里不能 throw,否则一条超前行会让整个 session 的对账永久卡死)、或 blob JSON 坏。
777
784
  *
778
785
  * 匹配是两段的:`tool_call_id` **列**是 `put()` 从 `pendingAction.toolCallId` 盖下来的权威投影,SQL 谓词
779
786
  * 先按它(或 NULL)收窄;列为 NULL 的行(无工具动作的 park,或列存在之前的旧行)靠解 blob 补判——解得出
780
787
  * 且相等才算候选,解不出就标 `unparseable`。
781
788
  * 🔴 **没有「列命中即零解析」的快路径**(原注写过,已作废,别照它优化):函数体对**每一行**无条件解
782
- * blob,原因有二 ——(a) 前向兼容门与 blob 可读性门必须门在**所有**命中路径之前(codex F3 + 确认轮:
789
+ * blob,原因有二 ——(a) 判官的 `lacking` 臂与 blob 可读性门必须门在**所有**命中路径之前(codex F3 + 确认轮:
783
790
  * 列长得对不代表 blob 读得出,放行一条读不出的行去 `bindBatch` 会把 ask 钉成 PARKED + 一张本进程读不出
784
791
  * 的 resume 坐标,而 PARKED 不可回滚);(b) `[ref]` 之后 blob 顶层的 `sourceSessionId` 是和解三元组的第一
785
792
  * 维,列命中行结构上也必须解 blob 才拿得到它。
@@ -835,7 +842,7 @@ export declare class SqlCheckpointStore implements CheckpointStore, KindlessBatc
835
842
  *
836
843
  * 刻意**不带** `status='pending'` 谓词:判据是「这一行是不是你的」,与它此刻处在哪一态无关(`get()`
837
844
  * 自己也是裸 `WHERE token = ?`);加了反而在「刚被并发消费」的窗里把属主自己的诊断吞掉。
838
- * 只 SELECT `scope` 一列 ⇒ 版本闸/坏 blob 都影响不到它(那是 `get()` 读 `version`/`checkpoint` 的事)。
845
+ * 只 SELECT `scope` 一列 ⇒ 坏 blob 影响不到它(那是 `get()` 读 `checkpoint` 的事;7.144.0 起 `get()` 不再按版本列拒,本引擎续不续由 core 判官在续跑时答)。
839
846
  */
840
847
  peekScopeByToken(token: CheckpointToken): Promise<string | null | undefined>;
841
848
  peekPendingScope(sessionId: string, binding?: PendingRowBinding): Promise<string | null | undefined>;