From 018b2b4883eede47dae60f8860d92da0fac82f5e Mon Sep 17 00:00:00 2001 From: jesse-ux Date: Wed, 23 Sep 2026 18:51:08 +0800 Subject: [PATCH] fix(test): stream large report snapshot SQL Co-Authored-By: Claude Code --- BLOCKED.md | 6 ++ docs/BUG_HISTORY.md | 7 +- .../PROGRESS-report-density-fix2-20260923.md | 67 +++++++++++++++++++ docs/tasks/README.md | 2 +- .../database-personal-report-sections.test.ts | 4 +- ...database-postgres-fixture-contract.test.ts | 27 ++++++++ frontend/tests/helpers/postgres-fixture.ts | 32 +++++++++ .../tests/postgres-fixture-contract.test.ts | 9 +++ 8 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 docs/tasks/PROGRESS-report-density-fix2-20260923.md create mode 100644 frontend/tests/database-postgres-fixture-contract.test.ts diff --git a/BLOCKED.md b/BLOCKED.md index 21529a12..09fe1fad 100644 --- a/BLOCKED.md +++ b/BLOCKED.md @@ -6,6 +6,12 @@ - 本机 Docker 存在但地址池耗尽,不写成无 Docker,不清他人网络。Windows 完整构建在页面数据阶段因 Skill symlink EPERM 失败,Static/gzip 未完成。真实 Linux run 2856 的工具相关测试已核实通过,不能据旧 run 预写新 run 通过。 - 五宽度与选择器焦点使用隔离真实组件/新 Chrome profile,不是登录 E2E;没有借用账号。~~全量首次复验发现 starter-entry-soften 仍锁375的相关合同,须同步至480后再跑。~~ 已同步并复验通过,其他断言保留;进度见 `docs/tasks/PROGRESS-staging-gate-red-20260923.md`。 +## BUG-1011:本机 Docker 地址池耗尽,数据库测试待 Gitea Linux 门禁(2026-09-23) + +- Docker CLI/daemon 存在(Server `29.8.0`),但 `startPostgresFixture()` 建 compose 网络稳定报 `all predefined address pools have been fully subnetted`。本机未执行到 PostgreSQL SQL;未清理其他会话网络或卷。 +- `tests/database-postgres-fixture-contract.test.ts`、`tests/database-personal-report-sections.test.ts` 和 `npm run test:db` 因同一环境错误阻断,不能写成 helper、快照或数据库测试通过。最终证据交给推送后的 Gitea Linux gate。 +- 本机已替代验证:静态 fixture 合同 3/3、tsc 0 错、受影响文件 lint 0 error;全量前端 3,707 / 3,618 pass / 89 fail / 0 skip,失败清单含既有 Windows 路径、Docker、symlink EPERM 与既有断言,未据此宣称全绿。详见 `docs/tasks/PROGRESS-report-density-fix2-20260923.md`。 + ## 报告密度 BUG-1003~1007 / 1009 / 1010 验收缺口(2026-09-23) - 原实现 `bbd96d3b` 已提交并合入 staging;本轮从已 fetch 的 `0d37bec15` 工作树执行修复,尚无本轮交付或部署核验。不能继续把原实现写成尚未提交或未推送。进度:`docs/tasks/PROGRESS-report-density-20260922.md`。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index bf57e662..dfd79c59 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -13428,9 +13428,9 @@ ## BUG-1011 | 快照落库的数据库测试把 42 万字节 SQL 当一个命令行参数,Linux 上启动即失败 -- 状态:investigating(根因已复现,修复待执行;数据库层从未真跑过) +- 状态:investigating(修复已实现,真实数据库层仍待 Linux 门禁;本机 Docker 地址池耗尽) - 首次发现:2026-09-23 -- 最近更新:2026-09-23(门禁 run 2857 确认为唯一失败) +- 最近更新:2026-09-23(新增 stdin 单事务 helper;本机未进入 SQL) - 影响面:`frontend/tests/database-personal-report-sections.test.ts` 中报告密度快照一例;`backend-quality-gate` 的数据库测试步骤。生产写入走 RPC 请求体,不经命令行,不受影响。 - 用户现象:无用户界面现象。门禁在含该测试的提交上变红;报告快照行在真实 Postgres 里的写入、幂等与越权隔离从未被验证。 - 触发条件:在 Linux 上以 Docker 跑 `npm run test:db`(即门禁环境)。 @@ -13440,7 +13440,8 @@ - 防复发:超过数十 KB 的 SQL 一律走单事务的标准输入专用函数,并显式设 `maxBuffer`;专用函数须有测试覆盖超过 131,072 字节的 SQL,并证明事务内 `set_config(…, true)` 对后续语句可见。 - 相关记录:BUG-1005、BUG-1009 - 复发自:无 -- 修复版本:— +- 修复版本:待提交;候选改动在 `codex/report-density-fix2-20260923`,尚未推送 + ## BUG-1012 | 首页入口新增 375px 断点违反既有白名单 diff --git a/docs/tasks/PROGRESS-report-density-fix2-20260923.md b/docs/tasks/PROGRESS-report-density-fix2-20260923.md new file mode 100644 index 00000000..4928b540 --- /dev/null +++ b/docs/tasks/PROGRESS-report-density-fix2-20260923.md @@ -0,0 +1,67 @@ +# PROGRESS-report-density-fix2-20260923 · BUG-1011 快照 SQL E2BIG 修复 + +## 当前状态(2026-09-23) + +**实现已完成,真实数据库验证仍待 Gitea Linux 门禁。** 本轮从更正后的 `origin/staging` 基线 `451a58089a2eaa8c7e5ae1f0648199b008aa0dc2` 建立独立 worktree;没有修改旧的 `staging-gate-red-20260923` worktree。BUG-1011 尚不能标记 resolved。 + +| 项 | 事实 | +| --- | --- | +| 工作树 / 分支 | `.worktrees/report-density-fix2-20260923` / `codex/report-density-fix2-20260923` | +| 基线 | `451a58089a2eaa8c7e5ae1f0648199b008aa0dc2`(任务书更正提交) | +| 根因 | 约 428,557 字节的完整快照 SQL 被放进 `psql -c` 单个 argv;Linux `MAX_ARG_STRLEN` 约 131,072,门禁真实报 `spawnSync docker E2BIG` | +| 本机 Docker | CLI/daemon 存在,Server `29.8.0`;创建 compose 网络时报 `all predefined address pools have been fully subnetted`,未清理其他会话网络 | +| 远端门禁 | 前一 run 2857 / validate job 6347 唯一失败仍是本 BUG;本轮代码尚未推送,暂无新 run | +| 状态 | investigating;等待真实 PostgreSQL 快照测试和完整数据库套件证据 | + +## 实现与范围审计 + +### G1:专用 stdin 单事务 helper + +- `frontend/tests/helpers/postgres-fixture.ts` 新增 `psqlScriptAs(role, password, sql)`。 +- 既有 `psql` 与 `psqlAs` 的签名、argv、事务行为保持不变;未迁移其他数据库测试调用。 +- 新 helper 通过 stdin (`input: sql`) 传输 SQL,使用 `-X -At -v ON_ERROR_STOP=1 --single-transaction -f -`,并设置 `maxBuffer: 16 * 1024 * 1024`。 +- `database-personal-report-sections.test.ts` 仅把完整快照写入调用和大 JSON 读回调用切换到新 helper;其他 `psql` / `psqlAs` 调用保持原样。 +- 完整虚构 reader Markdown、engine packet、snapshot JSON 构造、哈希、幂等/首版保留、owner-read RLS 与越权断言均未裁剪、删除或弱化。 +- 新增 `database-postgres-fixture-contract.test.ts`:真实 fixture 路径构造 UTF-8 SQL 大于 131,072 字节,验证 `set_config(..., true)` 后续可见,并验证超过 1 MiB 的 stdout 能完整取回。这些运行时合同仍需可用 Docker 才能执行。 + +### G2:原值 / 新值 / 原因 + +| 原值 | 新值 | 原因 | +| --- | --- | --- | +| 完整快照写入使用 `fixture.psqlAs(...)`,SQL 作为单个 `-c` 参数 | 仅该大快照写入使用 `fixture.psqlScriptAs(...)`,SQL 经 stdin、整段单事务执行 | 避免 Linux `E2BIG`,同时保留 `set_config(..., true)` 所依赖的事务语义 | +| 完整快照读回使用默认 `fixture.psql(...)` 输出缓冲 | 该大 JSON 读回使用新 helper 的显式 16 MiB `maxBuffer` | 避免完整 payload 超过 Node 默认 1 MiB stdout 缓冲而产生 `ENOBUFS`;不改变断言 | +| 无大脚本运行时合同 | 新增超 argv 限制 SQL、事务本地设置可见性和大 stdout 合同 | 防止后续回退到 argv 传输或移除显式输出缓冲 | + +## 验证账本 + +| 检查 | 结果 | +| --- | --- | +| `./node_modules/.bin/tsc --noEmit` | 通过,0 错 | +| `npm run lint` | 通过,0 error;保留仓库既有 119 条 warning,未顺修无关 warning | +| 受影响文件 ESLint | 通过,0 error | +| `tests/postgres-fixture-contract.test.ts` | 2/2 通过(不启动 Docker) | +| `tests/database-postgres-fixture-contract.test.ts` | 失败于 `startPostgresFixture()` 创建网络:`all predefined address pools have been fully subnetted`;未执行 SQL,不是 helper 或产品断言失败 | +| `tests/database-personal-report-sections.test.ts` | 同一 Docker 网络创建错误;未执行迁移、快照写入、读回或 RLS 断言 | +| `npm run test:db` | 47 tests / 8 pass / 39 fail;数据库 fixture 均在 Docker 网络创建时报 `all predefined address pools have been fully subnetted`,未执行 BUG-1011 SQL;本机环境缺口 | +| `npm test` | 3,707 tests / 3,618 pass / 89 fail / 0 skipped;相对本轮此前记录的 3,704 名称基线新增 3 个合同测试,未发现 BUG-1011 引入的业务失败;失败含既有 Windows 路径、Docker 地址池、symlink EPERM 与既有断言,不能记全绿 | +| `npm run build -- --webpack` | 编译与 TypeScript 通过;页面数据阶段因 Windows Skill runtime symlink `EPERM` 在 `/api/daily-starlanguage` 失败,未取得 Static/gzip | +| `node --import tsx --test tests/postgres-fixture-contract.test.ts` | 3/3 通过(静态合同,不启动 Docker) | +| `node --import tsx --test tests/database-postgres-fixture-contract.test.ts` | 1 fail(Docker 地址池创建前阻断),2 个静态 fixture 合同通过;没有执行 SQL | +| `./node_modules/.bin/tsc --noEmit` | 通过,0 错 | +| 受影响文件 ESLint | 通过,0 error | +| `git diff --check` | 通过 | +| `git diff --check` | 已通过当前代码差异检查 | + +## 硬红线核对 + +- 未修改 `.gitea/workflows/**`、schema、RPC、migration、生产代码或共享 `psql` / `psqlAs`。 +- 未删除、skip 或弱化测试;完整快照 fixture 与断言保留。 +- 未使用 `git stash`、`reset`、`clean`、`rebase` 或强推。 +- 未读取或提交 `references/oracle/artifacts/pending_packets` 内容;未读取、猜测或借用凭据;未清理其他会话 Docker 资源。 +- staging 推送、Gitea 新 run、publish/deploy 与 health SHA 尚未发生,不能预写成功。 + +## 下一步 + +1. 完成本机可执行的静态、前端与构建验证,并如实记录环境失败。 +2. 仅提交本单允许文件;推送前 fetch 并核对远端 staging,若有并行推进则保留双方历史,不强推。 +3. 推送后读取 Gitea validate 日志。只有 `database-personal-report-sections.test.ts`、新增大脚本合同和全部 `tests/database-*.test.ts` 在 Linux runner 通过,且无其他失败,才把 BUG-1011 改为 resolved;否则保留 investigating 并记录原文。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index e0867bac..11b379a2 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -171,7 +171,7 @@ | --- | --- | --- | --- | --- | | `TASK-report-density-20260922.md` | `PROGRESS-report-density-20260922.md` | **报告信息密度与原始附录(BUG-1003~1007)**:与源头仓 PL9 全量报告同资料实测:对照 847,632 字符 / 8,044 表格行,我方 309,357 / 1,478。缺口不在引擎——20 张分盘全表、KP 四表、Ashtakavarga 四类、Shadbala 六分量、Avastha、Sahams、年度都已算得出,但 `personal-report-contract.ts` 的 `REPORT_SECTION_KINDS` **没有表格类型**、`CHART_IDS` 只有 9 种,这些表在产品页报告里没有落脚字段。另测出对外原始附录泄漏 925 种 / 3,797 处工程标识符(`parameter_sensitive` 697、`cmd_full_reading` 68、`PyJHora`/`JHora` 35、`PL9` 页码 59),对照物同项为 0。**产品 2026-09-22 三点拍板**:层 1 按 8 组表 + 分盘扩到 16 张;层 2 原始附录直接给 C 端且先全显示;B 类大运族要显示。**决策记录已写明推翻 BUG-999 的两条红线**(专业参考不得作普通下载 fallback;`parameter_sensitive`/「参数敏感」命中整行剔除)——**仅限附录通道**,普通正文投影不动。硬红线:表格层服务端装配、writer 不得写表;对照物的替代大运族日期与 Shadbala 分量在源头仍未闭环(506 行 date mismatch、`production_tuning_allowed=false`),不得照搬升级。让步顺序与串行依赖(任务 3 → 任务 4 同改契约)见任务书 | **验收未通过**(2026-09-23):任务 1/2/4/5 通过;任务 3 事实表在普通报告里显示 447 个引擎键路径(P1);隐私门新增失败(任务书自身写入真实出生资料 BUG-1008 已清 HEAD,fixture 数值碰撞待登记);当时 BUG-1003~1007、PROGRESS、testing 清单缺失;F3 现已补前两项,testing 新建受工具边界阻塞仍缺。Node 22 前端 0 新增失败、`/` Static、gzip +0.007% | 实现 `bbd96d3b`(原 `f968cb21`,09-23 为清除 BUG-1008 重写历史;已在 staging,未部署);修复单 `TASK-report-density-fix-20260923.md` | | `TASK-report-density-fix-20260923.md` | `PROGRESS-report-density-20260922.md` | **报告密度验收修复单(BUG-1009/1010 + 补写 1003~1007)**:F1 事实表改成真正的表(逐组规定列,普通报告可见格 0 个引擎键 / 0 个下标 / 最多 2 位小数,`sourcePath` 保留供回查;主运年数不得填出生剩余年数);F2 按既有机制登记 fixture 的数值碰撞(已独立重跑确认是虚构输入);F3 补 Bug 历史 / PROGRESS / 真人清单并更正「未提交推送」;F4 两份清洗规则只留一份(Python 版漏第三方页码);F5 writer 写表拦截补测试与提示;F6 事实表可打印。硬红线:不得弱化隐私扫描、不得强推或重写 staging(历史清除由产品负责人决定);Sade Sati 三轮日期是原任务书错误、不在本单 | **已验收,F1–F6 全部通过**(2026-09-23,`a12f2c0d`):事实表 130 行 / 600 格 0 引擎键 0 下标、主运年数 18、隐私门转绿、两份规则逐字节一致、写表拦截变异测试红→绿;Node 22 前端 3,748 / 26(名单与基线一致)、Python 948/0。剩数据库测试 E2BIG 另开 fix2(BUG-1011) | `a12f2c0d`(门禁 run 6294 红,未部署);真人清单 `docs/testing/report-density-20260922.md` | -| `TASK-report-density-fix2-20260923.md` | — | **快照数据库测试无法启动(BUG-1011)——staging 恢复部署前的唯一阻塞**:门禁 run 2857 为 3,762 / 1 fail,唯一失败即此。测试把 428,557 字节 SQL 作为单个 psql 参数,超过 Linux 131,072 上限 → `E2BIG`。**09-23 更正做法**:不得改共享 `psql`/`psqlAs`(29 文件 522 次调用依赖 `-c` 单事务语义,`selectAsAuthenticated` 的事务内 `set_config` 会失效);新增单事务 + 标准输入 + 显式 `maxBuffer` 的专用函数,只给本测试用。修通后若真实断言失败是产品缺陷,不得改断言 | **待领取** | — | +| `TASK-report-density-fix2-20260923.md` | `PROGRESS-report-density-fix2-20260923.md` | **快照数据库测试无法启动(BUG-1011)——staging 恢复部署前的唯一阻塞**:门禁 run 2857 为 3,762 / 1 fail,唯一失败即此。测试把 428,557 字节 SQL 作为单个 psql 参数,超过 Linux 131,072 上限 → `E2BIG`。**09-23 更正做法**:不得改共享 `psql`/`psqlAs`(29 文件 522 次调用依赖 `-c` 单事务语义,`selectAsAuthenticated` 的事务内 `set_config` 会失效);新增单事务 + 标准输入 + 显式 `maxBuffer` 的专用函数,只给本测试用。修通后若真实断言失败是产品缺陷,不得改断言 | **执行中** | `codex/report-density-fix2-20260923`(待推 staging) | | `TASK-report-sectioned-generation-20260830.md` | `PROGRESS-report-sectioned-20260830.md` | 分章节生成 | 已合入 | 见 PROGRESS | | `TASK-report-skill-parity-20260901.md` | `PROGRESS-report-skill-parity-20260901.md` | 内容对齐 skill 解读深度 | 已验收 | `90bad10d`、`ef1bd6df` | | `TASK-report-blocked-repairs-20260902.md` | `PROGRESS-report-blocked-repairs-20260902.md` | 全主题 blocked 修复 | 已验收 | `7faf8555` | diff --git a/frontend/tests/database-personal-report-sections.test.ts b/frontend/tests/database-personal-report-sections.test.ts index 937d1278..8a5df965 100644 --- a/frontend/tests/database-personal-report-sections.test.ts +++ b/frontend/tests/database-personal-report-sections.test.ts @@ -165,14 +165,14 @@ test("personal report sections enforce owner-read RLS and service-owned durable readerDashaApplicability: reader.reader_dasha_applicability, }, identity); const encoded = JSON.stringify(snapshot).replaceAll("'", "''"); - fixture.psqlAs("service_runtime", "service-runtime-test-password", serviceSql(` + fixture.psqlScriptAs("service_runtime", "service-runtime-test-password", serviceSql(` select status from public.ensure_personal_report_section('${USER_A}', '${REQUEST_ID}', '${LONGFORM_SNAPSHOT_SECTION_ID}', 2); select status from public.complete_personal_report_section('${USER_A}', '${REQUEST_ID}', '${LONGFORM_SNAPSHOT_SECTION_ID}', '${encoded}'::jsonb); -- Late ensure and conflicting completion must preserve the first committed snapshot. select status from public.ensure_personal_report_section('${USER_A}', '${REQUEST_ID}', '${LONGFORM_SNAPSHOT_SECTION_ID}', 2); select status from public.complete_personal_report_section('${USER_A}', '${REQUEST_ID}', '${LONGFORM_SNAPSHOT_SECTION_ID}', '{"kind":"stale-worker"}'::jsonb); `)); - const durable = JSON.parse(fixture.psql(`select payload from public.personal_report_sections where user_id = '${USER_A}' and request_id = '${REQUEST_ID}' and section_id = '${LONGFORM_SNAPSHOT_SECTION_ID}'`)); + const durable = JSON.parse(fixture.psqlScriptAs("postgres", "postgres-test-password", `select payload from public.personal_report_sections where user_id = '${USER_A}' and request_id = '${REQUEST_ID}' and section_id = '${LONGFORM_SNAPSHOT_SECTION_ID}'`)); assert.deepEqual(validateLongformSnapshot(durable, identity), snapshot); assert.equal(fixture.psqlAs("app_runtime", "app-runtime-test-password", selectAsAuthenticated(USER_B, `select count(*) from public.personal_report_sections where section_id = '${LONGFORM_SNAPSHOT_SECTION_ID}'`)), `SET\n${USER_B}\n0`); } finally { diff --git a/frontend/tests/database-postgres-fixture-contract.test.ts b/frontend/tests/database-postgres-fixture-contract.test.ts new file mode 100644 index 00000000..6a35e550 --- /dev/null +++ b/frontend/tests/database-postgres-fixture-contract.test.ts @@ -0,0 +1,27 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { startPostgresFixture } from "./helpers/postgres-fixture.ts"; + +test("stdin PostgreSQL scripts carry large SQL and preserve transaction-local settings", () => { + const fixture = startPostgresFixture(); + try { + const payload = "x".repeat(131_073); + const script = ` + select set_config('app.test_large_script', 'stdin-helper', true); + select current_setting('app.test_large_script'); + select length($$${payload}$$); + `; + assert.equal(Buffer.byteLength(script, "utf8") > 131_072, true); + assert.equal( + fixture.psqlScriptAs("postgres", "postgres-test-password", script), + "stdin-helper\nstdin-helper\n131073", + ); + assert.equal( + fixture.psqlScriptAs("postgres", "postgres-test-password", "select repeat('x', 1048577);"), + "x".repeat(1_048_577), + ); + } finally { + fixture.stop(); + } +}); diff --git a/frontend/tests/helpers/postgres-fixture.ts b/frontend/tests/helpers/postgres-fixture.ts index 2b722111..7276bb7a 100644 --- a/frontend/tests/helpers/postgres-fixture.ts +++ b/frontend/tests/helpers/postgres-fixture.ts @@ -17,6 +17,7 @@ export type PostgresFixture = { connectionUrl(role: string, password: string): string; psql(sql: string): string; psqlAs(role: string, password: string, sql: string): string; + psqlScriptAs(role: string, password: string, sql: string): string; stop(): void; }; @@ -270,6 +271,37 @@ export function startPostgresFixture(): PostgresFixture { { encoding: "utf8", env: environment }, ).trim(); }, + psqlScriptAs(role, password, sql) { + return execFileSync( + "docker", + [ + ...composeArguments, + "exec", + "-T", + "-e", + `PGPASSWORD=${password}`, + "postgres", + "psql", + "-X", + "-At", + "-v", + "ON_ERROR_STOP=1", + "--single-transaction", + "-f", + "-", + "-U", + role, + "-d", + "jyotisha", + ], + { + encoding: "utf8", + env: environment, + input: sql, + maxBuffer: 16 * 1024 * 1024, + }, + ).trim(); + }, stop() { try { execFileSync( diff --git a/frontend/tests/postgres-fixture-contract.test.ts b/frontend/tests/postgres-fixture-contract.test.ts index 74acbf7c..5ce7b411 100644 --- a/frontend/tests/postgres-fixture-contract.test.ts +++ b/frontend/tests/postgres-fixture-contract.test.ts @@ -33,3 +33,12 @@ test("postgres fixture start failure and stop both release the compose-network s /stop\(\) \{[\s\S]*releasePort\(portReservation\)[\s\S]*slot\.release\(\)/, ); }); + +test("large PostgreSQL scripts use stdin without changing shared -c helpers", () => { + assert.match(fixtureSource, /psqlScriptAs\(role, password, sql\)/); + assert.match(fixtureSource, /psqlScriptAs[\s\S]*input: sql/); + assert.match(fixtureSource, /psqlScriptAs[\s\S]*maxBuffer: 16 \* 1024 \* 1024/); + assert.match(fixtureSource, /psqlScriptAs[\s\S]*"--single-transaction"[\s\S]*"-f"[\s\S]*"-"/); + assert.match(fixtureSource, /psql\(sql\)[\s\S]*"-Atc",\s*sql/); + assert.match(fixtureSource, /psqlAs\(role, password, sql\)[\s\S]*"-Atc",\s*sql/); +});