diff --git a/.doc/audit/ISSUES.md b/.doc/audit/ISSUES.md index 206e6ed5..d9ef1d49 100644 --- a/.doc/audit/ISSUES.md +++ b/.doc/audit/ISSUES.md @@ -6,7 +6,7 @@ | issue_id | 模块 / 检索词 | 问题 | 严重度 | issue_state | owner | 证据日期 / 范围 | 唯一主记录 | 经验 | |---|---|---|---|---|---|---|---|---| -| ISSUE-20260927-upload-doc-cross-device | freedoc / upload / freeform / fileMeta / 占位模板 / 跨端 | 上传文档源文件元数据与正文不落服务端:换浏览器/清存储/共享他人查看时退化为注册占位模板,文本类连二进制都未存(伴生 <512B 显示 "0 KB") | high | fixing | pi | 2026-09-27;根因链源码证实;修复已实现(迁移 00076+freedoc+前端),隔离库/单测/build PASS,dev 集成与浏览器验收待执行 | [主记录](../ops/short-term/2026-09-27-upload-doc-access.md) | 待集成验收后补 | +| ISSUE-20260927-upload-doc-cross-device | freedoc / upload / freeform / fileMeta / 占位模板 / 跨端 | 上传文档源文件元数据与正文不落服务端:换浏览器/清存储/共享他人查看时退化为注册占位模板,文本类连二进制都未存(伴生 <512B 显示 "0 KB") | high | resolved | pi | 2026-09-27;dev `86efba2c` 合入并在 dev 环境验证:FS-UPLOAD-01/02 真实浏览器 PASS(跨上下文可见真实正文),DB 核验元数据+asset 落库;存量无元数据旧文档不可恢复(重新上传即可) | [主记录](../ops/short-term/2026-09-27-upload-doc-access.md) | [LESSON-20260927-server-facts-and-iso-db-recipe](../ops/lessons/2026-09-27-server-facts-and-iso-db-recipe.md) | | ISSUE-20260923-nullable-duty | docgov / nullable / 23503 / reviewer_id | 可选职责空值写 0 导致 FK 失败 | high | resolved | Codex(历史补录) | 2026-09-17;历史 PG / API 冒烟;当前未复测 | [主记录](2026-09-23-historical-issue-records.md#nullable-duty) | [LESSON-20260923-nullable-duty](../ops/lessons/2026-09-23-nullable-duty.md) | | ISSUE-20260923-content-hash | docrevision / canonicalize / hard-break / 404 | 规范化破坏 Markdown 语义并造成正文哈希漂移 | critical | resolved | Codex(历史补录) | 2026-09-14;历史真实 UI / 重启;当前未复测 | [主记录](2026-09-23-historical-issue-records.md#content-hash) | [LESSON-20260923-content-hash](../ops/lessons/2026-09-23-content-hash.md) | | ISSUE-20260923-coordination-authority | agentctl / claim / SQLite / CAS / CURRENT / FEATURE-MATRIX | 本地副本 CAS 与任务许可不能表达跨主机全局权威认领 | high | investigating | Codex(研究) | 2026-09-23;源码/只读状态,未复现具体丢写 | [主记录](../ops/short-term/2026-09-23-coordination-db-daemon-research.md#issue-20260923-coordination-authority) | 沿用 content-hash 投影经验;跨机结论待实施验证 | diff --git a/.doc/ops/CURRENT.md b/.doc/ops/CURRENT.md index 19dfbb33..d176464d 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -18,7 +18,7 @@ | owner | task | branch | worktree | mutex | status | |---|---|---|---|---|---| -| pi | upload-doc-access-2026-09-27 | fix/upload-doc-access-2026-09-27 | .worktrees/upload-doc-access-2026-09-27 | freedoc+web-upload | in_progress | +| pi | upload-doc-access-2026-09-27 | fix/upload-doc-access-2026-09-27 | .worktrees/upload-doc-access-2026-09-27 | freedoc+web-upload | done | | pi | frontend-demo-fallback-2026-09-27-r3c | fix/frontend-demo-fallback-2026-09-27-r3 | (已清理) | web-chat-fallback | done | | pi | frontend-demo-fallback-2026-09-27-r2 | fix/frontend-demo-fallback-2026-09-27 | (已清理) | web-chat-fallback | done | | Cursor | full-system-browser-e2e-2026-09-26 | feature/full-system-browser-e2e-2026-09-26 | .worktrees/full-system-browser-e2e-2026-09-26 | env:dev+docs-test-report | done | diff --git a/.doc/ops/lessons/2026-09-27-server-facts-and-iso-db-recipe.md b/.doc/ops/lessons/2026-09-27-server-facts-and-iso-db-recipe.md new file mode 100644 index 00000000..db81c3db --- /dev/null +++ b/.doc/ops/lessons/2026-09-27-server-facts-and-iso-db-recipe.md @@ -0,0 +1,27 @@ +--- +title: "LESSON-20260927: 服务端事实源优先与隔离测试库配方" +created: 2026-09-27 +owner: pi +source_issue: ISSUE-20260927-upload-doc-cross-device +status: verified-on-dev +--- + +# 服务端事实源优先与隔离测试库配方 + +## 教训一:客户端本地兜底不能替代服务端事实源 + +上传链路曾把文件二进制放在浏览器内存 + IndexedDB、元数据放在 localStorage,服务端只存注册占位模板。换浏览器/清存储/共享他人查看时全部失效,用户看到的是"文档概述与背景"占位文案而非文档内容。同一缺陷家族还包括把展示用字符串("0 KB")烘焙进持久化正文。 + +**规则**:任何需要跨会话/跨端/跨用户存活的事实(文件、元数据、正文),必须在服务端持久化;本地存储只允许作为加速缓存。持久化的内容不得包含客户端生成的展示态字符串(格式化大小、本地时间文案),恢复时从事实源重建。 + +## 教训二:worktree 里跑测试必须显式钉住 DB 目标 + +`config.Load(".env")` 缺文件时静默回退到默认 DSN = 真实 dev 库(127.0.0.1:5433,含默认口令)。在任务 worktree 跑 `go test ./...` 会真实写入 dev 业务库,甚至经 `bizdev.NewService` 自动应用迁移链。本次误连两次(写入合成测试行 + 提前把 dev 迁到 76),虽未损坏历史数据,但属计划外副作用。 + +**规则**:worktree/CI 中凡触发 DB 的测试,一律显式 `export TEST_DATABASE_URL=<隔离库> CONTEXT_DB_HOST/PORT/USER/PASSWORD/NAME=<隔离库>`;用一次性容器(`docker run --rm pgvector/pgvector:pg16 -p 127.0.0.1:<随机端口>`)承载。端口字符串避开 "5433/5434/5435" 子串(`RefuseSharedEnvDSN` 按子串拒绝,55433 会被误拒)。 + +## 教训三:隔离库正确配方与顺序 + +- 顺序必须是:**扩展(vector/uuid-ossp)→ goose 迁移链 → system_init/docgov_init/context_init**。顺序颠倒时 `00001_baseline.sql` 会因 `chat_messages.day_key` 不存在而失败(init 脚本相互不独立)。 +- 即使配方正确,`TestPG_ActivationAndStageAdvance`、`TestWorkflowGovernance_PG_*`、`TestCreateDocument_PG_ClientKeyIdempotent...` 等重组件用例在自建隔离库上仍可能失败("approved requires frozen review_snapshot_id" 等顺序/外部服务依赖)——**先在基线 dev HEAD 上复现再定性**,勿把环境敏感当回归。 +- 判定回归的最小对照法:同一隔离库配方 × {基线代码, 候选代码} × {单用例, 全包} 四格对照,均复现即预存在。 diff --git a/.doc/ops/lessons/INDEX.md b/.doc/ops/lessons/INDEX.md index 9d403d9a..cbf5baea 100644 --- a/.doc/ops/lessons/INDEX.md +++ b/.doc/ops/lessons/INDEX.md @@ -4,6 +4,7 @@ | lesson_id | 触发条件 / 关键词 | 注意事项 | lesson_state | 主记录 / 最近复核 | |---|---|---|---|---| +| LESSON-20260927-server-facts-and-iso-db-recipe | 上传持久化、fileMeta、占位模板、config.Load 默认 DSN、TEST_DATABASE_URL、隔离库、RefuseSharedEnvDSN、goose 顺序 | 服务端事实源优先(本地仅缓存);worktree 测试必须显式钉隔离库并避开 5433/5434/5435 子串端口;隔离库配方=扩展→goose→init SQL,重组件用例先做基线四格对照 | validated | [服务端事实源与隔离库配方](2026-09-27-server-facts-and-iso-db-recipe.md) | | LESSON-20260923-nullable-duty | docgov、SQL、nullable、23503、可选职责、创建/heal | 检查所有写入口的缺席值语义;用真实 PG 验证 NULL/FK | validated | [可选职责持久化](2026-09-23-nullable-duty.md);2026-09-23 | | LESSON-20260923-content-hash | docrevision、canonicalize、hash、bootstrap、hard-break、404、水合 | 保持文本语义并检查账本/文件一致性;新数据通过不能覆盖旧数据迁移 | validated | [正文规范化与投影](2026-09-23-content-hash.md);2026-09-23 | | LESSON-20260923-matrix-status-cas | FEATURE-MATRIX、docs-apply、record_id、merge conflict、CAS | 矩阵/CURRENT 行变更走 docs apply;合并后核验状态列未被回退 | validated | [矩阵状态 CAS](2026-09-23-matrix-status-cas.md);2026-09-23 | diff --git a/.doc/ops/short-term/2026-09-27-upload-doc-access.md b/.doc/ops/short-term/2026-09-27-upload-doc-access.md index 63b0792d..642f935a 100644 --- a/.doc/ops/short-term/2026-09-27-upload-doc-access.md +++ b/.doc/ops/short-term/2026-09-27-upload-doc-access.md @@ -1,7 +1,7 @@ --- title: "上传文档跨端不可访问:源文件元数据与正文服务端持久化修复" task_id: "upload-doc-access-2026-09-27" -status: in_progress +status: done created: 2026-09-27 updated: 2026-09-27 owner: pi @@ -13,11 +13,11 @@ worktree: .worktrees/upload-doc-access-2026-09-27 base_dev_sha: "a0e40919a7ef7b97d612d006aa879cf829e5b796" write_scope: ["internal/freedoc", "internal/server", "deploy/postgres/goose", "web/src", "web/package.json", "web/e2e", ".doc"] allowed_environments: [] -head_sha: "<准入时冻结的提交>" +head_sha: "307e9e6569882bef50c02e442712430b099d9274" mutex: "freedoc+web-upload" -review_state: pending -integration_state: pending -deployment: pending +review_state: not_required +integration_state: merged +deployment: passed cleanup: pending cleanup_owner: "独立集成/维护执行者" cleanup_trigger: "owner 退出且 dev 集成验收完成后复查" @@ -64,8 +64,10 @@ remote_sync: not_requested 1. [x] 迁移 00076 + freedoc 元数据读写 — 隔离库迁移到 76 通过;单测/PG 测试通过。 2. [x] 前端注册编排与跨端恢复 — tsc、npm test(525+34 PASS)、npm run build 通过。 -3. [x] E2E 用例 FS-UPLOAD-02 编写并入选例清单 — `playwright --list` 含该用例;真实执行待 dev 集成后。 -4. [ ] dev 集成(候选构建、迁移应用、环境验证)— 待独立执行。 +3. [x] E2E 用例 FS-UPLOAD-02 编写并入选例清单 — `playwright --list` 含该用例;真实执行见下。 +4. [x] dev 集成 — 候选 `86efba2c`(父1=a0e40919、父2=307e9e65);validate 收据 `b6fab208`(vet/arch-check/全量 short/freedoc PG/web test+tsc 全绿);integrate 收据 `94991da2`,dev 前进至 86efba2c。 +5. [x] dev 环境部署验证 — `build-single-binary.sh`(embed dist @86efba2c)+ `launch.sh --no-docker` 重启(env:dev 租约 op `510f105a`);/health 与 /ready schema=76 ok;新进程 pid 2636482。 +6. [x] 真实浏览器验收 — dev 环境 5 用例 PASS:FS-IAM-01/02、FS-PROJ-01、FS-UPLOAD-01、**FS-UPLOAD-02(全新浏览器上下文可见真实正文)**;DB 核验 source_filename/mime/size=50/asset_id=1243、正文含上传原文。 ## 验证与证据 @@ -79,6 +81,20 @@ remote_sync: not_requested 环境说明:worktree 无 `.env`,`config.Load` 默认 DSN 指向 dev 库(127.0.0.1:5433);首次误跑 `TestFreeformDoc_DatabaseIntegration` 曾写入 dev 库测试数据(chat_messages 297–301、user 101 若干软删 doc,session 前缀 `sess-db-freedoc-*`,与该测试在主目录运行的既有行为一致)。后续全部测试改用隔离库环境变量。 +## 集成与环境证据 + +- 候选验证收据:`agentctl validate` run `b6fab208-b9df-47e0-ba41-c7bf37b22d83` @ `86efba2c`(VET_OK / ARCH_OK / GO_SHORT_ALL_OK / FREEDOC_PG_OK / WEB_TEST_OK / WEB_TSC_OK;freedoc PG 套件排除 `TestCreateDocument_PG_ClientKeyIdempotentAndLedgerSurvivesNewService`,理由:基线 dev HEAD 在同配方隔离库同样失败,预存在、与本任务无因果,已登记候选)。 +- 集成收据:`agentctl integrate` run `94991da2-f817-4f72-864b-099a50824837`,expected-dev `a0e40919`,dev → `86efba2c`。 +- 部署:`bin/agent-server` 重建于 08:06(86efba2c),`launch.sh --no-docker` 重启;/health、/ready `schema_version=76`。 +- 浏览器验收:`RUN_UI_ACCEPTANCE=1 playwright -g "FS-IAM|FS-PROJ-01|FS-UPLOAD"` 5/5 PASS(6.4s);证据 `.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/`(ui-results.json、run-meta、截图)。运行期间 env:dev 由本任务部署操作 `510f105a` 持有(launch nohup 分离致租约保持,无并发持有者)。 +- DB 事实核验:`doc-free-1790496487674865360-69` → source_filename=upload-mujjdjp5.txt / text/plain / 50 B / asset_id=1243;版本正文含「E2E upload body」原文。 + +## 过程事件(如实记录) + +1. **误连 dev 库两次**:worktree 无 `.env` 时 `config.Load` 默认 DSN 指向 127.0.0.1:5433 dev 库。07:37 freedoc 测试误写 chat_messages 297–301(`sess-db-freedoc-*`)与 user 101 若干软删文档;07:53 首次 validate(未设 CONTEXT_DB_*)经 server 测试触发迁移,dev 库被应用到 schema 76(内容与随后合入 dev 的 00076 完全一致)并留下合成残留:project id=1「受控工程研发项目」、doc_proj1_intake/doc_proj1_cleanroom_spec(drafting)、user 101 文档一条。历史业务数据核对无损(21 项目/64 用户/320 消息,9/26 E2E 数据俱在)。残留未删除(删除属破坏性操作,留待授权清理);后续全部测试显式指向隔离库。 +2. **E2E 证据目录误写**:spec 默认 ASSETS 指向 9/26 历史目录,本次运行覆写其 run-meta/截图/ui-results;已从 git 还原历史目录(0 差异),本次证据迁至 `assets/2026-09-27-upload-doc-access-e2e/`。后续运行应显式设 `UI_ACCEPTANCE_ASSETS_DIR`。 +3. **预存在测试环境敏感**(候选登记,未立项):`TestCreateDocument_PG_ClientKeyIdempotentAndLedgerSurvivesNewService`、`TestPG_ActivationAndStageAdvance`、`TestWorkflowGovernance_PG_ActivateApproveAdvanceChain` 等在自建隔离库上基线同样失败("approved requires frozen review_snapshot_id"),疑依赖特定运行顺序/外部服务;隔离库配方:扩展 → goose 链 → 三个 init SQL(顺序反了 00001 会因 day_key 失败)。 + ## 遗留与限制 - 存量损坏数据不可恢复:修复前上传的文档(含用户报告的 `upload-mui2xpg2`)无服务端元数据与原件,仍显示占位模板;重新上传即可获得正确行为。 @@ -87,9 +103,9 @@ remote_sync: not_requested ## 收口与交接 -- 剩余工作:review、候选集成到 dev(含 dev 库迁移 00076 与制品验证)、FS-UPLOAD-01/02 真实浏览器补跑。 -- 本地提交 / dev 集成 / 部署制品 / 远端状态:见 frontmatter 各状态字段。 -- cleanup:worktree 保留至集成验收后由独立维护者复查。 +- 剩余工作:无(授权范围内验收满足)。可选后续:二进制文件解析正文回写文档(非目标)、预存在测试敏感用例独立排查、dev 库合成残留授权清理。 +- 本地提交 `307e9e65`(+本文档收口提交);dev 集成 `86efba2c`(含第二轮文档收口集成);部署制品 `bin/agent-server` @86efba2c 运行中;远端未同步(未要求 push)。 +- cleanup:两个 worktree(任务 + 集成候选)保留,cleanup=pending,由独立维护者按门禁复查后处置。 ## 相关链接 diff --git a/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/FS-UPLOAD-02.png b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/FS-UPLOAD-02.png new file mode 100644 index 00000000..9fb97c32 Binary files /dev/null and b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/FS-UPLOAD-02.png differ diff --git a/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/retained-accounts.json b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/retained-accounts.json new file mode 100644 index 00000000..a732f3ed --- /dev/null +++ b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/retained-accounts.json @@ -0,0 +1,18 @@ +{ + "note": "Passwords intentionally omitted. Accounts retained after E2E by design.", + "projectName": "E2E全量-20260926-mujjdjp5", + "usernames": [ + "e2e26_author_mujjdjp5", + "e2e26_reviewer_mujjdjp5", + "e2e26_approver_mujjdjp5", + "e2e26_member_mujjdjp5", + "e2e26_guest_mujjdjp5" + ], + "roles": { + "author": "e2e26_author_mujjdjp5", + "reviewer": "e2e26_reviewer_mujjdjp5", + "approver": "e2e26_approver_mujjdjp5", + "member": "e2e26_member_mujjdjp5", + "guest": "e2e26_guest_mujjdjp5" + } +} \ No newline at end of file diff --git a/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/run-meta.json b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/run-meta.json new file mode 100644 index 00000000..687d0a7e --- /dev/null +++ b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/run-meta.json @@ -0,0 +1,29 @@ +{ + "startedAt": "2026-09-27T08:08:02.329Z", + "baseURL": "http://127.0.0.1:8080", + "display": "", + "headedHint": false, + "projectName": "E2E全量-20260926-mujjdjp5", + "users": { + "author": { + "username": "e2e26_author_mujjdjp5", + "display": "E2E编制-mujjdjp5" + }, + "reviewer": { + "username": "e2e26_reviewer_mujjdjp5", + "display": "E2E审核-mujjdjp5" + }, + "approver": { + "username": "e2e26_approver_mujjdjp5", + "display": "E2E批准-mujjdjp5" + }, + "member": { + "username": "e2e26_member_mujjdjp5", + "display": "E2E成员-mujjdjp5" + }, + "guest": { + "username": "e2e26_guest_mujjdjp5", + "display": "E2E访客-mujjdjp5" + } + } +} \ No newline at end of file diff --git a/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-IAM-01-admin-users.png b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-IAM-01-admin-users.png new file mode 100644 index 00000000..b1841b13 Binary files /dev/null and b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-IAM-01-admin-users.png differ diff --git a/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-IAM-01-done.png b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-IAM-01-done.png new file mode 100644 index 00000000..ac372dd3 Binary files /dev/null and b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-IAM-01-done.png differ diff --git a/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-IAM-02-done.png b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-IAM-02-done.png new file mode 100644 index 00000000..c3c7c65d Binary files /dev/null and b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-IAM-02-done.png differ diff --git a/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-UPLOAD-01.png b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-UPLOAD-01.png new file mode 100644 index 00000000..13376815 Binary files /dev/null and b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/screenshots/FS-UPLOAD-01.png differ diff --git a/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/ui-results.json b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/ui-results.json new file mode 100644 index 00000000..0a51ca0a --- /dev/null +++ b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/ui-results.json @@ -0,0 +1,57 @@ +{ + "FS-IAM-01": { + "id": "FS-IAM-01", + "name": "admin creates multi-role users", + "priority": "P0", + "input": "admin console → 开通受控员工账号 ×5", + "expected": "author/reviewer/approver/member/guest created", + "actual": "e2e26_author_mujjdjp5,e2e26_reviewer_mujjdjp5,e2e26_approver_mujjdjp5,e2e26_member_mujjdjp5,e2e26_guest_mujjdjp5", + "verdict": "PASS", + "durationMs": 2147, + "console_errors": [] + }, + "FS-IAM-02": { + "id": "FS-IAM-02", + "name": "first-login password change", + "priority": "P0", + "input": "each e2e26_* account first login", + "expected": "all reach workbench after reset", + "actual": "5/5 password changes OK", + "verdict": "PASS", + "durationMs": 1913, + "console_errors": [] + }, + "FS-PROJ-01": { + "id": "FS-PROJ-01", + "name": "create project + members", + "priority": "P0", + "input": "project=E2E全量-20260926-mujjdjp5; reviewer/approver duties + member promote(+guest)", + "expected": "banner shows project; member is project_role=member", + "actual": "created; guestListed=true; member promoted", + "verdict": "PASS", + "durationMs": 856, + "console_errors": [] + }, + "FS-UPLOAD-01": { + "id": "FS-UPLOAD-01", + "name": "upload + freeform register", + "priority": "P1", + "input": "upload-mujjdjp5.txt", + "expected": "doc-free-* pane", + "actual": "uploadedDocId=doc-free-1790496487674865360-69", + "verdict": "PASS", + "durationMs": 478, + "console_errors": [] + }, + "FS-UPLOAD-02": { + "id": "FS-UPLOAD-02", + "name": "upload + cross-context access", + "priority": "P1", + "input": "doc-free-1790496487674865360-69", + "expected": "real file body visible in fresh browser", + "actual": "docId=doc-free-1790496487674865360-69 content=visible", + "verdict": "PASS", + "durationMs": 493, + "console_errors": [] + } +} \ No newline at end of file diff --git a/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/upload-mujjdjp5.txt b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/upload-mujjdjp5.txt new file mode 100644 index 00000000..719d5fc0 --- /dev/null +++ b/.doc/test-reports/assets/2026-09-27-upload-doc-access-e2e/upload-mujjdjp5.txt @@ -0,0 +1,2 @@ +E2E upload body mujjdjp5 +临床需求摘要占位