@sema-agent/server 7.103.2 → 7.105.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 (204) hide show
  1. package/MIGRATION.md +378 -2
  2. package/README.md +6 -1
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +320 -16
  5. package/deploy/sema-up/README.md +57 -7
  6. package/deploy/sema-up/cluster-up.sh +18 -4
  7. package/deploy/sema-up/kube-up.sh +16 -0
  8. package/deploy/sema-up/lib/dataroot.sh +142 -0
  9. package/deploy/sema-up/lib/flagshape.sh +69 -0
  10. package/deploy/sema-up/lib/lanes.py +14 -4
  11. package/deploy/sema-up/lib/shell-access.sh +41 -0
  12. package/deploy/sema-up/preflight.sh +14 -1
  13. package/deploy/sema-up/sema-up.sh +134 -37
  14. package/deploy/sema-up/smoke.sh +5 -0
  15. package/dist/auto-mode-face.js +3 -4
  16. package/dist/boot/budget-tracing.js +1 -1
  17. package/dist/boot/config-center.d.ts +6 -2
  18. package/dist/boot/config-center.js +5 -59
  19. package/dist/boot/execution-env.d.ts +11 -0
  20. package/dist/boot/execution-env.js +14 -0
  21. package/dist/boot/execution-lane.d.ts +25 -0
  22. package/dist/boot/execution-lane.js +95 -0
  23. package/dist/boot/memory-consolidation.d.ts +185 -9
  24. package/dist/boot/memory-consolidation.js +192 -32
  25. package/dist/boot/resolve-spec.d.ts +2 -0
  26. package/dist/boot/resolve-spec.js +28 -24
  27. package/dist/boot/runner-deps.d.ts +6 -2
  28. package/dist/boot/runner-deps.js +6 -1
  29. package/dist/boot/session-faces.d.ts +5 -0
  30. package/dist/boot/session-faces.js +18 -2
  31. package/dist/boot/session-incarnation-seat.d.ts +41 -0
  32. package/dist/boot/session-incarnation-seat.js +69 -0
  33. package/dist/boot/stage-01-config.js +2 -2
  34. package/dist/boot/stage-02-registry-pull.js +3 -1
  35. package/dist/boot/stage-04-stores.d.ts +2 -0
  36. package/dist/boot/stage-04-stores.js +3 -1
  37. package/dist/boot/stage-05-execution-env.d.ts +4 -0
  38. package/dist/boot/stage-05-execution-env.js +2 -2
  39. package/dist/boot/stage-06-runners.d.ts +4 -1
  40. package/dist/boot/stage-06-runners.js +7 -14
  41. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  42. package/dist/boot/stage-08-reapers.d.ts +4 -1
  43. package/dist/boot/stage-09-leader.d.ts +6 -1
  44. package/dist/boot/stage-09-leader.js +2 -1
  45. package/dist/boot/stage-10-http-server.d.ts +6 -1
  46. package/dist/boot/stage-10-http-server.js +4 -1
  47. package/dist/brain.d.ts +1 -1
  48. package/dist/config-catalog.d.ts +16 -1
  49. package/dist/config-catalog.js +9 -9
  50. package/dist/config-center/restart-signal.d.ts +4 -4
  51. package/dist/config-invariants.d.ts +7 -1
  52. package/dist/config-invariants.js +37 -6
  53. package/dist/config-provider.d.ts +42 -10
  54. package/dist/config-provider.js +18 -16
  55. package/dist/config-types.d.ts +37 -16
  56. package/dist/config-types.js +1 -0
  57. package/dist/config.d.ts +28 -30
  58. package/dist/config.js +95 -77
  59. package/dist/env-facts.d.ts +13 -3
  60. package/dist/env-facts.js +15 -3
  61. package/dist/execution-lane-caps.d.ts +15 -0
  62. package/dist/execution-lane-caps.js +8 -0
  63. package/dist/file-history-capture.d.ts +42 -35
  64. package/dist/file-history-capture.js +15 -10
  65. package/dist/file-history-disclosure.d.ts +7 -31
  66. package/dist/file-history-disclosure.js +12 -0
  67. package/dist/fleet/fleet-bus.d.ts +5 -1
  68. package/dist/http/admission.d.ts +12 -5
  69. package/dist/http/admission.js +5 -1
  70. package/dist/http/dispatch.d.ts +18 -0
  71. package/dist/http/dispatch.js +19 -6
  72. package/dist/http/principal-gate.d.ts +22 -0
  73. package/dist/http/principal-gate.js +20 -0
  74. package/dist/http/resume-legs.d.ts +1 -1
  75. package/dist/http/resume-legs.js +9 -5
  76. package/dist/http/route-table.d.ts +15 -4
  77. package/dist/http/route-table.js +6 -5
  78. package/dist/http/routes/agents-roster.d.ts +7 -0
  79. package/dist/http/routes/agents-roster.js +6 -2
  80. package/dist/http/routes/approvals-assistant.d.ts +48 -19
  81. package/dist/http/routes/approvals-assistant.js +117 -50
  82. package/dist/http/routes/capabilities.js +3 -2
  83. package/dist/http/routes/config-catalog.js +1 -0
  84. package/dist/http/routes/fleet.d.ts +3 -2
  85. package/dist/http/routes/images.js +0 -1
  86. package/dist/http/routes/memory-consolidation.d.ts +3 -2
  87. package/dist/http/routes/memory-policy.js +3 -3
  88. package/dist/http/routes/rules.js +2 -2
  89. package/dist/http/routes/runs.js +29 -10
  90. package/dist/http/routes/session-sync.js +14 -15
  91. package/dist/http/routes/sessions-list.js +3 -2
  92. package/dist/http/routes/sessions.js +49 -25
  93. package/dist/http/routes/shared-memory.js +2 -2
  94. package/dist/http/routes/side-query.js +8 -1
  95. package/dist/http/routes/tasks.js +22 -19
  96. package/dist/http/routes/trace-usage.d.ts +2 -0
  97. package/dist/http/routes/trace-usage.js +8 -13
  98. package/dist/http/send.d.ts +12 -0
  99. package/dist/http/send.js +12 -1
  100. package/dist/http/server.d.ts +30 -8
  101. package/dist/http/server.js +11 -5
  102. package/dist/http/sse-lifecycle.d.ts +1 -1
  103. package/dist/http/sse-log.d.ts +12 -9
  104. package/dist/http/sse-log.js +1 -8
  105. package/dist/http/sync-submit-body.d.ts +43 -0
  106. package/dist/http/sync-submit-body.js +4 -0
  107. package/dist/http/wire-types.d.ts +23 -5
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.js +1 -0
  110. package/dist/leader/merge.d.ts +8 -3
  111. package/dist/memory-incident-sink.js +2 -1
  112. package/dist/model-select.d.ts +9 -0
  113. package/dist/model-select.js +5 -1
  114. package/dist/observability/corrupt-read-seat.d.ts +151 -16
  115. package/dist/observability/corrupt-read-seat.js +158 -15
  116. package/dist/observability/fail-open.d.ts +17 -1
  117. package/dist/observability/fail-open.js +17 -1
  118. package/dist/observability/metrics.js +1 -1
  119. package/dist/observability/run-terminal.d.ts +60 -10
  120. package/dist/observability/run-terminal.js +119 -9
  121. package/dist/parked-decide.d.ts +8 -3
  122. package/dist/parked-decide.js +56 -20
  123. package/dist/parked-revive-surface.d.ts +14 -7
  124. package/dist/parked-revive-surface.js +5 -1
  125. package/dist/plugins/approval-ask-store-file.d.ts +4 -1
  126. package/dist/plugins/approval-ask-store-file.js +9 -12
  127. package/dist/plugins/approval-ask-store-sql.d.ts +4 -0
  128. package/dist/plugins/approval-ask-store-sql.js +4 -1
  129. package/dist/plugins/approval-exemption-store.d.ts +21 -1
  130. package/dist/plugins/approval-exemption-store.js +36 -7
  131. package/dist/plugins/approval-nonce-store.d.ts +16 -2
  132. package/dist/plugins/approval-nonce-store.js +17 -7
  133. package/dist/plugins/caching-session-store.d.ts +5 -0
  134. package/dist/plugins/caching-session-store.js +3 -0
  135. package/dist/plugins/file-resume-anchor-store.d.ts +7 -2
  136. package/dist/plugins/file-resume-anchor-store.js +19 -5
  137. package/dist/plugins/file-run-store.d.ts +21 -8
  138. package/dist/plugins/file-run-store.js +60 -38
  139. package/dist/plugins/image-bake-store-sql.d.ts +0 -2
  140. package/dist/plugins/image-bake-store-sql.js +0 -5
  141. package/dist/plugins/local-session-store.d.ts +22 -0
  142. package/dist/plugins/local-session-store.js +74 -11
  143. package/dist/plugins/memory-run-store.d.ts +7 -5
  144. package/dist/plugins/memory-run-store.js +11 -4
  145. package/dist/plugins/one-time-migrations.d.ts +38 -1
  146. package/dist/plugins/one-time-migrations.js +143 -15
  147. package/dist/plugins/permission-rule-store-file.d.ts +9 -2
  148. package/dist/plugins/permission-rule-store-file.js +37 -51
  149. package/dist/plugins/pg-session-storage.d.ts +3 -0
  150. package/dist/plugins/pg-session-storage.js +19 -10
  151. package/dist/plugins/retention-store-sql.js +1 -0
  152. package/dist/plugins/run-store-sql.d.ts +15 -6
  153. package/dist/plugins/run-store-sql.js +15 -13
  154. package/dist/plugins/schema-indexes.js +1 -0
  155. package/dist/plugins/send-file-ledger.d.ts +5 -1
  156. package/dist/plugins/send-file-ledger.js +11 -6
  157. package/dist/plugins/session-event-payload.d.ts +14 -4
  158. package/dist/plugins/session-event-payload.js +5 -2
  159. package/dist/plugins/session-policy-store-sql.js +18 -5
  160. package/dist/plugins/store-backend.d.ts +13 -7
  161. package/dist/plugins/store-backend.js +10 -6
  162. package/dist/plugins/store-contracts.d.ts +7 -2
  163. package/dist/plugins/tidb-pool.js +12 -5
  164. package/dist/plugins/tidb-session-store.d.ts +6 -1
  165. package/dist/plugins/tidb-session-store.js +17 -10
  166. package/dist/project-identity.d.ts +4 -6
  167. package/dist/read-face-posture.d.ts +21 -0
  168. package/dist/read-face-posture.js +9 -0
  169. package/dist/request-key-closure.js +1 -1
  170. package/dist/request-value-closure.d.ts +72 -3
  171. package/dist/request-value-closure.js +12 -4
  172. package/dist/roster-bound-http-status.d.ts +6 -0
  173. package/dist/roster-bound-http-status.js +6 -0
  174. package/dist/rules-consent.js +0 -11
  175. package/dist/run-local.js +22 -27
  176. package/dist/runs.d.ts +22 -2
  177. package/dist/runs.js +29 -7
  178. package/dist/sealed-key.d.ts +31 -4
  179. package/dist/sealed-key.js +9 -0
  180. package/dist/security.d.ts +5 -1
  181. package/dist/session-children-retire.d.ts +92 -0
  182. package/dist/session-children-retire.js +85 -0
  183. package/dist/session-error-wire.d.ts +58 -0
  184. package/dist/session-error-wire.js +38 -0
  185. package/dist/session-incarnation.d.ts +61 -0
  186. package/dist/session-incarnation.js +43 -0
  187. package/dist/session-policy-wire.d.ts +98 -12
  188. package/dist/session-policy-wire.js +71 -8
  189. package/dist/session-records-skipped-notice.d.ts +20 -0
  190. package/dist/session-records-skipped-notice.js +27 -0
  191. package/dist/tool-approval.d.ts +65 -0
  192. package/dist/tool-approval.js +36 -9
  193. package/dist/trace/core-keyset-guard.d.ts +8 -7
  194. package/dist/trace/engine-notice-wire.d.ts +3 -1
  195. package/dist/trace/engine-notice-wire.js +2 -0
  196. package/dist/trace/project.d.ts +11 -6
  197. package/dist/trace/project.js +12 -3
  198. package/dist/trace/redact.d.ts +7 -0
  199. package/dist/trace/redact.js +153 -61
  200. package/dist/trace/wire-projection-faces.d.ts +10 -3
  201. package/dist/trace/wire-projection-faces.js +8 -2
  202. package/package.json +2 -2
  203. package/dist/plugins/memory-session-policy-store.d.ts +0 -46
  204. package/dist/plugins/memory-session-policy-store.js +0 -52
@@ -55,10 +55,12 @@ export declare class MemoryRunStore {
55
55
  /** [ref] FIRST terminal writer wins(SQL 孪生同款正向 CAS on running/suspended/needs_review,
56
56
  * [1.207 codex M1] 负向形漏 blocked/timeout);claim 释放保持无条件(幂等)。 */
57
57
  setTerminal(taskId: string, status: TaskStatus, result: PersistedTaskResult | null, error: string | null): Promise<void>;
58
- /** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard). */
59
- setSuspended(taskId: string): Promise<void>;
60
- /** [ref] D-B: park NON-terminal `needs_review`, KEEP the claim. CAS on running/needs_review. */
61
- setNeedsReview(taskId: string): Promise<void>;
58
+ /** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard).
59
+ * S-173:同时落 `result` —— 封口脱敏后的投影(`sealRunRowResult`,凭据不落;`null` = 没观察到 park 结果),SQL 孪生同注。 */
60
+ setSuspended(taskId: string, result: PersistedTaskResult | null): Promise<void>;
61
+ /** [ref] D-B: park NON-terminal `needs_review`, KEEP the claim. CAS on running/needs_review.
62
+ * S-173:同 {@link setSuspended} 落封口脱敏后的 `result`。 */
63
+ setNeedsReview(taskId: string, result: PersistedTaskResult | null): Promise<void>;
62
64
  /** Durable F4 resume: flip suspended/needs_review → running (CAS) + reset cancel/preempt flags.
63
65
  * [ref](codex R1 [medium]):认领 = 接管 `instanceId`(= 当前持有副本;SQL 孪生同注)。CAS 输不改。 */
64
66
  markResuming(taskId: string, instanceId: string): Promise<boolean>;
@@ -124,7 +126,7 @@ export declare class MemoryRunStore {
124
126
  /** Fail stale `running` rows (crashed-run backstop) + release task_active for any now-terminal row. */
125
127
  reapStale(olderThanMs: number): Promise<number>;
126
128
  /** [ref] shared tail of both suspended-run reapers: flip one parked row → failed('approval.expired'), the
127
- * in-memory twin of the SQL UPDATE (`error`/`error_code` set directly — no result blob to derive from). */
129
+ * in-memory twin of the SQL UPDATE (`error`/`error_code` set directly — no result blob to derive from; S-173: the park-time `result` is cleared, same as the SQL `result = NULL`). */
128
130
  private failExpiredPark;
129
131
  /** Release the claim for TERMINAL rows only — suspended/needs_review keep it (the `DELETE ta … WHERE tr.status
130
132
  * NOT IN ('running','suspended','needs_review')` twin, shared by reapStale + the [ref] suspended reapers). */
@@ -1,4 +1,4 @@
1
- import { redactErrorMessage, redactLedgerEventData, redactTerminalResult } from "../observability/run-terminal.js";
1
+ import { redactErrorMessage, redactLedgerEventData, sealRunRowResult } from "../observability/run-terminal.js";
2
2
  import { outwardPlane } from "../terminal.js";
3
3
  import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
4
4
  import { notifyRunTerminal } from "../observability/run-terminal.js";
@@ -105,7 +105,7 @@ export class MemoryRunStore {
105
105
  .map((e) => ({ ...e, data: e.data == null ? null : structuredClone(e.data) }));
106
106
  }
107
107
  async setTerminal(taskId, status, result, error) {
108
- result = redactTerminalResult(result);
108
+ result = sealRunRowResult(result);
109
109
  error = error === null ? null : (redactErrorMessage(error) ?? null);
110
110
  const r = this.runs.get(taskId);
111
111
  if (!r) {
@@ -140,17 +140,21 @@ export class MemoryRunStore {
140
140
  if (pending)
141
141
  notifyRunTerminal(pending);
142
142
  }
143
- async setSuspended(taskId) {
143
+ async setSuspended(taskId, result) {
144
+ const sealed = sealRunRowResult(result);
144
145
  const r = this.runs.get(taskId);
145
146
  if (r && (r.status === "running" || r.status === "suspended")) {
146
147
  r.status = "suspended";
148
+ r.result = sealed;
147
149
  r.updatedAt = new Date();
148
150
  }
149
151
  }
150
- async setNeedsReview(taskId) {
152
+ async setNeedsReview(taskId, result) {
153
+ const sealed = sealRunRowResult(result);
151
154
  const r = this.runs.get(taskId);
152
155
  if (r && (r.status === "running" || r.status === "needs_review")) {
153
156
  r.status = "needs_review";
157
+ r.result = sealed;
154
158
  r.updatedAt = new Date();
155
159
  }
156
160
  }
@@ -159,6 +163,7 @@ export class MemoryRunStore {
159
163
  if (!r || (r.status !== "suspended" && r.status !== "needs_review"))
160
164
  return false;
161
165
  r.status = "running";
166
+ r.result = null;
162
167
  r.instanceId = instanceId;
163
168
  r.cancelRequested = false;
164
169
  r.preemptRequested = false;
@@ -292,6 +297,7 @@ export class MemoryRunStore {
292
297
  for (const r of this.runs.values()) {
293
298
  if (r.status === "running" && r.updatedAt.getTime() < cutoff) {
294
299
  r.status = "failed";
300
+ r.result = null;
295
301
  r.error = "run stalled (instance lost?)";
296
302
  r.updatedAt = new Date();
297
303
  reaped += 1;
@@ -302,6 +308,7 @@ export class MemoryRunStore {
302
308
  }
303
309
  failExpiredPark(r) {
304
310
  r.status = "failed";
311
+ r.result = null;
305
312
  r.error = "approval expired before decision";
306
313
  r.errorCode = "approval.expired";
307
314
  r.updatedAt = new Date();
@@ -16,8 +16,45 @@ export interface OneTimeMigrationReport {
16
16
  /** M4:`device_audit.detail` 里 JSON 文本 `null` 被改成列 NULL 的行数(表不在 ⇒ 恒 0)。
17
17
  * 非零 = 这次迁移真改写了存量字节,必须让运维看见(与 M1 的 `retiredSnapshotRows` 同一条纪律)。 */
18
18
  readonly auditDetailJsonNullRows: number;
19
+ /** M5:`session_meta.incarnation` 列 —— `added` = 本次 `ADD COLUMN`;`present` = 早已在(新库 / 第二跑);
20
+ * `no-table` = 这只库上没有 `session_meta`(中央 DDL 之后不可达,只为不编一个读数);`lazy-on-read` = local 文件车道
21
+ * (没有这张表,存量会话首读补铸)。 */
22
+ readonly sessionIncarnationColumn: "added" | "present" | "no-table" | "lazy-on-read";
23
+ /** M5:本次给空化身铸了令牌的行数。非零 = 这次迁移真写了存量行,必须让运维看见(与 M4 同一条纪律)。 */
24
+ readonly sessionIncarnationsBackfilled: number;
19
25
  }
20
- /** 三条迁移都不适用的载体(local 车道没有 SQL 表)。 */
26
+ /**
27
+ * S-590 —— 一条一次性迁移的窗(登记表的一行;纯数据)。字段注写着每个值的取证方式,改一行之前照着重核。
28
+ * 规则(窗长 30 天、窗关后的第一个 minor 删码、门怎么判)在本文件头注「窗」一节,只写那一次。
29
+ */
30
+ export interface OneTimeMigrationWindow {
31
+ /** 本文件里的条目名(头注「四条迁移」的 M1…M4)。 */
32
+ readonly id: string;
33
+ /** 引入这条迁移的版本 = 第一只含其代码的发布 tag(取证:`git log -S '<该条的常量名>' -- src/plugins/one-time-migrations.ts`
34
+ * 找引入提交 → `git tag --contains <提交> --sort=creatordate` 取最早)。 */
35
+ readonly introducedIn: string;
36
+ /** 该版本在 npm 上的发布日,UTC 日历日 YYYY-MM-DD(取证:`npm view @sema-agent/server time`)。`null` = **未发布形**:引入它的
37
+ * 那一版还没发布,由抬 `package.json` 到那一版的发布准备提交回填(规则见文件头「窗」一节;键必须显式写,缺席 ⇒ 到期门红)。 */
38
+ readonly releasedOn: string | null;
39
+ /** = `releasedOn` + 30 天(UTC 日历日;到期门逐条核这条等式)。今天晚于它 ⇒ 到期门红,删码排进其后第一个 minor。
40
+ * 与 `releasedOn` 同为 `null`(未发布形,不计到期)或同为日期。 */
41
+ readonly removableAfter: string | null;
42
+ /** 删码时删什么(到期门的红句逐字引用)。 */
43
+ readonly removal: string;
44
+ /** 删码之后,存量库若还带着迁移前的形,由哪一条**既有**响亮臂接住;没有就如实写没有(不为它新造兼容读)。 */
45
+ readonly afterRemoval: string;
46
+ /** 本条在 {@link OneTimeMigrationReport} 里的读数键 —— 报告的每个键恰属一条(到期门核:新迁移不登记 ⇒ 红)。 */
47
+ readonly reportKeys: readonly (keyof OneTimeMigrationReport)[];
48
+ }
49
+ /**
50
+ * 🔴 S-590 —— 一次性迁移登记表,本文件各条的窗的**唯一属主**。别处(接口 JSDoc、调用点、DDL 注、部署文档)只写「窗口见
51
+ * `plugins/one-time-migrations.ts` 登记表」;起动日志行 `one_time_migrations_applied` 的 `removableAfter` 读数由本表派生。
52
+ *
53
+ * M1–M3 同一次删:M3 的「本版在这只库上跑过了」标记就是 M1 / M2 的退役 schema 在不在(头注「幂等」一节),先删 M1 / M2
54
+ * 而留着 M3 会让 M3 永远判「跑过了」。三条同版引入、同一天到期,本来就同一次删。
55
+ */
56
+ export declare const ONE_TIME_MIGRATIONS: readonly OneTimeMigrationWindow[];
57
+ /** local 车道上 SQL 独有的四条(M1 / M2 / M4 / M5)的读数:那几张表 / 列在文件底座上从来不存在 ⇒ 如实答 absent / 0。 */
21
58
  export declare const NO_SQL_MIGRATIONS: Omit<OneTimeMigrationReport, "askOriginRowsMigrated">;
22
59
  /** 启动期的日志口(logger 的子集)。两级各有其用,见 {@link logOneTimeMigrations}。 */
23
60
  export interface MigrationLogSink {
@@ -1,12 +1,76 @@
1
1
  import { acquireEnsureSchemaLock, releaseEnsureSchemaLock } from "./tidb-pool.js";
2
2
  import { acquirePgEnsureSchemaLock, releasePgEnsureSchemaLock } from "./pg-pool.js";
3
3
  import { jsonColumnTextExpr } from "./sql-json-column.js";
4
+ import { SESSION_META_INCARNATION_COLUMN, mintSessionIncarnation } from "../session-incarnation.js";
5
+ export const ONE_TIME_MIGRATIONS = [
6
+ {
7
+ id: "M1",
8
+ introducedIn: "7.74.0",
9
+ releasedOn: "2026-09-13",
10
+ removableAfter: "2026-10-13",
11
+ removal: "删 migrateOn 的 M1 步(`snapshot_manifest` 在场探针、删前计数、`DROP TABLE`)与 `RETIRED_SNAPSHOT_TABLE`;报告、日志行与 " +
12
+ "`NO_SQL_MIGRATIONS` 的 `retiredSnapshotTable` / `retiredSnapshotRows`;与 M2 / M3 同一次删(见本表头注)",
13
+ afterRemoval: "没有响亮臂:存量库里的 `snapshot_manifest` 成一张无人读写的孤儿表 —— 它的读腿(边界探针)已随 core 7.17.0 的 seam 退役删掉," +
14
+ "起服列册探针 `schema-consistency.ts` 只对账代码认识的表。只有从 7.73.x 及更早直接跳到删码版的库会遇到;不影响任何读腿," +
15
+ "运维可手工 `DROP TABLE snapshot_manifest`",
16
+ reportKeys: ["retiredSnapshotTable", "retiredSnapshotRows"],
17
+ },
18
+ {
19
+ id: "M2",
20
+ introducedIn: "7.74.0",
21
+ releasedOn: "2026-09-13",
22
+ removableAfter: "2026-10-13",
23
+ removal: "删 migrateOn 的 M2 拒启臂与 `DROP COLUMN` 步、`RETIRED_STEER_SEAT_COLUMN`、`DELIVERABLE_STEER_SEAT_WHERE`、`RetiredSteerSeatError`;" +
24
+ "报告、日志行与 `NO_SQL_MIGRATIONS` 的 `retiredSteerSeatColumn` / `retiredSteerSeatStaleRows`;与 M1 / M3 同一次删",
25
+ afterRemoval: "起服列册探针 `schema-consistency.ts`(S-547)的多列臂:存量库的 `checkpoint.pending_steer` 是代码不认识的列 ⇒ 拒启 " +
26
+ "`config.schema_mismatch`(响亮;⚠️ 那一臂的句子说「库比代码新」,对这一形方向说反了)。运维照拒句手工删列时,列上还送得出去的 " +
27
+ "steering 指令随之丢失 —— 删码之后没有任何东西再替它数一遍送得出去的格",
28
+ reportKeys: ["retiredSteerSeatColumn", "retiredSteerSeatStaleRows"],
29
+ },
30
+ {
31
+ id: "M3",
32
+ introducedIn: "7.74.0",
33
+ releasedOn: "2026-09-13",
34
+ removableAfter: "2026-10-13",
35
+ removal: "删 migrateOn 的 M3 两条整表 `UPDATE` 与它的「退役 schema 还在」跳过标记、`RETIRED_ASK_ORIGIN`、`GATE_BEARING_LEDGER_EVENT_TYPE`、" +
36
+ "`migrateRetiredAskOrigin`,以及 local 腿(`FileRunStore.migrateRetiredAskOriginRows` 与 `LocalBackend.runOneTimeMigrations` 里那一格);" +
37
+ "报告与日志行的 `askOriginRowsMigrated`;与 M1 / M2 同一次删",
38
+ afterRemoval: "读腿既有的 `server.trace.gate-outcome-defective`(`trace/project.ts` 的 `screenedGate`):残留旧拼法的 gate 与任何集外词同判 —— " +
39
+ "整条 withhold + 计数 + 一次 warn(与头注「M3 够不到的一格」同一条臂)",
40
+ reportKeys: ["askOriginRowsMigrated"],
41
+ },
42
+ {
43
+ id: "M4",
44
+ introducedIn: "7.93.5",
45
+ releasedOn: "2026-09-21",
46
+ removableAfter: "2026-10-21",
47
+ removal: "删 migrateOn 的 M4 步(`device_audit` 在场探针 + `UPDATE … SET detail = NULL`)与 `AUDIT_TABLE` / `AUDIT_DETAIL_COLUMN`;" +
48
+ "报告、日志行与 `NO_SQL_MIGRATIONS` 的 `auditDetailJsonNullRows`;它若是表里最后一条,按头注「窗」一节整个文件一起删",
49
+ afterRemoval: "`device-store-sql.ts` `listAudit` 的读形拒(S-550:一行读不出形 ⇒ 整页拒,刻意不逐行隔离):含一行 JSON 文本 `null` 的那一页" +
50
+ "读不出来,拒句点名行 id —— 响亮,但那一页会一直读不出来,直到运维手工把那些行的 `detail` 置成 SQL NULL",
51
+ reportKeys: ["auditDetailJsonNullRows"],
52
+ },
53
+ {
54
+ id: "M5",
55
+ introducedIn: "7.104.0",
56
+ releasedOn: "2026-09-28",
57
+ removableAfter: "2026-10-28",
58
+ removal: "删 migrateOn 的 M5 步(`session_meta` 在场探针、`incarnation` 列在场探针 + `ADD COLUMN`、空化身回填循环)与 `backfillSessionIncarnations`;" +
59
+ "报告、日志行与 `NO_SQL_MIGRATIONS` 的 `sessionIncarnationColumn` / `sessionIncarnationsBackfilled`;**列本身留在中央 DDL 里**(它不是迁移的一部分)",
60
+ afterRemoval: "① 列不在的存量库:起服列册探针 `schema-consistency.ts`(S-547)的缺列臂拒启 `config.schema_mismatch`,拒句里的 `ALTER TABLE … ADD COLUMN` " +
61
+ "由同一份 DDL 生成(响亮);② 列在但行上仍是空化身(跳过了带 M5 的版本直接升):会话店读口 `sessionFaceOf` 对那一行响亮拒 " +
62
+ "`session.incarnation_missing`(parked 决定腿因此拒、记忆座退回按 id 绑定并 warn)—— 运维手工给那些行填一只随机令牌即可",
63
+ reportKeys: ["sessionIncarnationColumn", "sessionIncarnationsBackfilled"],
64
+ },
65
+ ];
4
66
  export const NO_SQL_MIGRATIONS = {
5
67
  retiredSnapshotTable: "absent",
6
68
  retiredSnapshotRows: 0,
7
69
  retiredSteerSeatStaleRows: 0,
8
70
  retiredSteerSeatColumn: "absent",
9
71
  auditDetailJsonNullRows: 0,
72
+ sessionIncarnationColumn: "lazy-on-read",
73
+ sessionIncarnationsBackfilled: 0,
10
74
  };
11
75
  const RETIRED_SNAPSHOT_TABLE = "snapshot_manifest";
12
76
  const RETIRED_STEER_SEAT_COLUMN = "pending_steer";
@@ -37,24 +101,24 @@ export class RetiredSteerSeatError extends Error {
37
101
  }
38
102
  function mysqlDb(conn) {
39
103
  return {
40
- async rows(sql) {
41
- const [rows] = await conn.query(sql);
104
+ async rows(sql, params) {
105
+ const [rows] = params === undefined ? await conn.query(sql) : await conn.query(sql, [...params]);
42
106
  return rows;
43
107
  },
44
- async exec(sql) {
45
- const [r] = await conn.query(sql);
108
+ async exec(sql, params) {
109
+ const [r] = params === undefined ? await conn.query(sql) : await conn.query(sql, [...params]);
46
110
  return Number(r.affectedRows ?? 0);
47
111
  },
48
112
  };
49
113
  }
50
114
  function pgDb(client) {
51
115
  return {
52
- async rows(sql) {
53
- const r = await client.query(sql);
116
+ async rows(sql, params) {
117
+ const r = params === undefined ? await client.query(sql) : await client.query(sql, [...params]);
54
118
  return r.rows;
55
119
  },
56
- async exec(sql) {
57
- const r = await client.query(sql);
120
+ async exec(sql, params) {
121
+ const r = params === undefined ? await client.query(sql) : await client.query(sql, [...params]);
58
122
  return r.rowCount ?? 0;
59
123
  },
60
124
  };
@@ -139,6 +203,7 @@ async function migrateOn(db, dialect) {
139
203
  ? 0
140
204
  : await db.exec(`UPDATE ${AUDIT_TABLE} SET ${AUDIT_DETAIL_COLUMN} = NULL ` +
141
205
  `WHERE ${AUDIT_DETAIL_COLUMN} IS NOT NULL AND ${jsonColumnTextExpr(dialect, AUDIT_DETAIL_COLUMN)} = 'null'`);
206
+ const { sessionIncarnationColumn, sessionIncarnationsBackfilled } = await migrateSessionIncarnations(db, dialect);
142
207
  let retiredSnapshotTable = "absent";
143
208
  let retiredSnapshotRows = 0;
144
209
  let retiredSteerSeatColumn = "absent";
@@ -169,7 +234,64 @@ async function migrateOn(db, dialect) {
169
234
  throw new Error(`one-time migration failed AFTER completing destructive steps — record these before retrying, a retry cannot ` +
170
235
  `report them again: ${destroyed.join("; ")}. Underlying failure: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
171
236
  }
172
- return { retiredSnapshotTable, retiredSnapshotRows, retiredSteerSeatColumn, retiredSteerSeatStaleRows, askOriginRowsMigrated, auditDetailJsonNullRows };
237
+ return {
238
+ retiredSnapshotTable, retiredSnapshotRows, retiredSteerSeatColumn, retiredSteerSeatStaleRows, askOriginRowsMigrated, auditDetailJsonNullRows,
239
+ sessionIncarnationColumn, sessionIncarnationsBackfilled,
240
+ };
241
+ }
242
+ const INCARNATION_BACKFILL_BATCH = 500;
243
+ async function migrateSessionIncarnations(db, dialect) {
244
+ if (!(await tableExists(db, dialect, "session_meta")))
245
+ return { sessionIncarnationColumn: "no-table", sessionIncarnationsBackfilled: 0 };
246
+ const col = SESSION_META_INCARNATION_COLUMN;
247
+ let sessionIncarnationColumn = "present";
248
+ if (!(await columnExists(db, dialect, "session_meta", col.name))) {
249
+ try {
250
+ await db.exec(`ALTER TABLE session_meta ADD COLUMN ${dialect === "tidb" ? col.tidb : col.pg}`);
251
+ sessionIncarnationColumn = "added";
252
+ }
253
+ catch (e) {
254
+ if (!(await columnExists(db, dialect, "session_meta", col.name)))
255
+ throw e;
256
+ }
257
+ }
258
+ return { sessionIncarnationColumn, sessionIncarnationsBackfilled: await backfillSessionIncarnations(db, dialect) };
259
+ }
260
+ async function backfillSessionIncarnations(db, dialect) {
261
+ const remaining = async () => Number((await db.rows("SELECT COUNT(*) AS n FROM session_meta WHERE incarnation = ''"))[0]?.n ?? 0);
262
+ const initial = await remaining();
263
+ if (initial === 0)
264
+ return 0;
265
+ const ceiling = Math.ceil(initial / INCARNATION_BACKFILL_BATCH) * 2 + 2;
266
+ let written = 0;
267
+ let after;
268
+ for (let round = 0; round < ceiling; round++) {
269
+ const ids = (after === undefined
270
+ ? await db.rows(`SELECT session_id FROM session_meta WHERE incarnation = '' ORDER BY session_id LIMIT ${INCARNATION_BACKFILL_BATCH}`)
271
+ : await db.rows(`SELECT session_id FROM session_meta WHERE incarnation = '' AND session_id > ${dialect === "pg" ? "$1" : "?"} ORDER BY session_id LIMIT ${INCARNATION_BACKFILL_BATCH}`, [after])).map((r) => String(r.session_id));
272
+ if (ids.length === 0) {
273
+ if (after === undefined)
274
+ return written;
275
+ after = undefined;
276
+ continue;
277
+ }
278
+ const params = [];
279
+ const bind = (v) => {
280
+ params.push(v);
281
+ return dialect === "pg" ? `$${params.length}` : "?";
282
+ };
283
+ const cases = ids.map((id) => `WHEN ${bind(id)} THEN ${bind(mintSessionIncarnation())}`).join(" ");
284
+ const inList = ids.map((id) => bind(id)).join(", ");
285
+ written += await db.exec(`UPDATE session_meta SET incarnation = CASE session_id ${cases} END WHERE incarnation = '' AND session_id IN (${inList})`, params);
286
+ after = ids.length < INCARNATION_BACKFILL_BATCH ? undefined : ids[ids.length - 1];
287
+ }
288
+ const left = await remaining();
289
+ if (left > 0) {
290
+ throw new Error(`one-time migration M5: ${left} session_meta row(s) still carry an empty incarnation after ${ceiling} backfill round(s) ` +
291
+ `(${written} written) — refusing to start rather than serve sessions whose occupant cannot be told apart. Retry the boot; ` +
292
+ `if it repeats, something keeps writing rows without an incarnation (an older replica still inserting?).`);
293
+ }
294
+ return written;
173
295
  }
174
296
  export function migrateRetiredAskOrigin(type, data) {
175
297
  if (type !== GATE_BEARING_LEDGER_EVENT_TYPE)
@@ -186,7 +308,8 @@ export function migrateRetiredAskOrigin(type, data) {
186
308
  return { ...d, gate: { ...g, origin: RETIRED_ASK_ORIGIN.to } };
187
309
  }
188
310
  export function logOneTimeMigrations(log, backend, report) {
189
- const changed = report.retiredSnapshotTable === "dropped" || report.retiredSteerSeatColumn === "dropped" || report.askOriginRowsMigrated > 0 || report.auditDetailJsonNullRows > 0;
311
+ const changed = report.retiredSnapshotTable === "dropped" || report.retiredSteerSeatColumn === "dropped" || report.askOriginRowsMigrated > 0 || report.auditDetailJsonNullRows > 0 ||
312
+ report.sessionIncarnationColumn === "added" || report.sessionIncarnationsBackfilled > 0;
190
313
  (changed ? log.warn : log.info).call(log, "one_time_migrations_applied", {
191
314
  backend,
192
315
  retiredSnapshotTable: report.retiredSnapshotTable,
@@ -195,11 +318,16 @@ export function logOneTimeMigrations(log, backend, report) {
195
318
  retiredSteerSeatStaleRows: report.retiredSteerSeatStaleRows,
196
319
  askOriginRowsMigrated: report.askOriginRowsMigrated,
197
320
  auditDetailJsonNullRows: report.auditDetailJsonNullRows,
198
- note: "one-time persisted-data migrations for this version's retirements (retired whole-tree snapshot index table " +
199
- "dropped; retired single-seat steering column dropped; retired ask-origin spelling rewritten in place; audit " +
200
- "details written as the JSON text `null` by an earlier version folded to a SQL NULL, so \"no detail\" has exactly " +
201
- "one on-disk spelling). This " +
202
- "whole migration step is removed in the next minor — start this version at least once before upgrading past it.",
321
+ sessionIncarnationColumn: report.sessionIncarnationColumn,
322
+ sessionIncarnationsBackfilled: report.sessionIncarnationsBackfilled,
323
+ removableAfter: Object.fromEntries(ONE_TIME_MIGRATIONS.map((w) => [w.id, w.removableAfter])),
324
+ note: "one-time persisted-data migrations (M1: retired whole-tree snapshot index table dropped; M2: retired single-seat " +
325
+ "steering column dropped; M3: retired ask-origin spelling rewritten in place; M4: audit details written as the JSON " +
326
+ "text `null` by an earlier version folded to a SQL NULL, so \"no detail\" has exactly one on-disk spelling; M5: the " +
327
+ "session incarnation column added and every session row without one minted its own token). Each step " +
328
+ "runs on every boot until its window closes (the release that introduced it + 30 days; the per-step date is in " +
329
+ "removableAfter, null while that release is not published yet) and is deleted in the first minor after that — start a " +
330
+ "version that still carries a step at least once before upgrading past its deletion.",
203
331
  });
204
332
  }
205
333
  //# sourceMappingURL=one-time-migrations.js.map
@@ -1,6 +1,7 @@
1
1
  import { FileDurableRulePartitionProvider, type RuleApprovalRecord, type RuleApprovalRecordStore, type StaleRuleApprovalRecord } from "@sema-agent/core";
2
2
  import { type RuleImportTicket, type RuleTicketPurpose, type RuleTicketReclaimResult, type RuleTicketRedeemResult, type RuleTicketSnapshot } from "./permission-rule-store-sql.js";
3
3
  import type { PermissionRuleStoreBundle } from "../rules-consent.js";
4
+ import type { Logger } from "../observability/logger.js";
4
5
  import { type RuleCandidate } from "@sema-agent/core";
5
6
  /**
6
7
  * `RuleApprovalRecordStore` 的 File 形。
@@ -11,8 +12,10 @@ import { type RuleCandidate } from "@sema-agent/core";
11
12
  export declare class FileRuleApprovalRecordStore implements RuleApprovalRecordStore {
12
13
  private readonly rows;
13
14
  private readonly log;
15
+ /** 本面的损坏闸(S-582 起由重放属主交回;置位后每个方法第一行响亮拒)。 */
14
16
  private readonly gate;
15
- constructor(dir: string, onError?: (message: string) => void);
17
+ /** @param warn 腐读披露座(S-582;缺席 ⇒ 属主退回 `console.warn`)。 */
18
+ constructor(dir: string, warn?: Logger["warn"]);
16
19
  /** 取证/自证用:本面是否因内部损坏而 fail-closed。 */
17
20
  get corrupt(): boolean;
18
21
  /** 归还本面 eager 持有的日志描述符(束的 `dispose()` 唯一调用点)。`AppendLog` 上没有 finalizer,
@@ -39,10 +42,12 @@ export declare class FileRuleApprovalRecordStore implements RuleApprovalRecordSt
39
42
  export declare class FileRuleImportTicketStore {
40
43
  private readonly rows;
41
44
  private readonly log;
45
+ /** 本面的损坏闸(S-582 起由重放属主交回)。 */
42
46
  private readonly gate;
43
47
  /** 单调钟(判定用)。与 {@link now}(墙钟,记账用)是两只不同的钟,别混。 */
44
48
  private readonly monotonic;
45
- constructor(dir: string, now?: () => number, onError?: (message: string) => void, monotonic?: () => number);
49
+ /** @param warn 腐读披露座(S-582;缺席 ⇒ 属主退回 `console.warn`)。 */
50
+ constructor(dir: string, now?: () => number, warn?: Logger["warn"], monotonic?: () => number);
46
51
  private readonly now;
47
52
  /** 取证/自证用:本面是否因内部损坏而 fail-closed。 */
48
53
  get corrupt(): boolean;
@@ -100,9 +105,11 @@ export interface FilePermissionRuleStores extends PermissionRuleStoreBundle {
100
105
  * `onError` 接的是 core 规则文件的**披露面**(读不出来 / 校验和不符 / 撞上符号链接时它答零规则并
101
106
  * 说出来)。接住它打一条 warn 是**必须**的:那条路径上「零规则」与「真的没有规则」在读面上同形,
102
107
  * 不留痕就变成一次静默的 fail-closed(用户会突然被反复询问,却没有任何线索)。
108
+ * `warn`(S-582)接的是本仓审批记录 / 导入票两份日志的**腐读座**(逐行披露 + 计数;缺席 ⇒ `console.warn`)。
103
109
  */
104
110
  export declare function createFilePermissionRuleStores(root: string, opts?: {
105
111
  onError?: (message: string) => void;
112
+ warn?: Logger["warn"];
106
113
  now?: () => number;
107
114
  monotonic?: () => number;
108
115
  }): FilePermissionRuleStores;
@@ -2,10 +2,10 @@ import { join } from "node:path";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { readdirSync } from "node:fs";
4
4
  import { performance } from "node:perf_hooks";
5
- import { FileDurableRulePartitionProvider, AppendLog, ensureDir, readJsonlRecords, } from "@sema-agent/core";
5
+ import { FileDurableRulePartitionProvider, AppendLog, ensureDir, } from "@sema-agent/core";
6
6
  import { z } from "zod";
7
7
  import { buildRulePayloadHash } from "./permission-rule-store-sql.js";
8
- import { createSkippedRecordSink } from "../observability/corrupt-read-seat.js";
8
+ import { replayJsonlGuarded } from "../observability/corrupt-read-seat.js";
9
9
  import { EDITED_RULE_BREADTH_WARNING_CODES, RULE_APPROVAL_RECORD_SCHEMA, UNCOVERED_SEGMENT_REASONS } from "../permission-rule-vocab.js";
10
10
  import { RULE_BEHAVIORS } from "@sema-agent/core";
11
11
  const RuleScopeSchema = z.union([
@@ -109,53 +109,44 @@ const TicketLineSchema = z.union([
109
109
  z.object({ op: z.literal("settle"), ticketId: z.string().min(1), outcome: z.unknown() }).strict(),
110
110
  ]);
111
111
  const LEGACY_TICKET_PURPOSE = "cc-import";
112
- class CorruptionGate {
113
- face;
114
- path;
115
- onError;
116
- reason;
117
- constructor(face, path, onError) {
118
- this.face = face;
119
- this.path = path;
120
- this.onError = onError;
121
- }
122
- trip(reason) {
123
- this.reason ??= reason;
124
- this.onError?.(`${this.face} at ${this.path} is corrupt: ${reason}`);
125
- }
126
- get tripped() {
127
- return this.reason !== undefined;
128
- }
129
- assertUsable() {
130
- if (this.reason === undefined)
131
- return;
132
- throw new Error(`refusing to use a corrupt ${this.face} (${this.path}): ${this.reason} — ` +
133
- "the permission-rule lane is fail-closed until a person inspects the file (moving it aside restarts the lane with an empty log)");
134
- }
135
- }
112
+ const APPROVAL_LOG_SHAPE = {
113
+ what: "permission-rule approval line that is neither a current-form put/discard nor a pre-form put",
114
+ read(record) {
115
+ const parsed = ApprovalLineSchema.safeParse(record);
116
+ if (parsed.success)
117
+ return { kind: "line", line: parsed.data };
118
+ const stale = StaleApprovalLineSchema.safeParse(record);
119
+ if (stale.success && stale.data.record.schema !== RULE_APPROVAL_RECORD_SCHEMA)
120
+ return { kind: "stale", envelope: toStaleEnvelope(stale.data.record) };
121
+ return undefined;
122
+ },
123
+ };
124
+ const TICKET_LOG_SHAPE = {
125
+ what: "permission-rule import-ticket line outside the mint/consume/release/settle shapes",
126
+ read(record) {
127
+ const parsed = TicketLineSchema.safeParse(record);
128
+ return parsed.success ? parsed.data : undefined;
129
+ },
130
+ };
136
131
  export class FileRuleApprovalRecordStore {
137
132
  rows = new Map();
138
133
  log;
139
134
  gate;
140
- constructor(dir, onError) {
135
+ constructor(dir, warn) {
141
136
  ensureDir(dir);
142
137
  const path = join(dir, "approvals.jsonl");
143
- this.gate = new CorruptionGate("permission-rule approval log", path, onError);
144
- for (const raw of readJsonlRecords(path, createSkippedRecordSink((info) => this.gate.trip(info.reason)))) {
145
- const parsed = ApprovalLineSchema.safeParse(raw);
146
- if (!parsed.success) {
147
- const stale = StaleApprovalLineSchema.safeParse(raw);
148
- if (stale.success && stale.data.record.schema !== RULE_APPROVAL_RECORD_SCHEMA) {
149
- this.rows.set(stale.data.record.id, toStaleEnvelope(stale.data.record));
150
- continue;
151
- }
152
- this.gate.trip(`a replayed record does not carry a readable shape: ${parsed.error.message}`);
138
+ const { records, gate } = replayJsonlGuarded(path, "permission-rule-approvals", APPROVAL_LOG_SHAPE, warn);
139
+ this.gate = gate;
140
+ for (const entry of records) {
141
+ if (entry.kind === "stale") {
142
+ this.rows.set(entry.envelope.id, entry.envelope);
153
143
  continue;
154
144
  }
155
- if (parsed.data.op === "discard")
156
- this.rows.delete(parsed.data.id);
145
+ const line = entry.line;
146
+ if (line.op === "discard")
147
+ this.rows.delete(line.id);
157
148
  else
158
- this.rows.set(parsed.data.record.id, toApprovalRecord(parsed.data.record));
149
+ this.rows.set(line.record.id, toApprovalRecord(line.record));
159
150
  }
160
151
  this.log = new AppendLog(path);
161
152
  }
@@ -204,19 +195,14 @@ export class FileRuleImportTicketStore {
204
195
  log;
205
196
  gate;
206
197
  monotonic;
207
- constructor(dir, now, onError, monotonic) {
198
+ constructor(dir, now, warn, monotonic) {
208
199
  this.now = now ?? Date.now;
209
200
  this.monotonic = monotonic ?? (() => performance.now());
210
201
  ensureDir(dir);
211
202
  const path = join(dir, "import-tickets.jsonl");
212
- this.gate = new CorruptionGate("permission-rule import-ticket log", path, onError);
213
- for (const raw of readJsonlRecords(path, createSkippedRecordSink((info) => this.gate.trip(info.reason)))) {
214
- const parsed = TicketLineSchema.safeParse(raw);
215
- if (!parsed.success) {
216
- this.gate.trip(`a replayed record does not carry a readable shape: ${parsed.error.message}`);
217
- continue;
218
- }
219
- const line = parsed.data;
203
+ const { records, gate } = replayJsonlGuarded(path, "permission-rule-tickets", TICKET_LOG_SHAPE, warn);
204
+ this.gate = gate;
205
+ for (const line of records) {
220
206
  if (line.op === "mint") {
221
207
  this.rows.set(line.ticketId, {
222
208
  ownerKey: line.ownerKey,
@@ -344,8 +330,8 @@ export function createFilePermissionRuleStores(root, opts) {
344
330
  const dir = join(root, "permission-rules");
345
331
  ensureDir(dir);
346
332
  const durable = opts?.onError ? new FileDurableRulePartitionProvider(dir, opts.onError) : new FileDurableRulePartitionProvider(dir);
347
- const approvals = new FileRuleApprovalRecordStore(dir, opts?.onError);
348
- const tickets = new FileRuleImportTicketStore(dir, opts?.now, opts?.onError, opts?.monotonic);
333
+ const approvals = new FileRuleApprovalRecordStore(dir, opts?.warn);
334
+ const tickets = new FileRuleImportTicketStore(dir, opts?.now, opts?.warn, opts?.monotonic);
349
335
  return {
350
336
  durable,
351
337
  approvals,
@@ -4,6 +4,7 @@ import type { SessionSummary } from "./store-contracts.js";
4
4
  import { type PlacementAcquireOpts } from "./session-placement.js";
5
5
  import { type StagingHandle } from "../session-sync-kernel.js";
6
6
  import { type InstructionSourceBaseline } from "../instruction-source-notice.js";
7
+ import { type SessionFace } from "../session-incarnation.js";
7
8
  import { type IndexSpec } from "./ensure-index.js";
8
9
  /**
9
10
  * PG-dialect DDL for the L1 session log, translated from tidb-pool.ts SCHEMA_STATEMENTS:
@@ -80,6 +81,8 @@ export declare class PgSessionStore implements SessionStore {
80
81
  }): Promise<PlacedSessionRow[]>;
81
82
  /** Owner principal of a session, `null` if registered ownerless, `undefined` if no such session. */
82
83
  ownerOf(sessionId: string): Promise<string | null | undefined>;
84
+ /** S-790:属主 + 化身一次读(TiDB 孪生 {@link TiDBSessionStore.sessionFaceOf} 同义;空化身 ⇒ 响亮拒)。 */
85
+ sessionFaceOf(sessionId: string): Promise<SessionFace | undefined>;
83
86
  touch(sessionId: string): Promise<void>;
84
87
  /** E18 — the session's current leaf SessionTreeEntry.id (session_meta.leaf_id). @see TiDBSessionStore.getLeafId
85
88
  * (single SELECT, no acquire/lock, cache-bypassing — safe to read mid-run for the resume-at turn-capture). */