@sema-agent/server 7.143.0 → 7.145.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 (139) hide show
  1. package/MIGRATION.md +295 -0
  2. package/README.md +3 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +34 -16
  5. package/dist/approval-card.d.ts +31 -14
  6. package/dist/approval-card.js +8 -8
  7. package/dist/audit.d.ts +2 -21
  8. package/dist/audit.js +0 -9
  9. package/dist/boot/execution-env.js +1 -1
  10. package/dist/boot/runner-deps.d.ts +10 -5
  11. package/dist/boot/runner-deps.js +5 -4
  12. package/dist/boot/session-faces.d.ts +1 -1
  13. package/dist/boot/session-faces.js +3 -3
  14. package/dist/boot/shutdown.d.ts +1 -1
  15. package/dist/boot/shutdown.js +4 -2
  16. package/dist/boot/stage-01-config.js +2 -0
  17. package/dist/boot/stage-07-capability-layer.js +1 -1
  18. package/dist/boot/stage-09-leader.d.ts +1 -1
  19. package/dist/boot/stage-10-http-server.d.ts +1 -1
  20. package/dist/boot/stage-10-http-server.js +4 -9
  21. package/dist/capabilities/scenarios.d.ts +14 -18
  22. package/dist/capabilities/scenarios.js +1 -2
  23. package/dist/config-d-migration.d.ts +3 -0
  24. package/dist/delegated-session-owner.d.ts +33 -151
  25. package/dist/delegated-session-owner.js +1 -83
  26. package/dist/entry-mode-clamp-notice.d.ts +1 -1
  27. package/dist/execution-placement.d.ts +7 -4
  28. package/dist/fleet/fleet-bus.d.ts +13 -3
  29. package/dist/fleet/fleet-bus.js +17 -3
  30. package/dist/hooks/classic-stations.d.ts +3 -1
  31. package/dist/hooks/classic-stations.js +2 -0
  32. package/dist/http/admission.js +2 -1
  33. package/dist/http/checkpoint-ctx.d.ts +1 -0
  34. package/dist/http/checkpoint-ctx.js +1 -0
  35. package/dist/http/durable-run-leg.js +7 -6
  36. package/dist/http/principal-gate.d.ts +1 -1
  37. package/dist/http/refusal-body.d.ts +7 -3
  38. package/dist/http/refusal-body.js +1 -0
  39. package/dist/http/resume-legs.js +16 -7
  40. package/dist/http/route-ctx.d.ts +15 -0
  41. package/dist/http/routes/approvals-assistant.d.ts +20 -1
  42. package/dist/http/routes/approvals-assistant.js +28 -6
  43. package/dist/http/routes/rules.d.ts +1 -1
  44. package/dist/http/routes/rules.js +5 -2
  45. package/dist/http/routes/session-sync.js +12 -0
  46. package/dist/http/routes/tasks.js +12 -9
  47. package/dist/http/run-meta.d.ts +14 -7
  48. package/dist/http/run-meta.js +5 -2
  49. package/dist/http/server.d.ts +13 -5
  50. package/dist/http/server.js +3 -0
  51. package/dist/http/submit-ctx.d.ts +12 -0
  52. package/dist/http/submit-ctx.js +23 -0
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.js +1 -0
  55. package/dist/lane-home-probe.d.ts +15 -0
  56. package/dist/lane-home-probe.js +4 -0
  57. package/dist/observability/corrupt-read-seat.d.ts +5 -9
  58. package/dist/observability/corrupt-read-seat.js +1 -2
  59. package/dist/observability/fail-open.d.ts +26 -10
  60. package/dist/observability/fail-open.js +26 -10
  61. package/dist/observability/run-terminal.d.ts +1 -1
  62. package/dist/observability/run-terminal.js +2 -2
  63. package/dist/parked-revive-surface.d.ts +6 -7
  64. package/dist/peer-inbound-face.d.ts +29 -34
  65. package/dist/peer-inbound-face.js +28 -63
  66. package/dist/plugins/approval-ask-store-file.d.ts +16 -2
  67. package/dist/plugins/approval-ask-store-file.js +21 -1
  68. package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
  69. package/dist/plugins/approval-ask-store-memory.js +11 -8
  70. package/dist/plugins/caching-session-store.d.ts +3 -3
  71. package/dist/plugins/caching-session-store.js +3 -3
  72. package/dist/plugins/checkpoint-store-sql.d.ts +62 -37
  73. package/dist/plugins/checkpoint-store-sql.js +123 -40
  74. package/dist/plugins/file-run-store.d.ts +13 -1
  75. package/dist/plugins/file-run-store.js +11 -1
  76. package/dist/plugins/fork-routing-session-store.d.ts +8 -1
  77. package/dist/plugins/fork-routing-session-store.js +6 -0
  78. package/dist/plugins/local-checkpoint-store.d.ts +17 -8
  79. package/dist/plugins/local-checkpoint-store.js +26 -7
  80. package/dist/plugins/local-session-store.d.ts +105 -34
  81. package/dist/plugins/local-session-store.js +168 -65
  82. package/dist/plugins/local-session-window.d.ts +4 -2
  83. package/dist/plugins/memory-run-store.d.ts +2 -0
  84. package/dist/plugins/memory-run-store.js +6 -0
  85. package/dist/plugins/one-time-migrations.d.ts +12 -12
  86. package/dist/plugins/one-time-migrations.js +13 -13
  87. package/dist/plugins/pg-session-storage.d.ts +1 -9
  88. package/dist/plugins/pg-session-storage.js +2 -19
  89. package/dist/plugins/remote-env-host.d.ts +8 -4
  90. package/dist/plugins/remote-env-host.js +7 -9
  91. package/dist/plugins/remote-shell.js +12 -2
  92. package/dist/plugins/run-store-sql.d.ts +4 -0
  93. package/dist/plugins/run-store-sql.js +3 -0
  94. package/dist/plugins/schema-consistency.d.ts +5 -1
  95. package/dist/plugins/session-fork.d.ts +1 -1
  96. package/dist/plugins/session-ownership.d.ts +9 -0
  97. package/dist/plugins/session-ownership.js +6 -0
  98. package/dist/plugins/session-store.d.ts +13 -16
  99. package/dist/plugins/session-store.js +9 -10
  100. package/dist/plugins/sql-json-column.d.ts +24 -2
  101. package/dist/plugins/sql-json-column.js +38 -3
  102. package/dist/plugins/store-backend.js +3 -2
  103. package/dist/plugins/tidb-session-store.d.ts +2 -14
  104. package/dist/plugins/tidb-session-store.js +2 -20
  105. package/dist/prompt-screen.d.ts +21 -0
  106. package/dist/prompt-screen.js +8 -0
  107. package/dist/rules-consent.d.ts +63 -51
  108. package/dist/rules-consent.js +6 -10
  109. package/dist/run-acceptance.d.ts +40 -10
  110. package/dist/run-acceptance.js +38 -5
  111. package/dist/run-local.js +2 -1
  112. package/dist/runs.js +5 -1
  113. package/dist/security.d.ts +10 -7
  114. package/dist/session-agent.d.ts +39 -0
  115. package/dist/session-agent.js +16 -0
  116. package/dist/session-error-wire.d.ts +25 -26
  117. package/dist/session-error-wire.js +4 -5
  118. package/dist/session-incarnation.d.ts +2 -2
  119. package/dist/session-notify-door.d.ts +2 -2
  120. package/dist/session-notify-door.js +5 -1
  121. package/dist/tool-approval.d.ts +3 -1
  122. package/dist/tool-approval.js +47 -18
  123. package/dist/trace/core-keyset-guard.d.ts +1 -1
  124. package/dist/trace/engine-notice-wire.d.ts +1 -1
  125. package/dist/trace/engine-notice-wire.js +1 -1
  126. package/dist/trace/ledger-sink.js +12 -4
  127. package/dist/trace/model-segment.d.ts +26 -6
  128. package/dist/trace/model-segment.js +58 -9
  129. package/dist/trace/project.d.ts +8 -2
  130. package/dist/trace/project.js +6 -4
  131. package/dist/trace/redact.d.ts +77 -2
  132. package/dist/trace/redact.js +234 -99
  133. package/dist/trace/self-minted-wire.d.ts +160 -9
  134. package/dist/trace/self-minted-wire.js +52 -3
  135. package/package.json +3 -3
  136. package/wire-changes/server@7.144.0.json +291 -0
  137. package/wire-changes/server@7.145.0.json +146 -0
  138. package/dist/session-records-skipped-notice.d.ts +0 -21
  139. package/dist/session-records-skipped-notice.js +0 -27
@@ -5,8 +5,8 @@ import { durableRowIdentity } from "../checkpoint-identity.js";
5
5
  import { createHash } from "node:crypto";
6
6
  import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync } from "node:fs";
7
7
  import { join } from "node:path";
8
- import { FileCheckpointStore, atomicWriteFile, executionVerdict, sanitizePathComponent, } from "@sema-agent/core";
9
- import { approvalDecisionOfWinner, approvalPayloadFingerprint, boundedRuleOffers, boundedToolInput, pendingActionToolCallId, pendingRowPrecedes, queueRowGateKind, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-sql.js";
8
+ import { CHECKPOINT_LIST_LIMITS, FileCheckpointStore, atomicWriteFile, executionVerdict, sanitizePathComponent, } from "@sema-agent/core";
9
+ import { approvalDecisionOfWinner, approvalPayloadFingerprint, boundedRuleOffers, boundedToolInput, candidateIdentityOf, pendingRowPrecedes, queueRowGateKind, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-sql.js";
10
10
  import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
11
11
  import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, readStringArrayColumn } from "./sql-json-column.js";
12
12
  import { UNMANAGED_RETENTION } from "./retention-store-sql.js";
@@ -160,6 +160,24 @@ export class LocalCheckpointStore {
160
160
  }
161
161
  return out;
162
162
  }
163
+ async sessionPendings(sessionId, scope) {
164
+ if (sessionId === "")
165
+ return [];
166
+ const out = [];
167
+ let after;
168
+ do {
169
+ const page = await this.inner.listBySession({ sessionId, status: "pending", ...(scope !== undefined ? { scope } : {}), ...(after !== undefined ? { after } : {}), limit: CHECKPOINT_LIST_LIMITS.session.max });
170
+ for (const row of page.rows) {
171
+ const token = row.kind === "row" ? row.checkpoint.token : row.token;
172
+ const cp = row.kind === "row" ? row.checkpoint : await this.inner.get(row.token);
173
+ if (cp === null || cp.status !== "pending" || (scope !== undefined && cp.scope !== scope))
174
+ continue;
175
+ out.push({ token, cp });
176
+ }
177
+ after = page.next;
178
+ } while (after !== undefined);
179
+ return out;
180
+ }
163
181
  async listPendingSessionIds() {
164
182
  return [...new Set((await this.pendings()).map(({ cp }) => cp.sessionId))];
165
183
  }
@@ -194,12 +212,13 @@ export class LocalCheckpointStore {
194
212
  ...((card) => (card !== undefined ? { approvalCard: card } : {}))(parkedApprovalCard(cp.gate)),
195
213
  ...parkRowFacts(cp.pendingAction),
196
214
  ...skillCardFacts(summary),
215
+ rowVerdict: summary.rowVerdict,
197
216
  });
198
217
  }
199
218
  return rows.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
200
219
  }
201
220
  async findPendingTokenBySession(sessionId, scope, binding) {
202
- return this.pickPendingRow(await this.pendings(scope || undefined), sessionId, binding)?.token ?? null;
221
+ return this.pickPendingRow(await this.sessionPendings(sessionId, scope || undefined), sessionId, binding)?.token ?? null;
203
222
  }
204
223
  pickPendingRow(rows, sessionId, binding) {
205
224
  let best;
@@ -276,7 +295,7 @@ export class LocalCheckpointStore {
276
295
  if (cp.scope !== scope || cp.sessionId !== sessionId || cp.createdAt < sinceMs)
277
296
  continue;
278
297
  const base = { token: t, status: cp.status, createdAtMs: cp.createdAt };
279
- const derived = pendingActionToolCallId(cp);
298
+ const derived = candidateIdentityOf(cp);
280
299
  if (!derived.readable) {
281
300
  out.push({ ...base, sourceSessionId: null, boundCallId: null, boundInputHash: null, unparseable: true });
282
301
  continue;
@@ -291,7 +310,7 @@ export class LocalCheckpointStore {
291
310
  return (await this.inner.get(token))?.scope;
292
311
  }
293
312
  async peekPendingScope(sessionId, binding) {
294
- return this.pickPendingRow(await this.pendings(), sessionId, binding)?.cp.scope;
313
+ return this.pickPendingRow(await this.sessionPendings(sessionId), sessionId, binding)?.cp.scope;
295
314
  }
296
315
  async reapExpired(cutoff) {
297
316
  let n = 0;
@@ -414,8 +433,8 @@ export class LocalCheckpointStore {
414
433
  return 0;
415
434
  }
416
435
  let n = 0;
417
- for (const { token, cp } of await this.pendings()) {
418
- if (cp.sessionId === sessionId && (await this.inner.expire(token, cp.scope)))
436
+ for (const { token, cp } of await this.sessionPendings(sessionId)) {
437
+ if (await this.inner.expire(token, cp.scope))
419
438
  n++;
420
439
  }
421
440
  const p = join(this.ctxDir, ctxFileOf(sessionId));
@@ -1,5 +1,4 @@
1
1
  import { FileSessionRepo, type AcquiredSession, type PlacedSessionRow, type SessionHead, type SessionPlacementRecord, type SessionStore, type SessionRepo, type SessionMetadata, type SessionTreeEntry, type StoreDurability } from "@sema-agent/core";
2
- import { type OwnerlessClaim } from "../delegated-session-owner.js";
3
2
  import { type PlacementAcquireOpts } from "./session-placement.js";
4
3
  import { z } from "zod";
5
4
  import { StoreBootRefusalError, type SessionRunClaimSource, type SessionRunRollupSource, type SessionSummary } from "./store-contracts.js";
@@ -30,11 +29,39 @@ export declare const SESSION_META_CELL_OBJECT: z.ZodObject<{
30
29
  createdAt: z.ZodOptional<z.ZodString>;
31
30
  forkedFrom: z.ZodOptional<z.ZodString>;
32
31
  }, z.core.$strict>;
32
+ /** 严格边档清册里一只边档的对账读数(拒句的逐文件一段;{@link LocalDataRootSchemaMismatchError.findings})。 */
33
+ export interface LocalSidecarMismatch {
34
+ /** 文件的绝对路径。 */
35
+ readonly path: string;
36
+ /** 角色(`<文件名> (the local <角色> sidecar)`)。 */
37
+ readonly label: string;
38
+ /** 读不出的种别:`not-json` / `malformed`(形),或 I/O 的 errno(`EACCES` …;没有 errno ⇒ `unreadable`)。 */
39
+ readonly problem: string;
40
+ /** 格里本构建不认得的键(非空 ⇒ 这只边档是更新的引擎写下的:数据根比本构建新)。 */
41
+ readonly unknownFields: readonly string[];
42
+ }
43
+ /**
44
+ * **local 数据根的「本构建读不出」拒**(起服对账一族的 local 腿;机读码与 SQL 列册对账同一个 `config.schema_mismatch`,拒句同形)。
45
+ * 一只严格边档用它那一只严格读者读不出 ⇒ 这一只拒,文件原地不动、一个字节不改。两处触发、一只类:
46
+ * · `boot` —— 起服对账({@link LocalSessionStore.assertStrictSidecarsReadable},唯一调用点 `LocalBackend.ensureSchema()`)把清册里每一只读不出的合成一只:拒启;
47
+ * · `serving` —— 起服之后这只边档第一次被碰时才读不出(嵌入方跳过了 `ensureSchema()`;或起服后别的写者把它写成本构建读不出的形,在替换之前那一读里看见):
48
+ * 读不出的是**形**(`not-json` / `malformed`,确定的判词)⇒ 这只边档在本进程里从此每一读每一写都交这一只(重启才重判);碰它的请求失败(把它交给 HTTP 全局兜底的路由:
49
+ * 500 `internal.error` + 一行 error 级 `request_error` 带本码;自己接住故障的路由按各自的 500 臂答)。I/O 故障(非确定)不粘。
50
+ * 属 {@link StoreBootRefusalError} 族:起服降级臂不吞它(缺省推导的 local 不会被吃成内存后端)。拒句零原文:只点名文件、种别与不认得的键名,不带任何值。
51
+ */
52
+ export declare class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
53
+ readonly findings: readonly LocalSidecarMismatch[];
54
+ readonly when: "boot" | "serving";
55
+ /** 机读码(与 SQL 列册对账 / 退役 schema 的拒启同一个)。 */
56
+ readonly code = "config.schema_mismatch";
57
+ constructor(findings: readonly LocalSidecarMismatch[], when: "boot" | "serving");
58
+ }
33
59
  /**
34
60
  * local 车道会话存档的**唯一 repo 出口**:本仓里每一只读 `<root>/sessions/*.jsonl` 的 `FileSessionRepo` 都从这里取(`LocalSessionStore` 的底座;会话视图窗不再自开 repo,
35
61
  * 它的元数据与日志都由店交出 —— `local-session-window.ts`),所以坏记录的披露座只有这一份接线。座 = F 类 `server.session-repo.corrupt-record-skipped`(会话存档里读不出的记录丢的是可用性:
36
- * 历史少一截 / 列表少一行,不开任何门;计数单位 = 一条被跳过的记录,逐命中探针行,warn 每进程一次),外加两条作用域腿:会话打开作用域(`noteReplayInSessionOpenScope`:
37
- * 只在 `openSessionForItself` 的作用域里才记)与一次性迁移 M33 的整档读作用域({@link noteReplayInArchiveRewriteRead});作用域外 —— 列表 / 导出 / 视图 —— 两条腿都是空操作,那些读只进运维面。
62
+ * 历史少一截 / 列表少一行,不开任何门;计数单位 = 一条被跳过的记录,逐命中探针行,warn 每进程一次),外加一条作用域腿:一次性迁移 M33 的整档读作用域({@link noteReplayInArchiveRewriteRead});
63
+ * 作用域外那条腿是空操作。用户面的丢失账不经这只座:core 的 `FileSessionRepo` 把回放的计数记在它交出的存储上(`getReplayLoss`),引擎在 run 里读它说
64
+ * `session.lineage_rerooted`(core 7.62.0 [ref];本仓 7.104.0–7.143.0 在这里另铸 `session.records_skipped`,7.144.0 退役 —— 一个事实一个来源)。
38
65
  */
39
66
  export declare function createLocalSessionRepo(root: string): FileSessionRepo;
40
67
  /** 快照目录名(数据根下;本店自有的目录与格式)。常设拒启臂 {@link assertNoArchiveSnapshots} 读它,所以它活得比 M33 的码久。 */
@@ -101,11 +128,6 @@ export declare class LocalSessionStore implements SessionStore {
101
128
  olderThanMs?: number;
102
129
  scope?: string;
103
130
  }) => Promise<PlacedSessionRow[]>;
104
- /**
105
- * 委派继承律的认领口(`delegated-session-owner.ts`;停驻挂点对宿主无主子代行的认领):这只会话的那一位住客(判断时读到的化身)仍无主、且是 placed 行 ⇒ 写 `owner`,
106
- * 返回是否写成。与 `placementOf` 同在或同缺(`delegatedOwnerHostOf`)。随一次性迁移 M23 的窗同删(登记表 M23 行)。
107
- */
108
- readonly claimOwnerless?: OwnerlessClaim;
109
131
  /** placement 的唯一读法(repo 的 `placementOf`;repo 不记 placement ⇒ 恒答缺席 —— 那种 repo 里没有 placed 行)。 */
110
132
  private readonly recordedPlacementOf;
111
133
  /** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one (mirrors TiDBSessionStore). */
@@ -170,7 +192,12 @@ export declare class LocalSessionStore implements SessionStore {
170
192
  * 盘上有会话而边档里没有它那一格 ⇒ 第一次读补铸一格只有化身的(属主没有事实可写)并落盘。严格读(读不出 ⇒ 整只拒,见 `SidecarMap.strict`)。
171
193
  */
172
194
  private readonly meta;
173
- /** 认领行(`session_owner_claimed`,一次性迁移 M10 的认领臂,窗口见 `plugins/one-time-migrations.ts` 登记表)与委派孤行 warn(`delegated_session_owner_orphaned`)的出口。 */
195
+ /**
196
+ * [ref] —— 本店的**严格边档清册**(今天一只:会话元数据)。唯一来源 = 构造里 `sidecar(…, strict)` 那一参:工厂造出严格读的边档时同一手登进来,
197
+ * 没有第二张手写名单。读者只有起服对账 {@link assertStrictSidecarsReadable}。
198
+ */
199
+ private readonly strictSidecars;
200
+ /** 认领行(`session_owner_claimed`,一次性迁移 M10 的认领臂,窗口见 `plugins/one-time-migrations.ts` 登记表)的出口(本店今天只有这一行经它;委派孤行 warn 的铸点已不在 `src/`)。 */
174
201
  private readonly log;
175
202
  /**
176
203
  * 按会话的写锁(表见 {@link SESSION_WRITE_LOCKS_BY_ROOT})—— 会话行两位的**每一笔**写(用户的行格写原语:判 → 边档草稿 → 落盘 → 发布;自动标题器的
@@ -204,12 +231,18 @@ export declare class LocalSessionStore implements SessionStore {
204
231
  * @param dataDir the SAME data dir the file-backed repo persists under (LocalBackend passes `fileBackend.root`).
205
232
  * When given, lastActivity is persisted to `<dataDir>/session-last-activity.json` so it survives a
206
233
  * restart (the file-backed deployment); omit it for the in-memory deployment (nothing survives).
207
- * @param opts.log the `session_owner_claimed` / `delegated_session_owner_orphaned` lines' sink (LocalBackend passes a logger at the deployment's
208
- * LOG_LEVEL; absent ⇒ an info-level JSON-line logger — a claim or an orphaned delegated row is never silent).
234
+ * @param opts.log the `session_owner_claimed` line's sink (LocalBackend passes a logger at the deployment's
235
+ * LOG_LEVEL; absent ⇒ an info-level JSON-line logger — a claim is never silent).
209
236
  * @param opts.durability / opts.placements the deployment's declaration ({@link LocalSessionStoreDeclaration}; see the two fields). */
210
237
  constructor(repo: HeadReadingSessionRepo & Partial<PlacementRecordingRepo>, runs: SessionRunRollupSource & SessionRunClaimSource, dataDir?: string, opts?: {
211
238
  log?: Pick<Logger, "info" | "warn">;
212
239
  } & LocalSessionStoreDeclaration);
240
+ /**
241
+ * [ref] —— **起服对账的 local 腿**(SQL 列册对账的对位;唯一调用点 `LocalBackend.ensureSchema()`,排在 M33 快照消化之前 —— 消化会写会话元数据格):
242
+ * 严格边档清册里的每一只读一次盘(`SidecarMap.reconcile`:冷 ⇒ 载入那一读;已载入 ⇒ 重读只判);读不出的合成**一只** {@link LocalDataRootSchemaMismatchError}
243
+ * (`boot` 形,逐文件一段)⇒ 拒启,开始服务之前。每一只只会以那一只拒失败(严格读的拒臂把读不出的每一形都收成它);别的故障原样上抛(响亮,不归并)。内存部署(没有数据根)⇒ 零读。
244
+ */
245
+ assertStrictSidecarsReadable(): Promise<void>;
213
246
  /**
214
247
  * 这一格此刻是不是 `forking`(fork 创建的格 {@link isForkCreatedCell} ∧ 存档不在)。存档在不在只问 repo 的唯一探针 {@link probeArchive} 的「不在」—— **只问有没有**:存档在而用不了(会话头读不出 / 截断)
215
248
  * 是「在」,内容读不读得出归真正读内容的那一读(门之后、体之前读一次叶子,读不出 ⇒ `corrupt_session`)。所以带键的会话与不带键的会话在执行席上同形,坏档照样能被换装修好
@@ -258,10 +291,10 @@ export declare class LocalSessionStore implements SessionStore {
258
291
  private withSessionWriteLock;
259
292
  /**
260
293
  * 盘上这只会话的存档此刻是什么状态(repo 的**唯一探针**;`open` 读整只存档,所以只在边档里没有那一格、或要判 `forking` 时才走到这里)。三种答案:
261
- * 不在(`not_found`)· 在而读得出 · 读不出({@link UnusableArchive}:`corrupt_session` / `invalid_session` = 在而内容用不了;`storage` = 读的时候抛了别的故障,I/O 级,
294
+ * 不在(`not_found`)· 在而读得出 · 读不出({@link UnusableArchive}:`corrupt_session` = 在而内容用不了;`storage` = 读的时候抛了别的故障,I/O 级,
262
295
  * 说不出在不在 —— 词的读法见 {@link archiveFaultWord})。探针**不吞也不改**那只故障:{@link UnusableArchive.fault} 就是抛出来的那一只,每个读者各自决定上抛还是点名 ——
263
296
  * 要对这份存档动手的({@link existsInRepo};{@link metaOf};{@link readExecutionPlacement})一律上抛它;一次性迁移 M32 的回填({@link executionPlacementBackfill})经
264
- * {@link metaRead} / {@link forkingRead} 收下它、点名跳过,不上抛。只问有没有的 {@link forkingRead} 把 `corrupt_session` / `invalid_session` 读作「在」(存档找到了),`storage` 不替它答。
297
+ * {@link metaRead} / {@link forkingRead} 收下它、点名跳过,不上抛。只问有没有的 {@link forkingRead} 把 `corrupt_session` 读作「在」(存档找到了),`storage` 不替它答。
265
298
  */
266
299
  private probeArchive;
267
300
  /**
@@ -270,7 +303,7 @@ export declare class LocalSessionStore implements SessionStore {
270
303
  */
271
304
  private placementProbe;
272
305
  /**
273
- * 改写面与执行席的读法({@link placementProbe} 的一形):placed ⇒ 那份元组;普通 ⇒ `undefined`;行头读不出(`corrupt_session` / `invalid_session`)⇒ `undefined` —— 没有可读的
306
+ * 改写面与执行席的读法({@link placementProbe} 的一形):placed ⇒ 那份元组;普通 ⇒ `undefined`;行头读不出(`corrupt_session`)⇒ `undefined` —— 没有可读的
274
307
  * placement 记录,这一行按普通行答,内容读不读得出归真正读内容的那一读(坏档照样换得掉,与 forking 探针同一条规矩);说不出(`storage`)⇒ 抛那只故障。
275
308
  * core 7.61.0 起 repo 的探针按行头的元组自己读、不整头解码:placed 行的属主格坏了探针照答元组(本臂不再接得住 placed 行)—— 探针拒只剩「行头记录整个读不出」,
276
309
  * 那时没有任何元组可守(`test/local-session-placement.test.ts` 的 [ref] 三格)。
@@ -290,7 +323,7 @@ export declare class LocalSessionStore implements SessionStore {
290
323
  private metaOf;
291
324
  /**
292
325
  * {@link metaOf} 的本体(三态,不抛「用不了」):边档里有那一格 ⇒ 读它;没有 ⇒ 问**唯一探针** {@link probeArchive} 一次 —— 不在 ⇒ 无此会话;在而读得出 ⇒ 补铸({@link cellOfPresent});
293
- * 读不出 ⇒ {@link UnusableArchive}(**不补铸**:不替一份读不出的存档铸化身;`corrupt_session` / `invalid_session` 两词的故障换成具名拒 {@link SessionOwnerUnreadableError} ——
326
+ * 读不出 ⇒ {@link UnusableArchive}(**不补铸**:不替一份读不出的存档铸化身;`corrupt_session` 的故障换成具名拒 {@link SessionOwnerUnreadableError} ——
294
327
  * 格缺 ∧ 存档用不了 = 这只会话没有可读的属主,见 {@link ownerUnreadableArchive})。要动存档的读者({@link metaOf})把它上抛;不动存档、只要「有没有」的读者
295
328
  * ({@link executionPlacementBackfill})原样收下。
296
329
  */
@@ -315,8 +348,9 @@ export declare class LocalSessionStore implements SessionStore {
315
348
  */
316
349
  headOf(sessionId: string): Promise<SessionHead | undefined>;
317
350
  /**
318
- * 引擎仓存档的头(core `SessionRepo.headOf`:日志首记录与尾,不重放)—— 本店读存档头的**唯一**一处。存档头在本店只有两种读法:叶子(读内容的人要的那一格)
319
- * 与行代号(换装 / 一次性迁移的 `expect.row`、迁移判「有没有、读不读得出」);属主 / 建档时刻 / 谱系从不从这里读(规则一,行事实只住边档)。
351
+ * 引擎仓存档的头(core `SessionRepo.headOf`:日志首记录与尾,不重放)—— 本店读存档头的**唯一**一处。存档头在本店只有三种读法:叶子(读内容的人要的那一格)、
352
+ * 行代号(换装 / 一次性迁移的 `expect.row`、迁移判「有没有、读不读得出」)与带戳判({@link assertArchiveAnonymous};读的是存档的**形** —— 头上有没有属主戳 —— 不是属主事实);
353
+ * 属主 / 建档时刻 / 谱系作为行事实从不从这里读(规则一,行事实只住边档)。
320
354
  */
321
355
  private archiveHeadOf;
322
356
  /**
@@ -451,21 +485,47 @@ export declare class LocalSessionStore implements SessionStore {
451
485
  /** 这次 acquire 要落的 placement:只有声明了分区的店才收(未声明 ⇒ `undefined`,会话就是普通会话 —— core `SessionStore.placements` 缺席的读法)。 */
452
486
  private declaredPlacement;
453
487
  private load;
454
- /** 为这只会话打开它自己的存档,锁内一形(调用方持这只会话的写锁,见 {@link load})。[ref]:这是「为这只会话打开它自己的存档」的那一次
455
- * (引擎 prepare 的 acquire)⇒ 包在打开作用域里:这次回放跳过了记录 ⇒ 打开成功后铸一帧 `session.records_skipped` 投给这只会话(唯一铸点
456
- * 与作用域见 `session-records-skipped-notice.ts`);读不出(`corrupt_session`)照旧原样上抛,不铸。存档只读这一次:住客那一格缺(更早版本
488
+ /** 为这只会话打开它自己的存档,锁内一形(调用方持这只会话的写锁,见 {@link load})。回放跳过了记录 ⇒ 交出的存储带着那份丢失账(core 文件店的
489
+ * `getReplayLoss`,经 {@link occupiedBy} 的视图原样转发),引擎在 run 里说 `session.lineage_rerooted`;读不出(`corrupt_session`)原样上抛。存档只读这一次:住客那一格缺(更早版本
457
490
  * 写下的会话)时,这次打开就是补铸的证明({@link handOut})。GUARD(open 腿):打开一只已存在的会话**不写属主** —— 记下的属主(含「登记为
458
491
  * 无主」)一个字节不动,没被记下的也不在这里替它记成无主:打开不是一个属主事实(7.119.0 前这一腿把「不知道」记成进程内存里的 null,重启即失)。 */
459
492
  private openForItselfHeld;
460
493
  /**
461
- * 带 placement 的按 id 首建(锁内一形,调用方持这只会话的写锁、已判过存档不在且不是 fork 创建的格)。写序 = **判别位先落**:先建存档(引擎文件 repo 把元组写进行头、
462
- * 首写不可变),再写会话元数据那一格(化身新铸;属主按委派继承律 —— 根会话的属主,根行不在取父,都不在 ⇒ 孤行哨兵 + 一行 warn;读法与次序只住在
463
- * `delegated-session-owner.ts` 的 `createPlacedSessionRow`,三只店同调)。崩在两笔之间 = 存档在而格缺 = 一只 placed 行、属主从没被记下(读作无主;
464
- * 这形只在 fleet 调用方可读,停驻挂点的认领口 {@link claimOwnerless} 按委派律补上)。
465
- * 格已在而存档不在(登记先落了格、存档还没建 —— 一只普通会话的行)⇒ `placement_refused`(SQL 孪生:登记过的行在,带 placement 的建读回缺席元组,同一判)。
466
- * 建后读回准入(镜像方向):别的进程(同一数据根上的 run-local 腿)在这一刻抢先建了同 id 的存档 ⇒ repo 返回的是那一只 ⇒ 元组缺席 / 异组 ⇒ 响亮拒。
494
+ * 带 placement 的按 id 首建(锁内一形,调用方持这只会话的写锁、已判过存档不在且不是 fork 创建的格)。写序 = **格先、存档后**(与登记先记属主再建存档、fork 先写目标格再拷存档、
495
+ * 导入先落格再导条目同一序;普通 acquire 的两条首建 —— 无 id 新建、按 id 缺档新建 —— 仍是存档先、格后:那两处写的属主是「无主」,崩在两笔之间留下的存档补铸后同样读作无主,
496
+ * 与「登记为无主」只在 M10 认领窗内有别,不在本条):
497
+ * 1. 会话元数据那一格:化身新铸;属主 = `placement.owner` 原样(core 7.62.0 [ref] 盒义务 7:委派树的属主是引擎判的,店只记、不推断;唯一读法 `placedSessionOwnerOf`,
498
+ * 坏值 ⇒ 响亮拒、一个字节不写;SQL 孪生把它与元组写进同一条 INSERT);建档时刻同一笔写(铸行点律);
499
+ * 2. 存档:元组 = `placement` 去掉属主(`SessionPlacementRecord` 没有属主 —— core 自己的店同样只把元组交给 repo),不交属主(引擎仓是匿名内容仓)。
500
+ * 崩在两笔之间 ⇒ 格在而存档不在 = 一次没建完的建行(与登记 / fork / 导入崩溃留下的同一族;不是 placed 行:不上列表、不进分区枚举、`requireExisting` ⇒ not_found、
501
+ * 同 id 再带 placement 建 ⇒ 下面那一判 `placement_refused`)—— 没有任何一刻盘上是「placed 存档在而格缺」(属主从没被记下)。保证到进程崩溃为止(那一格改名落定在建存档之前;
502
+ * 不另加 fsync 屏障,与另外三个建行点同)。
503
+ * 「placed 存档在而格缺」此后只剩三个来源,都照旧读作无主(补铸的格不写属主:属主没有事实可写),在具名树的停驻挂点被否决、响亮,匿名树 `null === null` 照放 ——
504
+ * ① 7.144.x 及更早崩在两笔之间留下的存量行(当年存档先建;不回填:行头属主格是空的,元组里那一份属主是本店当年漏进去的未声明成员,不作来源);
505
+ * ② `release` 删了格、没删存档就崩(判别位最后删;它的 agent 行已先删,没有复活腿会碰它,重发 release / 分区收割收完);③ 同一数据根上 run-local 腿经 core 店写下的 placed 存档
506
+ * (core 形:属主在行头,本店不读行头属主)。SQL 孪生的存量无主 placed 行由 M23 在窗内回填。
507
+ * 格已在而存档不在(登记先落了格、存档还没建 —— 一只普通会话的行;或本条崩在两笔之间留下的格)⇒ `placement_refused`(SQL 孪生:登记过的行在,带 placement 的建读回缺席元组,同一判)。
508
+ * 建后读回准入(镜像方向):别的写者在这一刻抢先建了同 id 的存档 ⇒ repo 返回的是那一只 ⇒ 元组缺席 / 异组 ⇒ 响亮拒。
509
+ * 第 2 步失败时那一格撤不撤,按「这只 id 上的存档是不是本次的」判,先认确证、说不准才问盘:
510
+ * · 确证不是本次的 ⇒ {@link withdrawCreatedCell} 撤(留着它 = 一只属主落在别人存档上的格):读回准入拒(读到的就是 create 交回那一只存档的元组)·
511
+ * core 的 create 抛 `conflict`(它判的「别的写者在存在判与写之间抢先落盘」);确证不被之后的读推翻;
512
+ * · 说不准(create 抛别的故障 —— 可能在行头已落盘之后,core 的 create 写完行头还要开追加日志;读元数据抛)⇒ 问盘({@link holdsThisPlacement}):本次元组下的 placed 存档
513
+ * ⇒ 那一格就是它的属主事实,留着(撤了正是「placed 存档在而格缺」);不在 / 普通 / 别的元组 ⇒ 撤;读不出 ⇒ 留着(残格惰性;撤了可能丢属主 —— 别的写者同一刻抢建同 id
514
+ * 又恰好不是 `conflict` 的那一形;数据根锁之下没有第二个进程,进程内同 id 的写者都排在这只会话的写锁上,这一形只有注入造得出,如实登记不另设臂)。
515
+ * 确证两臂同理是防线:这一节在写锁里、打开刚答「不在」,正常路上 create 交回的就是本次建的那一只。
516
+ * 撤完(或留下)之后上抛第 2 步的那一只故障。
467
517
  */
468
518
  private createPlacedHeld;
519
+ /**
520
+ * 带 placement 的建行第 2 步说不准地失败之后,盘上这只 id 是不是本次元组下的 placed 存档 —— 判据就是读回准入那一只(`admitSessionPlacement`:缺席 / 普通 / 异组 ⇒ 不是;
521
+ * 它是纯比较,只抛 `placement_refused`),读的是 {@link placementProbe}(行头上的元组,唯一一读)。读不出(`corrupt_session` / `storage`)⇒ 答「是」(见调用方)。
522
+ */
523
+ private holdsThisPlacement;
524
+ /**
525
+ * 格先、存档后的建行点(fork 的目标 · 带 placement 的建行)建存档那一步失败时,撤回第一步刚写的那一格(调用方持这只会话的写锁):撤得回 ⇒ 返回,调用方原样上抛那只故障;
526
+ * 撤不回 ⇒ 两只故障同句点名(句子由调用方给,带出路),原故障挂在 `cause` —— 留下的格就是崩在两步之间的那一形。
527
+ */
528
+ private withdrawCreatedCell;
469
529
  /**
470
530
  * Idempotently record a session's owner — never overwrites a RECORDED owner (incl. a recorded `null`, "registered ownerless";
471
531
  * mirrors TiDBSessionStore.register's first-write rule). TiDB parity: register INSERTs a session_meta row so the session EXISTS
@@ -488,11 +548,6 @@ export declare class LocalSessionStore implements SessionStore {
488
548
  * 整段在这只会话的写锁里(与删除 / 换装 / 登记同一把)。
489
549
  */
490
550
  release(sessionId: string): Promise<void>;
491
- /**
492
- * 委派继承律的认领口(锁内 CAS):placed 行、那一格的化身 = `incarnation`、属主无主(记下的 `null` 或从没被记下)⇒ 写 `owner`、返回 true;任何一条不符 ⇒ false、零写
493
- * (SQL 孪生的 `CLAIM_OWNERLESS_PLACED_SQL` 同四个条件)。placement 读不出 ⇒ 上抛(不写)。
494
- */
495
- private claimOwnerlessPlaced;
496
551
  /**
497
552
  * P0.5 variant-2 (E16, clay 2026-06-26) — enumerate the caller's sessions (CC /resume picker) from the SESSION
498
553
  * abstraction (`repo.list()`), keyset-paginated newest-first by last activity. Session IDENTITY comes from the
@@ -621,7 +676,7 @@ export declare class LocalSessionStore implements SessionStore {
621
676
  * ③ 内容,按写口的动词(唯一读法在下面这一只 switch;这里不读那一格、不判属主,只换**内容**;交给引擎仓的属主恒缺席 —— 规则一):
622
677
  * · `create` / `judged`(导入):原地换锁里读到的那一份存档的日志(没有存档 ⇒ 导入新建一只,匿名),以锁里读到的存档头为 `expect.row`(**行代号**:
623
678
  * 读与写之间存档换了一代 ⇒ 引擎仓拒 `conflict`;它判不出同一代里的追加)。匿名导入只换匿名存档的日志 —— 仍带属主戳的存档(更早的版本写下、一次性迁移 M33
624
- * 改写不了的,或跳版升上来的)本店先以具名拒 `StampedSessionArchiveError` 答(与 fork 同一只;引擎导入律同样会拒,本店不重写、不绕,只让拒带对的出路)。
679
+ * 改写不了的,或跳版升上来的)到不了这里:暂存提交在一切「谁赢」的判之前就以具名拒答了它({@link assertArchiveAnonymous});`create` 落在没有存档的名上,`replace` 先清存档。
625
680
  * 读存档、校验、存档判与写在引擎仓里是一步,抛在哪一处内容都一个字节没动 ⇒ 撤回 ②({@link withdrawImportCell}:行原样 —— 净零写,SQL 孪生的事务回滚同义)再原样上抛;
626
681
  * · `replace`(接口句「先清既有对话日志,再导入」):先清掉内容存档(不带 `expect` —— 带了就得先读它的头,读不出的存档就清不掉),再导入一只新存档。
627
682
  * 清不可撤:清过之后导入再抛(I/O 故障),内容就没了,行留 ② 落下的 mirror(只读方向,下次推送补齐)—— 已发版本的 replace 同形(删后导入)。
@@ -667,10 +722,26 @@ export declare class LocalSessionStore implements SessionStore {
667
722
  readStagedEntries(stagingId: string): Promise<SessionTreeEntry[]>;
668
723
  /** Drop a finished/aborted local staging handle from the registry (called by the handle on commit/abort). */
669
724
  forgetStaging(stagingId: string): void;
725
+ /**
726
+ * **带戳存档拒绝判的唯一一读**(fork 与改日志的推送问「这份存档的头仍记着属主戳吗」只此一处,也是 {@link StampedSessionArchiveError} 在本店的唯一铸点;只读、零写;
727
+ * 一次性迁移 M33 为改写存档而读头 / 整行是窗内的另一件事,窗关即删):这份存档的头仍记着属主戳
728
+ * (更早的版本导入 / 换装写下的,一次性迁移 M33 改写不了的,或跳版升上来的)⇒ 抛那只具名拒。引擎的 fork 会把戳抄给子存档、引擎导入律不让匿名导入替换带戳存档,
729
+ * 本店不重写、不绕 —— 只让拒带对的出路(删掉会话后重推)。读的是存档的**形**(「这不是本店的存档形」),不是属主事实(属主读行那一格);读口是 {@link archiveHeadOf}
730
+ * (core `SessionRepo.headOf` 契约:与 `open()` 读到的头同一读)。没有存档的 id ⇒ 放行(没有存档就没有戳)。
731
+ * 三处问它,问的时刻不同、答案同一个:
732
+ * · {@link forkTo} 的源 —— 在写目标格之前(持源与目标两把写锁);
733
+ * · {@link commitStagedImport} —— 这一提交要换日志,排在一切「谁赢」的判之前(持这只会话的写锁):换不了日志的存档上谈不上同步冲突或载荷分叉,
734
+ * 那两条拒的出路(带同意重来)都是死路,唯一的出路在这一句里;
735
+ * · 会话同步导入的**第一阶段**(`http/routes/session-sync.ts`,经 `OwnerAwareSessionStore.assertArchiveAnonymous` 这一缝,不持锁):按 id 分类出 fork / stale 又没带同意时,
736
+ * 通用冲突指的出路同样是「带同意重来」⇒ 先问这一口,不等第二轮。不持锁的一问是早拒(此刻的答案),权威的一问是提交里持锁的那一问 —— 同一个事实的两次读。
737
+ * 只有 local 店有带戳存档这一形;SQL 孪生没有 ⇒ 不交这一口(调用方按「没有带戳存档」处置)。
738
+ */
739
+ assertArchiveAnonymous(sessionId: string): Promise<void>;
670
740
  /**
671
741
  * {@link LocalStagingHandle.commit} 的整段 —— **一次持锁**(这只会话的写锁,{@link withSessionWriteLock}):属主 CAS(此刻的属主格 = 这次导入的门判过的那一格,
672
- * 否则抛 {@link StagedCommitOwnerChangedError},一个字节不写;在判目的端日志之前判,SQL 孪生同序)→ 提交时重新分类(目的端此刻的日志 vs staged)
673
- * → 拒未经同意的 fork / stale / 载荷分叉 → 本处写者拒 → 换装(导入写序 ①②③,落点 `judged` = 上面这一判;化身与 fork 键按换装律沿用)或 identical 且内容相同时日志不动、
742
+ * 否则抛 {@link StagedCommitOwnerChangedError},一个字节不写;在判目的端日志之前判,SQL 孪生同序)→ 分区判 → 提交时重新分类(目的端此刻的日志 vs staged)→
743
+ * 这一提交要换日志(不是「id 与内容都相同」那一支)⇒ 带戳存档判({@link assertArchiveAnonymous};只有 local 店有带戳存档,SQL 孪生没有这一步)→
744
+ * 拒未经同意的 fork / stale / 载荷分叉(带戳判在它们之前:带戳存档上日志换不了,同步冲突与载荷分叉的出路 —— 带同意重来 —— 无从谈起)→ 本处写者拒 → 换装(导入写序 ①②③,落点 `judged` = 上面这一判;化身与 fork 键按换装律沿用)或 identical 且内容相同时日志不动、
674
745
  * 行照样落(② 那一格:重戳属主 + mirror)→ 两支同记新近(推送即活动)→ **在同一次持锁里**
675
746
  * 读出落定的会话面交回。锁放下之前谁也换不掉这一位住客,所以交回的面就是这次提交落下的那一位(SQL 孪生在提交事务里读同一行,同义);
676
747
  * 提交之后按住客条件写的重放(会话行两位)只认这只面,不再重读。改属主的每一条写腿(登记 / 删除 / 导入 / 换装)都在同一把锁里,所以锁内这一读到锁放下都成立。