Files
Agent-System/.doc/ops/short-term/2026-09-27-upload-doc-access.md

11 KiB
Raw Permalink Blame History

title, task_id, status, created, updated, owner, task_type, task_tier, policy_revision, branch, worktree, base_dev_sha, write_scope, allowed_environments, head_sha, mutex, review_state, integration_state, deployment, cleanup, cleanup_owner, cleanup_trigger, remote_sync
title task_id status created updated owner task_type task_tier policy_revision branch worktree base_dev_sha write_scope allowed_environments head_sha mutex review_state integration_state deployment cleanup cleanup_owner cleanup_trigger remote_sync
上传文档跨端不可访问:源文件元数据与正文服务端持久化修复 upload-doc-access-2026-09-27 done 2026-09-27 2026-09-27 pi implementation small agent-governance-2026-09-21-r1;dev a0e40919a7 fix/upload-doc-access-2026-09-27 .worktrees/upload-doc-access-2026-09-27 a0e40919a7
internal/freedoc
internal/server
deploy/postgres/goose
web/src
web/package.json
web/e2e
.doc
307e9e6569 freedoc+web-upload not_required merged passed pending 独立集成/维护执行者 owner 退出且 dev 集成验收完成后复查 not_requested

上传文档跨端不可访问:源文件元数据与正文服务端持久化修复

用户目标与授权

  • 用户目标:登录测试用户账户后发现上传的私密文档无法访问,查看时显示注册占位模板(upload-mui2xpg2 / 上传人 E2E编制-mui2xpg2 / 文件大小 0 KB / 文档概述与背景…),此前人工测试亦大量出现;要求定位并修复。
  • 授权来源:用户在本会话中对「开任务 worktree 实施修复(方案 1+2+3 一起做)」回复「授权」。
  • 交付终点:代码与迁移在任务 worktree 完成并通过验证;dev 集成与环境验证按门禁另行执行(本任务记录集成状态)。
  • 禁止项:默认不 push;不操作 test/main/共享 KB;分析阶段不含业务实施(已进入实施)。

根因(已证实)

  1. 上传注册正文是占位模板:web/src/lib/uploadFreeformRegister.ts 的 buildUploadRegistrationMarkdown 生成的模板是服务端唯一持久化的"文档内容"。
  2. 文件二进制不落服务端(文本类):useSendMessage 仅对非 TEXT_EXTS 扩展名调用 uploadProjectAsset 登记 CAS;txt/md/csv/json 等只存浏览器 IndexedDB + 内存 fileRaw。非文本文件虽入 CAS,但 MinerU 解析结果只进知识库,不回写文档正文。
  3. 元数据不落服务端:freeform_documents 表无文件名/大小/mime/asset_id 列;useServerDocumentSync 恢复的 deliverable 无 sourceType/fileMeta,DocumentPane 的 isUploadedFile 启发式五条件全部不命中 → 走普通 AST 编辑器 → fetchFreeformDoc 渲染占位模板。触发条件:换浏览器/换设备/清存储/其他成员查看共享文档。
  4. "0 KB":InputArea 用 (file.size/1024).toFixed(0),<512B 文件四舍五入为 0,且被烘焙进持久化占位 markdown(E2E txt 仅约 41 字节)。
  5. E2E 盲区:FS-UPLOAD-01 只断言注册出 doc-free-* pane,不校验跨会话内容可见性。

方案

  • 迁移 00076:freeform_documents 增加 source_filename/source_mime_type/source_size_bytes/source_asset_id(软关联 CAS file_references,不加 FK)。
  • freedoc:Create/Get/List 读写上述列;负值参数拒绝(ErrInvalidParam);Summary 带出元数据。
  • 前端上传编排(useSendMessage):注册前先 CAS 登记全部附件(去掉 TEXT_EXTS 排除)并预读文本正文;注册时携带元数据与 asset_id;文本正文嵌入注册 markdown(## 1. 文档正文(上传原文))。
  • 跨端恢复(useServerDocumentSync → 新 lib/freeformDocSync.ts):按服务端 summary 恢复 sourceType: 'uploaded_file' 与 fileMeta(含 assetId/sizeBytes),DocumentPane 路由回 FilePreviewPanel,经 CAS raw 端点恢复原件预览。
  • 大小显示:新 lib/fileSize.ts formatFileSize(B/KB/MB,任何正数不显示 0)。
  • E2E:新增 FS-UPLOAD-02(全新浏览器上下文登录同账户,断言真实文件正文可见且占位句消失)。

范围与非目标

  • 允许写入:internal/freedoc、internal/server(无需改动,请求透传)、deploy/postgres/goose、web/src、web/e2e、.doc。
  • 非目标:MinerU 解析结果回写文档正文(二进制文件正文仍为占位+原件预览,属后续增强);历史已损坏数据(既无元数据也无 CAS 原件的旧上传文档)不可恢复,仅保持现状。

相关问题与经验

  • 开工检索:ISSUES.md 检索「上传/upload」,命中 ISSUE-20260924-non-author-400-duty(权限问题,机制无关,未采纳);lessons INDEX 无上传链路相关命中。
  • 待验证候选(本次发现、非本任务因果链):TestCreateDocument_PG_ClientKeyIdempotentAndLedgerSurvivesNewService 在全新隔离库(goose 76 + 全部 init SQL)上于基线 dev HEAD 同样失败(rev2 跨服务水合 HeadRevisionNo=0),疑似 docrevision 水合依赖未满足或用例自身问题;需独立排查后按闭环规范登记。

步骤与验收

  1. 迁移 00076 + freedoc 元数据读写 — 隔离库迁移到 76 通过;单测/PG 测试通过。
  2. 前端注册编排与跨端恢复 — tsc、npm test(525+34 PASS)、npm run build 通过。
  3. E2E 用例 FS-UPLOAD-02 编写并入选例清单 — playwright --list 含该用例;真实执行见下。
  4. dev 集成 — 候选 86efba2c(父1=a0e40919、父2=307e9e65);validate 收据 b6fab208(vet/arch-check/全量 short/freedoc PG/web test+tsc 全绿);integrate 收据 94991da2,dev 前进至 86efba2c。
  5. dev 环境部署验证 — build-single-binary.sh(embed dist @86efba2c)+ launch.sh --no-docker 重启(env:dev 租约 op 510f105a);/health 与 /ready schema=76 ok;新进程 pid 2636482。
  6. 真实浏览器验收 — 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、正文含上传原文。

验证与证据

范围 / 被测 SHA 命令与环境 预期 实际 / 判定 证据 / 脱敏说明
Go 迁移链(worktree HEAD) 隔离 PG(docker pgvector:pg16,127.0.0.1:5599,一次性容器)go run ./.tmp/migrate <dsn> + system/docgov/context_init.sql 迁移至 76 无错 PASS(schema_version: 76) 容器用后即删;DSN 仅本机随机端口
freedoc 单元+PG TEST_DATABASE_URL=<隔离库> go test ./internal/freedoc/ -count=1 -v 全 PASS 除下方预存在失败外全 PASS TestCreateDocument_PG_ClientKeyIdempotentAndLedgerSurvivesNewService 失败:基线 dev HEAD 在同一隔离库同样失败,判定预存在、与本任务无因果(见"相关问题")
Go 全量门禁 go vet ./...;./scripts/arch-check.sh --mode block;go test -short ./...(TEST_DATABASE_URL/CONTEXT_DB_* 指向隔离库) 全绿 vet 无输出;arch-check "production gate passed";short 69 包 ok,仅上述预存在失败 —
前端 npm test;npm run build(web/) 全 PASS 525(node:test)+34(vitest)PASS;build 成功 —
E2E npx playwright test --list 含 FS-UPLOAD-02 PASS(枚举) 真实浏览器执行需 dev 环境运行新代码后补跑

环境说明: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)无服务端元数据与原件,仍显示占位模板;重新上传即可获得正确行为。
  • 二进制(docx/pdf 等)文档正文仍为占位+原件预览;解析正文回写为后续增强。
  • readAttachmentsAsMaterials 行为变化:文本附件现在优先以 asset_id 引用(此前 inline Base64);CAS 失败时回退旧行为。

收口与交接

  • 剩余工作:无(授权范围内验收满足)。可选后续:二进制文件解析正文回写文档(非目标)、预存在测试敏感用例独立排查、dev 库合成残留授权清理。
  • 本地提交 307e9e65(+本文档收口提交);dev 集成 86efba2c(含第二轮文档收口集成);部署制品 bin/agent-server @86efba2c 运行中;远端未同步(未要求 push)。
  • cleanup:两个 worktree(任务 + 集成候选)保留,cleanup=pending,由独立维护者按门禁复查后处置。

相关链接

  • 问题分析见本文件"根因"节(用户会话内交付)。