@sema-agent/server 7.3.0 → 7.4.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 (98) hide show
  1. package/USAGE.md +20 -0
  2. package/dist/approval-reconciler.d.ts +12 -5
  3. package/dist/boot/coordinators.js +3 -1
  4. package/dist/boot/deferred-sandbox-path-env.d.ts +99 -0
  5. package/dist/boot/deferred-sandbox-path-env.js +279 -0
  6. package/dist/boot/execution-env.js +11 -1
  7. package/dist/boot/lexical-path-env.d.ts +0 -4
  8. package/dist/boot/lexical-path-env.js +6 -34
  9. package/dist/boot/resolve-spec.js +50 -38
  10. package/dist/capabilities/center-prompts.js +4 -1
  11. package/dist/capabilities/oa-tools.d.ts +15 -0
  12. package/dist/capabilities/oa-tools.js +54 -0
  13. package/dist/config-types.d.ts +8 -1
  14. package/dist/config.js +9 -1
  15. package/dist/elicitation.js +5 -1
  16. package/dist/finance/cost-taxonomy.d.ts +34 -0
  17. package/dist/finance/cost-taxonomy.js +26 -0
  18. package/dist/hooks/hook-runner.js +32 -0
  19. package/dist/http/routes/diagnostics.d.ts +84 -0
  20. package/dist/http/routes/diagnostics.js +140 -0
  21. package/dist/http/routes/tasks.js +31 -10
  22. package/dist/http/server.d.ts +5 -0
  23. package/dist/http/server.js +6 -0
  24. package/dist/main.js +34 -3
  25. package/dist/observability/fail-open.d.ts +98 -0
  26. package/dist/observability/fail-open.js +216 -0
  27. package/dist/observability/prompt-manifest.d.ts +13 -0
  28. package/dist/observability/prompt-manifest.js +8 -0
  29. package/dist/plugins/approval-store-sql.d.ts +116 -0
  30. package/dist/plugins/approval-store-sql.js +151 -0
  31. package/dist/plugins/file-workflow-journal-store.d.ts +12 -0
  32. package/dist/plugins/file-workflow-journal-store.js +12 -0
  33. package/dist/plugins/pg-approval-store.d.ts +9 -0
  34. package/dist/plugins/pg-approval-store.js +9 -0
  35. package/dist/plugins/pg-breaker-state.d.ts +8 -0
  36. package/dist/plugins/pg-breaker-state.js +8 -0
  37. package/dist/plugins/pg-checkpoint-store.d.ts +10 -0
  38. package/dist/plugins/pg-checkpoint-store.js +10 -0
  39. package/dist/plugins/pg-file-snapshot-store.d.ts +8 -0
  40. package/dist/plugins/pg-file-snapshot-store.js +8 -0
  41. package/dist/plugins/pg-image-bake.d.ts +12 -0
  42. package/dist/plugins/pg-image-bake.js +11 -0
  43. package/dist/plugins/pg-image-index.d.ts +12 -0
  44. package/dist/plugins/pg-image-index.js +11 -0
  45. package/dist/plugins/pg-outcome-ledger.d.ts +12 -0
  46. package/dist/plugins/pg-outcome-ledger.js +11 -0
  47. package/dist/plugins/pg-resume-anchor-store.d.ts +7 -0
  48. package/dist/plugins/pg-resume-anchor-store.js +7 -0
  49. package/dist/plugins/pg-run-store.d.ts +9 -0
  50. package/dist/plugins/pg-run-store.js +9 -0
  51. package/dist/plugins/pg-session-policy-store.d.ts +7 -0
  52. package/dist/plugins/pg-session-policy-store.js +7 -0
  53. package/dist/plugins/pg-session-store.d.ts +12 -0
  54. package/dist/plugins/pg-session-store.js +12 -0
  55. package/dist/plugins/pg-tool-result-store.d.ts +9 -0
  56. package/dist/plugins/pg-tool-result-store.js +9 -0
  57. package/dist/plugins/pg-workflow-journal-store.d.ts +9 -0
  58. package/dist/plugins/pg-workflow-journal-store.js +9 -0
  59. package/dist/plugins/pg-workflow-run-store.d.ts +9 -0
  60. package/dist/plugins/pg-workflow-run-store.js +9 -0
  61. package/dist/plugins/tidb-approval-store.d.ts +8 -0
  62. package/dist/plugins/tidb-approval-store.js +8 -0
  63. package/dist/plugins/tidb-breaker-state.d.ts +7 -0
  64. package/dist/plugins/tidb-breaker-state.js +7 -0
  65. package/dist/plugins/tidb-checkpoint-store.d.ts +9 -0
  66. package/dist/plugins/tidb-checkpoint-store.js +9 -0
  67. package/dist/plugins/tidb-file-snapshot-store.d.ts +8 -0
  68. package/dist/plugins/tidb-file-snapshot-store.js +8 -0
  69. package/dist/plugins/tidb-image-bake.d.ts +12 -0
  70. package/dist/plugins/tidb-image-bake.js +11 -0
  71. package/dist/plugins/tidb-image-index.d.ts +12 -0
  72. package/dist/plugins/tidb-image-index.js +11 -0
  73. package/dist/plugins/tidb-outcome-ledger.d.ts +12 -0
  74. package/dist/plugins/tidb-outcome-ledger.js +12 -0
  75. package/dist/plugins/tidb-resume-anchor-store.d.ts +7 -0
  76. package/dist/plugins/tidb-resume-anchor-store.js +7 -0
  77. package/dist/plugins/tidb-run-store.d.ts +10 -0
  78. package/dist/plugins/tidb-run-store.js +9 -0
  79. package/dist/plugins/tidb-session-policy-store.d.ts +7 -0
  80. package/dist/plugins/tidb-session-policy-store.js +7 -0
  81. package/dist/plugins/tidb-tool-result-store.d.ts +8 -0
  82. package/dist/plugins/tidb-tool-result-store.js +10 -0
  83. package/dist/plugins/tidb-workflow-journal-store.d.ts +9 -0
  84. package/dist/plugins/tidb-workflow-journal-store.js +9 -0
  85. package/dist/plugins/tidb-workflow-run-store.d.ts +10 -0
  86. package/dist/plugins/tidb-workflow-run-store.js +10 -0
  87. package/dist/plugins/workflow-journal-limits.d.ts +12 -0
  88. package/dist/plugins/workflow-journal-limits.js +12 -0
  89. package/dist/question.d.ts +18 -14
  90. package/dist/question.js +83 -34
  91. package/dist/sema-registry.d.ts +41 -0
  92. package/dist/sema-registry.js +40 -0
  93. package/dist/task-settings.d.ts +13 -12
  94. package/dist/task-settings.js +6 -6
  95. package/dist/tool-approval.js +15 -7
  96. package/dist/trace/project.d.ts +36 -1
  97. package/dist/trace/project.js +55 -2
  98. package/package.json +3 -3
@@ -0,0 +1,216 @@
1
+ /**
2
+ * #157 fail-open 治理专项 —— 兜底路径的**响亮化**工具(server 半场;跨仓方案见 sema-comms
3
+ * `audits/failopen-governance-176.md` §2,三仓语义对齐、实现各自)。
4
+ *
5
+ * ## 它解决的问题
6
+ * 「走了兜底」与「一切正常」在外部**同形**——成功回执一样、日志一样、指标一样。于是一个静默降级
7
+ * 能带病运行数周,只能靠"该发生的事没发生"这种间接症状发现。本模块给每条合法兜底臂一个**登记过的
8
+ * tag**,走到即留痕三件套:
9
+ *
10
+ * 1. **计数(逐次)** —— `fail_open_total{tag,cls}`;
11
+ * 2. **探针文件(逐次)** —— `SEMA_FAILOPEN_PROBE=<path>` 在场时逐次追加 `fail-open <tag> <detail>`。
12
+ * 逐次是承重的:同进程多腿时"第二腿起无行"会让「无行=没走兜底」的黑盒断言变成假绿;
13
+ * 3. **一次性 warn(每进程每 tag 一次)** —— 防刷屏。**只有这一件是一次性的**,计数与探针都逐次。
14
+ *
15
+ * ## 它**不**解决的问题(封装不是合法化通道)
16
+ * 词表的 `cls` 只有 `F` 与 `P-DEBT` 两种,**结构性**地把 P/Z 类挡在外面:
17
+ * - **P 保护型**(权限门/判定词表/凭证/车道选择):正解是 fail-closed,不是"留痕后继续放行"。
18
+ * 确实一时改不动的,才允许以 `P-DEBT` 登记——让债在遥测里显形,而不是让它消失。
19
+ * - **Z 计量型**(重试/预算/超时/清理范围):正解是归零 + 自己的节流告警
20
+ * (先例:`boot/reapers.ts` 的 `createThrottledReaperCatch`),不走本封装。
21
+ *
22
+ * ## 词表是闭集
23
+ * {@link FailOpenTag} 由 {@link FAIL_OPEN_TAGS} 的键推导,未登记的 tag 是**编译期错误**——
24
+ * "新 tag 须登记"因此是机器执法的,不靠自觉。新增 tag 同时要在 `docs/FAIL-OPEN-CENSUS.md` 补行。
25
+ *
26
+ * ## 进程级而非注入级
27
+ * 三件套里有两件(一次性 warn、进程计数)天然是**进程**范围的,且真正的兜底臂往往在没有 logger/
28
+ * metrics 句柄的深处(`question.ts` / `elicitation.ts` 全文零观测通道)。所以对外主口是自由函数
29
+ * {@link recordFailOpen},装配层用 {@link installFailOpenRecorder} 把 logger/metrics 接上即可;
30
+ * 装配之前的调用不会丢——探针与 stderr 契约行不依赖注入,计数也照常累加并在装配时继承过去。
31
+ */
32
+ import { appendFileSync } from "node:fs";
33
+ /** 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。 */
34
+ export const FAIL_OPEN_TAGS = {
35
+ "server.hitl.frame-undelivered-stream-closed": {
36
+ cls: "P-DEBT",
37
+ note: "HITL 的 **open** 帧(question / elicitation)写向一条已断/已关的 SSE 连接 ⇒ 静默丢弃,上游据此把投递记成成功,问到 TTL 才按无人应答结算。方向(no-op 而非 throw)是既有的刻意决定,本条只保证它不再无声。",
38
+ },
39
+ "server.stream.frame-dropped-stream-closed": {
40
+ cls: "F",
41
+ note: "非 HITL-open 的运行流帧写向已断连接 ⇒ 丢弃。连接都没了,这一帧本就无人可看;单独立项是为了不让它挤进上面那条保护型计数。",
42
+ },
43
+ "server.question.open-frame-undelivered": {
44
+ cls: "P-DEBT",
45
+ note: "AskUserQuestion 的 open 帧投递失败 ⇒ 按 headless 默认结算,模型不问人自己往下判。方向是刻意的产品姿态(绝不把 run 挂死),但一次投递故障确实让人在环这道门被跳过——记债,不当合法兜底。",
46
+ },
47
+ "server.elicitation.open-frame-undelivered": {
48
+ cls: "F",
49
+ note: "MCP elicitation 的 open 帧投递失败 ⇒ 结算 decline。方向本身是 fail-closed(问不到人就是拒),缺的只是留痕。",
50
+ },
51
+ "server.question.complete-breadcrumb-dropped": {
52
+ cls: "F",
53
+ note: "question_complete 面包屑(对话框消解提示)持久化失败。答案本身走的是另一条路且早已返回,丢的只是壳里一次收尾渲染。",
54
+ },
55
+ "server.elicitation.complete-breadcrumb-dropped": {
56
+ cls: "F",
57
+ note: "elicitation_complete 面包屑持久化失败。同上——答案已经在壳手里,丢的是收尾渲染。",
58
+ },
59
+ "server.prompts.artifact-cache-write-failed": {
60
+ cls: "F",
61
+ note: "已验签的提示词工件回填本地缓存失败。工件本身已返回给调用方;代价是下次同 digest 还要再取一次网络。",
62
+ },
63
+ };
64
+ /**
65
+ * 断流丢帧该记哪个 tag —— **按帧型分类**,纯函数(与写流的那条闭包解耦,才单测得动)。
66
+ *
67
+ * 一刀切记同一个 tag 是错的:承载 HITL 帧的那条闭包同时驮着完成面包屑与其它运行流帧,于是**一次**
68
+ * 断连会让保护型计数涨两次(open 一次、complete 又一次),真正的 open 失败率被面包屑的假阳性盖住。
69
+ * 只有 open 帧是"人在环这道门被跳过"的证据,其余都是体验损失。
70
+ */
71
+ export function failOpenTagForDroppedFrame(frameType) {
72
+ switch (frameType) {
73
+ case "question":
74
+ case "elicitation":
75
+ return "server.hitl.frame-undelivered-stream-closed";
76
+ case "question_complete":
77
+ return "server.question.complete-breadcrumb-dropped";
78
+ case "elicitation_complete":
79
+ return "server.elicitation.complete-breadcrumb-dropped";
80
+ default:
81
+ return "server.stream.frame-dropped-stream-closed";
82
+ }
83
+ }
84
+ /** 探针文件的 env 名(跨仓同名同形:`audits/failopen-governance-176.md` §2-r2,壳/引擎同认)。 */
85
+ export const FAIL_OPEN_PROBE_ENV = "SEMA_FAILOPEN_PROBE";
86
+ /** 计数器名。`cls` 进标签:一眼分得出"合法兜底"与"保护型欠账"两种曲线,后者应当只降不升。 */
87
+ const FAIL_OPEN_COUNTER = "fail_open_total";
88
+ export function createFailOpenRecorder(deps = {}) {
89
+ const counts = new Map(deps.seedCounts ?? []);
90
+ const warned = new Set(deps.seedWarned ?? []);
91
+ const logged = new Set(deps.seedLogged ?? []);
92
+ const backlog = new Map(deps.seedBacklog ?? []);
93
+ const probePath = deps.probePath ?? process.env[FAIL_OPEN_PROBE_ENV];
94
+ const writeStderr = deps.writeStderr ?? ((line) => void process.stderr.write(line));
95
+ const appendProbe = deps.appendProbe ?? ((path, line) => appendFileSync(path, line));
96
+ deps.metrics?.counter(FAIL_OPEN_COUNTER, "fallback (fail-open) arms traversed, by registered tag — see docs/FAIL-OPEN-CENSUS.md");
97
+ // 补账:装配前(无 metrics/无 logger)命中的那几次不能凭空消失——否则 `fail_open_total` 永久
98
+ // 少算,少的还正是 boot 期兜底。metrics 到场即把欠账一次性灌进去并清零(所以再 install 一次不会重放),
99
+ // 结构化 warn 同理按 tag 补一条。stderr 契约行**不补**:它在装配前就已经喊过了,一 tag 一次是它的全部价值。
100
+ // 🔴 记账**后于**投递(codex R2 复审 MEDIUM):先清 backlog / 先 `logged.add` 再投递,等于把一次
101
+ // 投递失败记成"已送达"——欠账从此无法重放,而它保的正是最需要补的那批 boot 期兜底。
102
+ if (deps.metrics !== undefined && backlog.size > 0) {
103
+ for (const [tag, n] of [...backlog]) {
104
+ try {
105
+ deps.metrics.inc(FAIL_OPEN_COUNTER, { tag, cls: FAIL_OPEN_TAGS[tag].cls }, n);
106
+ backlog.delete(tag); // 只有真进了汇才销账
107
+ }
108
+ catch {
109
+ /* 留在 backlog 里,下一次装配再补 */
110
+ }
111
+ }
112
+ }
113
+ // 补痕的起算集=**观测到的 tag 全集**(`counts`),不是"stderr 喊成功过的那批"(`warned`)——
114
+ // 后者在"装配前命中 + stderr 正断裂"这条组合路上恒空,那次兜底的结构化痕迹就永久没了(codex R3)。
115
+ if (deps.logger !== undefined) {
116
+ for (const tag of counts.keys()) {
117
+ if (logged.has(tag))
118
+ continue;
119
+ try {
120
+ deps.logger.warn("fail_open", { tag, cls: FAIL_OPEN_TAGS[tag].cls, note: FAIL_OPEN_TAGS[tag].note, backfilled: true });
121
+ logged.add(tag); // 同上:warn 真发出去才算已报
122
+ }
123
+ catch {
124
+ /* 不记 logged,下一次装配/下一次命中再补 */
125
+ }
126
+ }
127
+ }
128
+ return {
129
+ record: (tag, detail) => {
130
+ // 三件套逐件独立 try:一件的失败(坏路径的探针文件、抛错的 logger 传输)绝不能让**另外两件**
131
+ // 也丢,更不能让调用方的兜底臂变成抛错——那等于把"降级"换成"故障",正是本模块要消灭的方向。
132
+ const entry = FAIL_OPEN_TAGS[tag];
133
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
134
+ if (deps.metrics === undefined) {
135
+ backlog.set(tag, (backlog.get(tag) ?? 0) + 1); // 记欠账,等 install 补
136
+ }
137
+ else {
138
+ try {
139
+ deps.metrics.inc(FAIL_OPEN_COUNTER, { tag, cls: entry.cls });
140
+ }
141
+ catch {
142
+ // 汇在场但这一次没进去 ⇒ 同样记欠账(而不是蒸发),等下一次装配补。
143
+ backlog.set(tag, (backlog.get(tag) ?? 0) + 1);
144
+ }
145
+ }
146
+ if (probePath !== undefined && probePath !== "") {
147
+ try {
148
+ appendProbe(probePath, `fail-open ${tag}${detail !== undefined && detail !== "" ? ` ${detail}` : ""}\n`);
149
+ }
150
+ catch {
151
+ /* 探针路径不可写(只读盘/权限)⇒ 静默放弃,不牵连本次兜底 */
152
+ }
153
+ }
154
+ // 只有**真发出去**才记进 logged —— 没有 logger 时记了,会让日后 install 的补账把这条当成
155
+ // "已经报过"而跳过,那正是本轮复审要修的漏账形。
156
+ if (deps.logger !== undefined && !logged.has(tag)) {
157
+ try {
158
+ deps.logger.warn("fail_open", { tag, cls: entry.cls, note: entry.note, ...(detail !== undefined ? { detail } : {}) });
159
+ logged.add(tag); // 记账后于投递:没发出去就不算已报,下一次命中重试
160
+ }
161
+ catch {
162
+ /* 观测不得成为故障 */
163
+ }
164
+ }
165
+ if (warned.has(tag))
166
+ return;
167
+ try {
168
+ // 跨仓约定的黑盒锚,逐字 `[sema] fail-open: <tag>`。**不依赖** logger 是否装配上——
169
+ // 评测线的断言打的就是这一行,它在 boot 之前的兜底上也必须成立。
170
+ writeStderr(`[sema] fail-open: ${tag}\n`);
171
+ warned.add(tag); // 同上。stderr 永久断裂时代价=每次命中试写一次(有界),不是丢掉这条契约行
172
+ }
173
+ catch {
174
+ /* 断裂的 stderr(EPIPE 族)⇒ 丢这行,不丢进程 */
175
+ }
176
+ },
177
+ counts: () => counts,
178
+ warnedTags: () => warned,
179
+ metricsBacklog: () => backlog,
180
+ loggedTags: () => logged,
181
+ };
182
+ }
183
+ /** 进程单例。装配前也可用(探针+stderr 契约行照常,计数照常;只是没有结构化 warn/metric)。 */
184
+ let current = createFailOpenRecorder();
185
+ /**
186
+ * 装配层接线:把 logger/metrics 接到进程单例上。搬四份状态过去,各有各的理由:
187
+ * - `seedCounts` —— 逻辑总数续算,不从 0 重来;
188
+ * - `seedWarned` —— boot 期已喊过的 stderr 契约行不再喊(「每进程每 tag 一次」是它的全部价值);
189
+ * - `seedBacklog` —— 装配**前**命中的次数还没进任何 metrics 汇,交给新实例补账(补完清零,
190
+ * 所以第二次 install 不会重放);
191
+ * - `seedLogged` —— 已真发出去的结构化 warn 不重发;没发过的由新 logger 补一条。
192
+ */
193
+ export function installFailOpenRecorder(deps) {
194
+ current = createFailOpenRecorder({
195
+ ...deps,
196
+ seedCounts: current.counts(),
197
+ seedWarned: current.warnedTags(),
198
+ seedBacklog: current.metricsBacklog(),
199
+ seedLogged: current.loggedTags(),
200
+ });
201
+ return current;
202
+ }
203
+ /** 兜底臂的调用口。`tag` 必须已登记(闭集类型),`detail` 是可选的一行现场(路径/原因,不放密钥)。 */
204
+ export function recordFailOpen(tag, detail) {
205
+ current.record(tag, detail);
206
+ }
207
+ /** 本进程各 tag 累计次数——`/metrics` 之外的进程内读数(测试与自检面用)。 */
208
+ export function failOpenCounts() {
209
+ return current.counts();
210
+ }
211
+ /** test-only:换一个全新单例(计数与 warn 集合清零),让每个用例都从"第一次"看起。 */
212
+ export function resetFailOpenRecorderForTest(deps = {}) {
213
+ current = createFailOpenRecorder(deps);
214
+ return current;
215
+ }
216
+ //# sourceMappingURL=fail-open.js.map
@@ -31,6 +31,12 @@ export interface PromptManifestRecord {
31
31
  shapeDigest: string;
32
32
  wireSchemaDigest: string;
33
33
  }>;
34
+ /** core 5.15.0(#181):deferred 披露策略自证三键(诊断值,零内容)。缺席=core <5.15 或无 deferred。 */
35
+ toolDisclosure?: {
36
+ deferredTools: number;
37
+ strategy: string;
38
+ source: string;
39
+ };
34
40
  totalChars: number;
35
41
  }
36
42
  /** Whitelist one `prompt.assembled` trace event into a {@link PromptManifestRecord} (v1 face required,
@@ -61,6 +67,13 @@ export declare function promptManifestRecordOf(e: {
61
67
  shapeDigest: string;
62
68
  wireSchemaDigest: string;
63
69
  }>;
70
+ /** core 5.15.0(#181,[2872] 提货②):deferred 工具的披露策略自证——{deferredTools(数), strategy
71
+ * ("static"|"swap"), source(四值:spec/env/api-default/default)}。诊断三键(数+两闭集词,零内容)。 */
72
+ toolDisclosure?: {
73
+ deferredTools: number;
74
+ strategy: string;
75
+ source: string;
76
+ };
64
77
  totalChars: number;
65
78
  }): PromptManifestRecord;
66
79
  /**
@@ -14,6 +14,14 @@ export function promptManifestRecordOf(e) {
14
14
  ...(Array.isArray(e.tools)
15
15
  ? { tools: e.tools.map((t) => ({ wireName: String(t.wireName), aliases: (Array.isArray(t.aliases) ? t.aliases : []).map(String), contractId: String(t.contractId), implementationRevision: String(t.implementationRevision), cardId: String(t.cardId), shapeDigest: String(t.shapeDigest), wireSchemaDigest: String(t.wireSchemaDigest) })) }
16
16
  : {}),
17
+ // toolDisclosure(core 5.15.0 #181,[2872] 提货②):三键逐挑——数值有限、两词字符串非空才铸;
18
+ // 畸形按缺席(不铸假值,与 sections/tools 的 additive 同规)。
19
+ ...(typeof e.toolDisclosure === "object" && e.toolDisclosure !== null &&
20
+ typeof e.toolDisclosure.deferredTools === "number" && Number.isFinite(e.toolDisclosure.deferredTools) &&
21
+ typeof e.toolDisclosure.strategy === "string" && e.toolDisclosure.strategy.length > 0 &&
22
+ typeof e.toolDisclosure.source === "string" && e.toolDisclosure.source.length > 0
23
+ ? { toolDisclosure: { deferredTools: e.toolDisclosure.deferredTools, strategy: e.toolDisclosure.strategy, source: e.toolDisclosure.source } }
24
+ : {}),
17
25
  totalChars: e.totalChars,
18
26
  };
19
27
  }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Durable approval store for the human-in-the-loop write gate (F4) — SINGLE-FILE DUAL-DIALECT
3
+ * (design/158 A12 定型半场). ONE implementation, TWO dialects; the historical `TiDBApprovalStore` /
4
+ * `PgApprovalStore` class names survive as thin ctor subclasses so every consumer (store-backend.ts,
5
+ * src/index.ts, the approval/audit/security-coverage suites) is untouched.
6
+ *
7
+ * When a gated tool call is about to run, a `pending` row is recorded here; the instance running the
8
+ * task polls it for a decision. Because the row is durable and shared, an operator's approve/deny can
9
+ * land on ANY instance — critical under async runs + a load balancer, where the deciding request and
10
+ * the waiting run are usually on different instances.
11
+ *
12
+ * Unlike the run/checkpoint PG store (whose schema is owned centrally by pg-pool.ts), this file also
13
+ * exports a self-contained PG schema (`PG_APPROVAL_SCHEMA` + `ensureSchema`) — the `approval` table is
14
+ * DISJOINT from the other PG stores' tables, so a CREATE IF NOT EXISTS here shadows nothing. Central
15
+ * aggregation into pg-pool.ts composes it alongside the others. Kept in lock-step with the TiDB store
16
+ * by the shared real-DB integration suite (test/pg-approval-store-integration.test.ts).
17
+ *
18
+ * ── Dialect deltas, kept EXPLICIT ────────────────────────────────────────────────────────────────────
19
+ * - `?` placeholders vs `$n`
20
+ * - null-safe scope compare: `<=>` vs `IS NOT DISTINCT FROM` (a NULL scope row — legacy/untenanted
21
+ * run — is matched by a NULL scope arg; one tenant's scope NEVER matches another's — the
22
+ * single-DB fleet read guard, defense-in-depth)
23
+ * - JSON binding: TiDB `JSON.stringify` verbatim vs PG `pgProtocolJsonStringify` (codex R4-H2: the
24
+ * persisted approval `args` is the operator's REVIEW surface and must agree byte-for-byte with the
25
+ * policy-executed args — a lossy/rewriting envelope would let an operator approve one payload while
26
+ * a different one runs)
27
+ * - affectedRows vs rowCount (via SqlDriver)
28
+ * - schema ownership: TiDB DDL lives in tidb-pool.ts SCHEMA_STATEMENTS; PG DDL is this file's own
29
+ * self-contained `PG_APPROVAL_SCHEMA` (see the PG twin's original header — unlike the other PG
30
+ * stores, `approval` is NOT centrally owned by pg-pool.ts's DDL array, only aggregated into its
31
+ * `ensureSchema` composition).
32
+ * - `getStatus` result shape: `RowDataPacket[]` (mysql2 typed query) vs a plain-record `pg` query —
33
+ * normalized to `SqlRow` by the shared driver, so both arms read the same field names.
34
+ */
35
+ import type { Pool as MySqlPool } from "mysql2/promise";
36
+ import type { Pool as PgPool, PoolClient } from "pg";
37
+ import { type SqlDriver } from "./sql-driver.js";
38
+ export type ApprovalStatus = "pending" | "approved" | "denied" | "expired";
39
+ export interface ApprovalRow {
40
+ id: string;
41
+ taskId: string | null;
42
+ sessionId: string | null;
43
+ owner: string | null;
44
+ /** Single-DB fleet scope guard (defense-in-depth): the run's tenant identity, owner-sourced. Reads filter on it. */
45
+ scope: string | null;
46
+ toolName: string;
47
+ args: unknown;
48
+ status: ApprovalStatus;
49
+ reason: string | null;
50
+ decidedBy: string | null;
51
+ createdAt: string;
52
+ decidedAt: string | null;
53
+ }
54
+ /** PG translation of the `approval` DDL in tidb-pool.ts SCHEMA_STATEMENTS (JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3),
55
+ * inline KEY→separate CREATE INDEX).
56
+ *
57
+ * SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth, schema changes are
58
+ * drop-and-recreate, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead. */
59
+ export declare const PG_APPROVAL_SCHEMA: string[];
60
+ /** Idempotent self-contained schema apply (for the integration test; central aggregation is done separately). */
61
+ export declare function ensureSchema(pool: PgPool | PoolClient): Promise<void>;
62
+ /** Dual-dialect durable approval store. See the file header for the dialect-delta ledger. */
63
+ export declare class SqlApprovalStore {
64
+ protected readonly db: SqlDriver;
65
+ constructor(db: SqlDriver);
66
+ /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
67
+ private q;
68
+ /** JSON column binding: TiDB stores the string verbatim; PG goes through the LOSSLESS protocol envelope
69
+ * (codex R4-H2 — the approval `args` is the operator's review surface and must agree with the
70
+ * policy-executed args at the NUL position). */
71
+ private json;
72
+ createPending(id: string, scope: string | null, ctx: {
73
+ taskId?: string | null;
74
+ sessionId?: string | null;
75
+ owner?: string | null;
76
+ toolName: string;
77
+ args: unknown;
78
+ }): Promise<void>;
79
+ getStatus(id: string, scope: string | null): Promise<{
80
+ status: ApprovalStatus;
81
+ reason: string | null;
82
+ decidedBy: string | null;
83
+ } | undefined>;
84
+ /** Apply a terminal decision, but only to a still-pending row (CAS). Returns whether it applied. The scope
85
+ * guard (null-safe) is carried on the operator path too for consistency with the read paths. */
86
+ decide(id: string, scope: string | null, decision: "approved" | "denied" | "expired", reason: string | null, decidedBy: string | null): Promise<boolean>;
87
+ /** Pending approvals (operator queue), scoped to one tenant (single-DB fleet read guard, null-safe), optionally
88
+ * further filtered to one owner. */
89
+ listPending(scope: string | null, owner?: string): Promise<ApprovalRow[]>;
90
+ get(id: string, scope: string | null): Promise<ApprovalRow | undefined>;
91
+ /** Operator-only CROSS-tenant by-id read (no scope filter). The F4 operator queue serves/decides across
92
+ * tenants by design; the HTTP layer gates this to an operator and then re-binds decide() to the row's OWN
93
+ * scope. Tenants NEVER reach this — they use the scope-bound {@link get}. */
94
+ getById(id: string): Promise<ApprovalRow | undefined>;
95
+ /** Operator-only CROSS-tenant pending list (no scope filter) — the unfiltered operator queue across all
96
+ * tenants. Tenants use the scope-bound {@link listPending}; this is HTTP-gated to an operator.
97
+ * 🔴 identical SQL text on both dialects (no placeholders, nothing dialect-specific) — kept as ONE literal
98
+ * rather than a `q()` pair so a reader isn't misled into looking for a divergence that isn't there. */
99
+ listPendingAll(): Promise<ApprovalRow[]>;
100
+ /** Expire pending approvals older than `olderThanMs` (a never-answered operator request). Deliberately GLOBAL
101
+ * (NOT scope-filtered): staleness expiry is fleet-level maintenance that protects the worker regardless of
102
+ * tenant — a stale row in any scope must be reaped. Scope isolation applies to the per-tenant READ paths, not
103
+ * to this maintenance sweep. */
104
+ expireStale(olderThanMs: number): Promise<number>;
105
+ }
106
+ /** approval row → {@link ApprovalRow} — shared by BOTH dialects. design/158 S8 归位 / A12 收编. */
107
+ export declare function mapRow(r: Record<string, unknown>): ApprovalRow;
108
+ /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
109
+ export declare class TiDBApprovalStore extends SqlApprovalStore {
110
+ constructor(pool: MySqlPool);
111
+ }
112
+ /** PostgreSQL binding — historical class name + ctor shape preserved. */
113
+ export declare class PgApprovalStore extends SqlApprovalStore {
114
+ constructor(pool: PgPool);
115
+ }
116
+ //# sourceMappingURL=approval-store-sql.d.ts.map
@@ -0,0 +1,151 @@
1
+ import { parseJsonStrict as parseJson, toIsoOrNull as iso } from "./sql-row-helpers.js";
2
+ import { pgProtocolJsonStringify } from "./pg-safe-json.js";
3
+ import { mysqlDriver, pgDriver } from "./sql-driver.js";
4
+ /** PG translation of the `approval` DDL in tidb-pool.ts SCHEMA_STATEMENTS (JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3),
5
+ * inline KEY→separate CREATE INDEX).
6
+ *
7
+ * SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth, schema changes are
8
+ * drop-and-recreate, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead. */
9
+ export const PG_APPROVAL_SCHEMA = [
10
+ `CREATE TABLE IF NOT EXISTS approval (
11
+ id VARCHAR(64) NOT NULL,
12
+ task_id VARCHAR(64),
13
+ session_id VARCHAR(64),
14
+ owner VARCHAR(190),
15
+ -- Single-DB fleet scope guard (tenant isolation). NULLABLE on purpose: a NULL-scope row is an untenanted
16
+ -- run, and every read/decide path matches it with \`scope IS NOT DISTINCT FROM $n\` (the PG twin of TiDB
17
+ -- \`<=>\`) so NULL matches only NULL and one tenant's scope NEVER matches another's.
18
+ scope VARCHAR(190),
19
+ tool_name VARCHAR(190) NOT NULL,
20
+ args JSONB,
21
+ status VARCHAR(16) NOT NULL,
22
+ reason TEXT,
23
+ decided_by VARCHAR(190),
24
+ created_at TIMESTAMPTZ(3) NOT NULL,
25
+ decided_at TIMESTAMPTZ(3),
26
+ PRIMARY KEY (id)
27
+ )`,
28
+ `CREATE INDEX IF NOT EXISTS idx_approval_owner_status ON approval (owner, status)`,
29
+ `CREATE INDEX IF NOT EXISTS idx_approval_status ON approval (status)`,
30
+ `CREATE INDEX IF NOT EXISTS idx_approval_scope_status ON approval (scope, status)`,
31
+ ];
32
+ /** Idempotent self-contained schema apply (for the integration test; central aggregation is done separately). */
33
+ export async function ensureSchema(pool) {
34
+ for (const stmt of PG_APPROVAL_SCHEMA)
35
+ await pool.query(stmt);
36
+ }
37
+ /** Dual-dialect durable approval store. See the file header for the dialect-delta ledger. */
38
+ export class SqlApprovalStore {
39
+ db;
40
+ constructor(db) {
41
+ this.db = db;
42
+ }
43
+ /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
44
+ q(tidb, pg) {
45
+ return this.db.dialect === "tidb" ? tidb : pg;
46
+ }
47
+ /** JSON column binding: TiDB stores the string verbatim; PG goes through the LOSSLESS protocol envelope
48
+ * (codex R4-H2 — the approval `args` is the operator's review surface and must agree with the
49
+ * policy-executed args at the NUL position). */
50
+ json(value, label) {
51
+ return this.db.dialect === "tidb" ? JSON.stringify(value) : pgProtocolJsonStringify(value, label);
52
+ }
53
+ async createPending(id, scope, ctx) {
54
+ await this.db.query(this.q("INSERT INTO approval (id, task_id, session_id, owner, scope, tool_name, args, status, created_at) " +
55
+ "VALUES (?,?,?,?,?,?,?,'pending',?)", "INSERT INTO approval (id, task_id, session_id, owner, scope, tool_name, args, status, created_at) " +
56
+ "VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,'pending',$8)"), [
57
+ id,
58
+ ctx.taskId ?? null,
59
+ ctx.sessionId ?? null,
60
+ ctx.owner ?? null,
61
+ scope,
62
+ ctx.toolName,
63
+ ctx.args == null ? null : this.json(ctx.args, "approval args"),
64
+ new Date(),
65
+ ]);
66
+ }
67
+ async getStatus(id, scope) {
68
+ // null-safe scope match (`<=>` / `IS NOT DISTINCT FROM`): a NULL scope row (legacy / untenanted run) is
69
+ // matched by a NULL scope arg, and a tenant's scope NEVER matches another tenant's — the single-DB fleet
70
+ // read guard (defense-in-depth).
71
+ const { rows } = await this.db.query(this.q("SELECT status, reason, decided_by FROM approval WHERE id = ? AND scope <=> ?", "SELECT status, reason, decided_by FROM approval WHERE id = $1 AND scope IS NOT DISTINCT FROM $2"), [id, scope]);
72
+ const r = rows[0];
73
+ if (!r)
74
+ return undefined;
75
+ return { status: r.status, reason: r.reason ?? null, decidedBy: r.decided_by ?? null };
76
+ }
77
+ /** Apply a terminal decision, but only to a still-pending row (CAS). Returns whether it applied. The scope
78
+ * guard (null-safe) is carried on the operator path too for consistency with the read paths. */
79
+ async decide(id, scope, decision, reason, decidedBy) {
80
+ const res = await this.db.query(this.q("UPDATE approval SET status = ?, reason = ?, decided_by = ?, decided_at = ? WHERE id = ? AND scope <=> ? AND status = 'pending'", "UPDATE approval SET status = $1, reason = $2, decided_by = $3, decided_at = $4 WHERE id = $5 AND scope IS NOT DISTINCT FROM $6 AND status = 'pending'"), [decision, reason, decidedBy, new Date(), id, scope]);
81
+ return res.affected === 1;
82
+ }
83
+ /** Pending approvals (operator queue), scoped to one tenant (single-DB fleet read guard, null-safe), optionally
84
+ * further filtered to one owner. */
85
+ async listPending(scope, owner) {
86
+ const { rows } = owner
87
+ ? await this.db.query(this.q("SELECT * FROM approval WHERE status = 'pending' AND scope <=> ? AND owner = ? ORDER BY created_at ASC", "SELECT * FROM approval WHERE status = 'pending' AND scope IS NOT DISTINCT FROM $1 AND owner = $2 ORDER BY created_at ASC"), [scope, owner])
88
+ : await this.db.query(this.q("SELECT * FROM approval WHERE status = 'pending' AND scope <=> ? ORDER BY created_at ASC", "SELECT * FROM approval WHERE status = 'pending' AND scope IS NOT DISTINCT FROM $1 ORDER BY created_at ASC"), [scope]);
89
+ return rows.map(mapRow);
90
+ }
91
+ async get(id, scope) {
92
+ const { rows } = await this.db.query(this.q("SELECT * FROM approval WHERE id = ? AND scope <=> ?", "SELECT * FROM approval WHERE id = $1 AND scope IS NOT DISTINCT FROM $2"), [id, scope]);
93
+ return rows[0] ? mapRow(rows[0]) : undefined;
94
+ }
95
+ /** Operator-only CROSS-tenant by-id read (no scope filter). The F4 operator queue serves/decides across
96
+ * tenants by design; the HTTP layer gates this to an operator and then re-binds decide() to the row's OWN
97
+ * scope. Tenants NEVER reach this — they use the scope-bound {@link get}. */
98
+ async getById(id) {
99
+ const { rows } = await this.db.query(this.q("SELECT * FROM approval WHERE id = ?", "SELECT * FROM approval WHERE id = $1"), [id]);
100
+ return rows[0] ? mapRow(rows[0]) : undefined;
101
+ }
102
+ /** Operator-only CROSS-tenant pending list (no scope filter) — the unfiltered operator queue across all
103
+ * tenants. Tenants use the scope-bound {@link listPending}; this is HTTP-gated to an operator.
104
+ * 🔴 identical SQL text on both dialects (no placeholders, nothing dialect-specific) — kept as ONE literal
105
+ * rather than a `q()` pair so a reader isn't misled into looking for a divergence that isn't there. */
106
+ async listPendingAll() {
107
+ const { rows } = await this.db.query("SELECT * FROM approval WHERE status = 'pending' ORDER BY created_at ASC");
108
+ return rows.map(mapRow);
109
+ }
110
+ /** Expire pending approvals older than `olderThanMs` (a never-answered operator request). Deliberately GLOBAL
111
+ * (NOT scope-filtered): staleness expiry is fleet-level maintenance that protects the worker regardless of
112
+ * tenant — a stale row in any scope must be reaped. Scope isolation applies to the per-tenant READ paths, not
113
+ * to this maintenance sweep. */
114
+ async expireStale(olderThanMs) {
115
+ const cutoff = new Date(Date.now() - olderThanMs);
116
+ const res = await this.db.query(this.q("UPDATE approval SET status = 'expired', reason = 'timed out before decision', decided_at = ? " +
117
+ "WHERE status = 'pending' AND created_at < ?", "UPDATE approval SET status = 'expired', reason = 'timed out before decision', decided_at = $1 " +
118
+ "WHERE status = 'pending' AND created_at < $2"), [new Date(), cutoff]);
119
+ return res.affected;
120
+ }
121
+ }
122
+ /** approval row → {@link ApprovalRow} — shared by BOTH dialects. design/158 S8 归位 / A12 收编. */
123
+ export function mapRow(r) {
124
+ return {
125
+ id: String(r.id),
126
+ taskId: r.task_id ?? null,
127
+ sessionId: r.session_id ?? null,
128
+ owner: r.owner ?? null,
129
+ scope: r.scope ?? null,
130
+ toolName: String(r.tool_name),
131
+ args: parseJson(r.args),
132
+ status: r.status,
133
+ reason: r.reason ?? null,
134
+ decidedBy: r.decided_by ?? null,
135
+ createdAt: iso(r.created_at),
136
+ decidedAt: iso(r.decided_at),
137
+ };
138
+ }
139
+ /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
140
+ export class TiDBApprovalStore extends SqlApprovalStore {
141
+ constructor(pool) {
142
+ super(mysqlDriver(pool));
143
+ }
144
+ }
145
+ /** PostgreSQL binding — historical class name + ctor shape preserved. */
146
+ export class PgApprovalStore extends SqlApprovalStore {
147
+ constructor(pool) {
148
+ super(pgDriver(pool));
149
+ }
150
+ }
151
+ //# sourceMappingURL=approval-store-sql.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SVC-2 — FILE-backed durable WorkflowJournalStore for the LOCAL (TOC) backend.
3
+ *
4
+ * Now a RE-EXPORT of core's copy (the write-here-then-core-adopts 剧本): the implementation was written here for server
5
+ * 1.89.0, then adopted verbatim by core (`@sema-agent/core` 1.223.0, file
6
+ * `src/stores/file/workflow-journal-store.ts` — crash-safe per-run JSONL, scope-guarded [CORE-9],
7
+ * idempotent-per-ordinal, oversize skip-journal, traversal-runId load degrade, 0o600/0o700). One source of
8
+ * truth in core; the SQL twins (tidb/pg) stay service-side (dialect = deployment glue). The service test
9
+ * suite (test/file-workflow-journal-store.test.ts) keeps pinning the contract through this re-export.
10
+ */
11
+ export { FileWorkflowJournalStore } from "@sema-agent/core";
12
+ //# sourceMappingURL=file-workflow-journal-store.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SVC-2 — FILE-backed durable WorkflowJournalStore for the LOCAL (TOC) backend.
3
+ *
4
+ * Now a RE-EXPORT of core's copy (the write-here-then-core-adopts 剧本): the implementation was written here for server
5
+ * 1.89.0, then adopted verbatim by core (`@sema-agent/core` 1.223.0, file
6
+ * `src/stores/file/workflow-journal-store.ts` — crash-safe per-run JSONL, scope-guarded [CORE-9],
7
+ * idempotent-per-ordinal, oversize skip-journal, traversal-runId load degrade, 0o600/0o700). One source of
8
+ * truth in core; the SQL twins (tidb/pg) stay service-side (dialect = deployment glue). The service test
9
+ * suite (test/file-workflow-journal-store.test.ts) keeps pinning the contract through this re-export.
10
+ */
11
+ export { FileWorkflowJournalStore } from "@sema-agent/core";
12
+ //# sourceMappingURL=file-workflow-journal-store.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Thin re-export shim (design/158 A12 收编 — see src/plugins/approval-store-sql.ts for the
3
+ * implementation, now SINGLE-FILE DUAL-DIALECT alongside the TiDB twin). Class name, ctor arity,
4
+ * and every exported type/const/helper stay EXACTLY as they were so no consumer (pg-pool.ts's
5
+ * central schema composition, store-backend.ts, test/pg-approval-store-integration.test.ts) has
6
+ * to move.
7
+ */
8
+ export { PgApprovalStore, PG_APPROVAL_SCHEMA, ensureSchema, type ApprovalRow, type ApprovalStatus, } from "./approval-store-sql.js";
9
+ //# sourceMappingURL=pg-approval-store.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Thin re-export shim (design/158 A12 收编 — see src/plugins/approval-store-sql.ts for the
3
+ * implementation, now SINGLE-FILE DUAL-DIALECT alongside the TiDB twin). Class name, ctor arity,
4
+ * and every exported type/const/helper stay EXACTLY as they were so no consumer (pg-pool.ts's
5
+ * central schema composition, store-backend.ts, test/pg-approval-store-integration.test.ts) has
6
+ * to move.
7
+ */
8
+ export { PgApprovalStore, PG_APPROVAL_SCHEMA, ensureSchema, } from "./approval-store-sql.js";
9
+ //# sourceMappingURL=pg-approval-store.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Re-export shim (design/158 A12 收编) — the real implementation moved to breaker-state-sql.ts
3
+ * (single-file dual-dialect: `SqlBreakerState` + the `TiDBBreakerState`/`PgBreakerState` ctor subclasses).
4
+ * Kept so every consumer's `from "./pg-breaker-state.js"` import (incl. pg-pool.ts's central ensureSchema
5
+ * composition and the real dual-DB integration suite) keeps working unchanged.
6
+ */
7
+ export { PgBreakerState, PG_BREAKER_STATE_SCHEMA, ensureSchema } from "./breaker-state-sql.js";
8
+ //# sourceMappingURL=pg-breaker-state.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Re-export shim (design/158 A12 收编) — the real implementation moved to breaker-state-sql.ts
3
+ * (single-file dual-dialect: `SqlBreakerState` + the `TiDBBreakerState`/`PgBreakerState` ctor subclasses).
4
+ * Kept so every consumer's `from "./pg-breaker-state.js"` import (incl. pg-pool.ts's central ensureSchema
5
+ * composition and the real dual-DB integration suite) keeps working unchanged.
6
+ */
7
+ export { PgBreakerState, PG_BREAKER_STATE_SCHEMA, ensureSchema } from "./breaker-state-sql.js";
8
+ //# sourceMappingURL=pg-breaker-state.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * PostgreSQL `CheckpointStore` — the implementation now lives in the single-file dual-dialect
3
+ * `checkpoint-store-sql.ts` (design/158 A12 定型半场), alongside its TiDB twin so the dialect deltas
4
+ * (placeholders, ON CONFLICT, `IS NOT DISTINCT FROM` + its placeholder-arity consequence, the lossless
5
+ * JSON envelope) read side by side instead of drifting across two files.
6
+ *
7
+ * This file is the historical import path: class name and ctor shape unchanged.
8
+ */
9
+ export { PgCheckpointStore } from "./checkpoint-store-sql.js";
10
+ //# sourceMappingURL=pg-checkpoint-store.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * PostgreSQL `CheckpointStore` — the implementation now lives in the single-file dual-dialect
3
+ * `checkpoint-store-sql.ts` (design/158 A12 定型半场), alongside its TiDB twin so the dialect deltas
4
+ * (placeholders, ON CONFLICT, `IS NOT DISTINCT FROM` + its placeholder-arity consequence, the lossless
5
+ * JSON envelope) read side by side instead of drifting across two files.
6
+ *
7
+ * This file is the historical import path: class name and ctor shape unchanged.
8
+ */
9
+ export { PgCheckpointStore } from "./checkpoint-store-sql.js";
10
+ //# sourceMappingURL=pg-checkpoint-store.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Thin re-export shim (design/158 A12 定型半场) — the real implementation collapsed into
3
+ * `file-snapshot-store-sql.ts` (single file, dual dialect). Kept so every existing import path
4
+ * (store-backend.ts, the migration script, the contract/integration/minio-equivalence suites) is
5
+ * untouched: same class name, same ctor arity, same exported surface.
6
+ */
7
+ export { PgFileSnapshotStore } from "./file-snapshot-store-sql.js";
8
+ //# sourceMappingURL=pg-file-snapshot-store.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Thin re-export shim (design/158 A12 定型半场) — the real implementation collapsed into
3
+ * `file-snapshot-store-sql.ts` (single file, dual dialect). Kept so every existing import path
4
+ * (store-backend.ts, the migration script, the contract/integration/minio-equivalence suites) is
5
+ * untouched: same class name, same ctor arity, same exported surface.
6
+ */
7
+ export { PgFileSnapshotStore } from "./file-snapshot-store-sql.js";
8
+ //# sourceMappingURL=pg-file-snapshot-store.js.map