# 按 id 取资产:PDF 默认直链、md/JSON 默认字节流,均可复写 > **当前内容契约更新**:固定来源 PDF 已恢复为 paper_access 开关下的鉴权字节, > 关闭404,qa_、别名、Range、历史原件不能绕过。新内容在独立 qatlas-content 桶的 > content/ 命名空间不可变发布:source 绑定确切 PDF SHA,arXiv vN 是语义版本、 > S3VersionId 是物理版本、parse revision 是独立不可变身份;三者不可混用。 > 同 paper/SHA 可复用 source;冻结导入别名不同字节409(overwrite也不替换)。 > 解析上传必须完整支持的 Middle+Markdown,保留所有成员原名/字节,MD-only拒收; > manifest最后核验发布且PG必需,重传新revision,不覆盖历史。旧MD/JSON/images忽略, > 只在内容/PDF访问按需冻结旧PDF,不bulk回填、不删除旧桶;没有nightly/boot/入库自动推理。 > 下文涉及旧三桶覆盖、仅full.md+images、原PDF410/外部直链的段落保留为历史背景, > **不作为当前操作规则**。当前API/上传/续读统一见[论文内容契约](../server/paper-content.md) > 与[Upload API](../server/upload-api.md);完整read视图不是原Middle JSON。 _实现 ADR `0007` 决定的读取界面,基于 ADR `0009` 的 `papers`/`paper_assets` schema。_ > **历史状态**:本ADR记录当时PDF直链与随后410停用阶段;新内容契约已替代 > 这些交付/覆盖规则。PDF现为gate下固定来源字节,format=link是同源认证locator; > Markdown也经完整不可变bundle,旧派生缓存不作为readiness。本ADR正文保留历史。 `GET /api/papers/{id}/markdown`(以及 `/pdf`)已经遵循 async LRO contract——`202 Accepted` + `Operation-Location` + `Retry-After`,轮询 `…/status`,完成后重新 GET。ADR `0009` 为每个 work 提供一行 catalog row 和一个子 `paper_assets` 表后,读取路径会**经由 catalog 解析** (`papers` → `paper_default_asset_id` → asset 存储的 `*_path` key)。catalog 只存 RustFS **对象键**;调用者如何拿到内容,由两个正交问题决定:**(1) QA 是否允许提供它**(server config 中的合规门控)以及 **(2) 以字节流还是以直链提供**(技术/UX 选择,与合规无关)。 ## 决策 - **按 id 经由 catalog 解析。** 通过三类 external ids 中任意一种读取(`arxiv` / `doi` / `openalex`,在 `papers` 上各自 `UNIQUE`)时,解析链路是 `papers` → `paper_default_asset_id` → asset 存储的 key(`pdf_path` / `mineru_md_path` / `mineru_json_path`)。default-asset pointer(ADR `0009` Q21:published-first,否则 latest arXiv) 决定裸 by-paper read 服务的是*哪一个* asset。批量 id → metadata 解析仍留在 `GET /api/papers/lookup`(ADR `0007`)。 - **合规由访问门控 `QATLAS_PAPER_ACCESS_ENABLED`(server config)决定。** QA 是否对外提供 paper content 完全在这里决定,再分发义务也在这里处理: - **OFF**(默认;公开 quantum-atlas.ai 姿态):QA 不做再分发。arXiv 论文的 **PDF** 读取返回规范 **`arxiv.org/pdf/vN` link**(由来源方分发,不是 QA);没有 arXiv source 的 PDF(published/DOI-only)**不提供**。**markdown / JSON** **不提供**。 - **ON**(operator 选择开启,并接受 derivative-work distribution obligation):PDF、markdown 和 JSON 都提供。 - **字节流 vs. 直链是正交的逐请求选择,不是合规决策。** 一旦门控允许提供内容,字节如何到达调用者就是纯技术问题; 每类资产有默认值,也有 REST 复写: - **PDF → 默认:RustFS 直链。** 该 link 是 server-configured RustFS public URL prefix (`QATLAS_S3_PUBLIC_ENDPOINT`,所有 clients 都可达)拼上存储的 `pdf_path`。默认给 link, 是因为 PDF 是大二进制,更适合由对象存储直接服务,而不是经 qatlasd proxy。 - **markdown / JSON → 默认:字节流。** handler 读取 `mineru_md_path` / `mineru_json_path` 并流式传输 bytes(`text/markdown`、`application/json`)——这些是小文本, API 适合 inline 提供。 - **通过 REST query parameter 复写**(`?format=link|bytes`):调用者可以请求 PDF **bytes** (经 qatlasd proxy)或 markdown/JSON **link**。在无法 presign / 没有 public prefix 的 backend (LocalStore dev)上,link request 会降级为 bytes。 - **Lazy suspend-and-wait,而不是 404-on-miss。** 当解析出的 asset 缺少请求的 artifact (还没 fetched PDF,或还没有 `mineru_md_path`)时,保留现有 LRO:`202` + `Operation-Location`, 后台 silent fetch-PDF + MinerU convert,调用者轮询 `…/status` 直到 ready。这样,一个 QA 尚未托管的 corpus hit 可以*变成* hosted(ADR `0007`),而不是 hard-missing。 - **文档自动生成。** 路由的 OpenAPI 桩位于 `internal/routes/openapi.go`,由 `pixi run swagger` 重新生成(CI 漂移检查);面向人的参考文档是 `docs/server/rest-api.md`。 ## 为什么这两个问题要分开 - **合规完全落在 access gate 里。** `QATLAS_PAPER_ACCESS_ENABLED`(以及 OFF 时的 arxiv-link / not-served 行为)负责避免 QA 再分发不该再分发的内容。这个决策在 server config 中一次性做出,不依赖 transport。 - **字节流 vs. 直链永远不改变披露内容**——两种方式交付的是同一份已被允许的内容;差别只是由谁搬运字节 (qatlasd 还是对象存储),以及 client contract 有多方便。因此它是技术/UX knob,不是合规 lever, 可以安全地暴露为逐请求选项。 ## 为什么选择这些默认值(PDF 直链,markdown/JSON 字节流) - **PDF 直链让 qatlasd 离开大二进制路径。** PDF 很大;返回 RustFS URL 可以让对象存储 直接服务它们。所有 clients 都能访问配置好的 RustFS endpoint,因此没有可达性限制。 - **markdown / JSON 字节流让 agents 更简单。** 消费方(qatlas-lean 的 scout/enrich)想要的是文本, 不是再跳一次;inline 流式传输这些小型 derived artifacts 是有用的默认值。 - **二者都可复写**,因为默认值都不是普适的:想要单个 authenticated hop 的调用者可以强制 PDF bytes; 想把 URL 继续传下去的调用者可以强制 markdown/JSON link。 ## 为什么保留 LRO,而不是 artifact 缺失时返回 404 References 和 reads 的目标是 *works*;QA 在 OpenAlex corpus 中持有某个 work、但尚未 fetch/convert, 是正常且可恢复的状态——suspend-and-wait contract 让第一个 reader 触发 materialisation 并拿到结果, 这正是 ADR `0007` 依赖的 agent-friendly 行为("a corpus hit … can later … become a hosted Paper")。 ## 备选方案 - **把字节流 vs. 直链归因于合规(本 ADR 的早期草案)。** 否决:合规已由访问门控 执行;transport 选择不披露任何额外内容,因此把它绑到合规是错误的,还会不必要地禁止例如给 已获许可的调用者提供 PDF byte-stream。 - **所有内容都作为字节流提供(现状)。** PDF 场景否决:这会让 qatlasd proxy 每个本可由 对象存储直接服务的大 PDF 二进制。 - **所有内容都作为 links 提供。** markdown/JSON 场景否决:对 API 可直接 stream 的小文本增加了不必要的 hop + presign dependency,并且在 LocalStore 上会破(no presign)。 - **artifact 缺失时返回 404。** 否决:破坏 lazy-materialise contract。 ## 影响 - **`/pdf` success shape changes(Phase C)。** 默认情况下,ready PDF 返回 direct link(gate OFF 时为 arXiv redirect;ON 时为 RustFS URL),而不是 `application/pdf` bytes——这会 breaking 当前 byte consumers(pre-launch,可接受),可用 `?format=bytes` 切回。`/markdown` 继续 streaming bytes,并获得一个 sibling `/json` byte endpoint(现在已有 `paper_assets.mineru_json_path`),二者都支持 `?format=link`。`docs/server/rest-api.md` + OpenAPI 规范会重新生成。 - **依赖 ADR `0009`。** handlers 读取 `papers`/`paper_assets`;它们随 Phase A 一起实施,不会早于 Phase A。 - **没有新的 write path。** 这些是 `papers:read` 下的读取能力,符合 QA/lean 边界(ADR `0007`/`0008`): QA 暴露只读 paper substrate;它永远不 author lean content。