Joye Dev

Back

Voyager 业务线学习:Export Job 的 durable Workflow 收口与跨层数据流

这条业务线解决的是“导出同一种设计时,为什么不同入口有不同的超限、失败、内存和重试语义”。在本次演进前,多页 PNG、选区图片和 PDF 分别存在同步路由或独立 Worker handler;一部分格式已经进入 Cloudflare Workflow,另一部分仍在 Next.js/Worker 请求生命周期内完成…


source_automation: voyager-merged-pr run_date: 2026-08-06 anchor_pr_number: 5683 pr_number: 5683 pr_title: “refactor(export): simplify export job data flow” pr_url: https://github.com/adastralab-ai/voyager/pull/5683 author: “luin” merged_at: “2026-08-06T03:24:55Z” modules:

  • idl/typespec/services/export.tsp
  • backend/go/apps/api/handler/export_jobs.go
  • backend/go/internal/exportjob
  • backend/workers/export/worker
  • backend/workers/export/worker/workflows
  • packages/site/src/app/(main)/files/[id]/_components/ExportButton.tsx
  • packages/site/src/app/(main)/files/[id]/_lib/exportRequest.ts files_changed: 9 changed_files:
  • backend/go/internal/exportjob/client.go
  • backend/go/internal/exportjob/client_test.go
  • backend/go/internal/exportjob/model.go
  • backend/go/internal/exportjob/service.go
  • backend/go/internal/exportjob/service_test.go
  • backend/workers/export/worker/workflows/exportImageWorkflow.test.ts
  • backend/workers/export/worker/workflows/exportImageWorkflow.ts
  • backend/workers/export/worker/workflows/exportPdfWorkflow.ts
  • idl/typespec/services/export.tsp learning_tags:
  • export-pipeline
  • cloudflare-workflows
  • durable-workflow
  • export-job-contract
  • normalized-input
  • immutable-page-source
  • backpressure
  • memory-bounds
  • api-compatibility
  • test-strategy business_line: “统一 Export Job 的 durable Workflow 管线、跨层契约与数据流” related_prs: [5580, 5600, 5668, 5686, 5687] related_prior_prs: [4938, 5235, 5320] line_stage: “JPG/PPTX 进入 Workflow (#5235) -> 多页 PNG 统一 items 与 page source (#5580) -> 删除同步多图分叉 (#5600) -> PDF 接入 durable job (#5668) -> Go/Worker/TypeSpec 收敛为规范化输入 (#5683) -> DESIGN 单页 PNG 收口 (#5686) -> 删除旧 Stage 渲染链 (#5687)” open_questions:
  • “exportJobs.ts 通过同一个 instance id 依次探测 image、pdf、pptx Workflow;格式继续增加时查询 fan-out、错误分类和观测成本会扩大。”
  • “#5683 把文件名冲突校验从 Worker 删除并前移到 Go service;如果未来存在绕过 Go 的受信调用方,内部 endpoint 与公共校验会重新漂移。”
  • “Workflow 成功/失败 retention 为 1 day,但 page source、逐页结果和 multipart 临时对象的 R2 清理策略不在本线代码中,需确认生命周期与成本边界。”
  • “PDF 多页合并逐个读取 R2,限制了输入并发,但最终 PDFDocument 仍随页数增长;100 页上限不等于有明确的字节或内存上限。”
  • “公共 Idempotency-Key 已标记 deprecated 且被忽略;创建响应丢失后的客户端重试可能产生重复 export job,需确认是否由上层调用方接受。”
  • “单测覆盖本地 Workflow step、Go contract 和 UI polling,但没有 Cloudflare Workflow、R2、浏览器渲染和滚动发布 skew 的部署级端到端证明。” feishu_doc_url: null github_repository: joyehuang/ai-agent-field-notes github_path: outputs/voyager-daily-pr-study/2026-08-06-pr-5683-export-workflow-unification.md voyager_merge_commit: 497b25ed10a789afd090b842cfbfbc532f1800cb voyager_origin_main_snapshot: 8543dac6d775632acf8694660bea1f0269c37599

业务线概览#

这条业务线解决的是“导出同一种设计时,为什么不同入口有不同的超限、失败、内存和重试语义”。在本次演进前,多页 PNG、选区图片和 PDF 分别存在同步路由或独立 Worker handler;一部分格式已经进入 Cloudflare Workflow,另一部分仍在 Next.js/Worker 请求生命周期内完成。结果是:页数上限分叉、同步 ZIP 有输入内存上限、资源缺失有不同错误码、同一个页面资源会被不同路径重复物化。

当前范围已经收敛为:

  • DESIGN 的 PNG/JPG/PDF/PPTX 通过 POST /api/export/jobs 创建 durable job,再由页面轮询并下载短期签名 URL;
  • 图片输入在 App API 中是 items,每个 item 可以是整页或同页节点选区;PDF/PPTX 和 Platform API 继续使用有序 pageIds
  • Worker 用 imagepdfpptx 三类 Workflow 复用同一页源物化与任务状态边界;
  • CODE PNG 和 MP4 仍保留同步渲染,因为 CODE 页不能直接复用 DESIGN 的 page source;旧 Stage export 路径则已删除。

今日锚点 #5683 看起来是一个小型 refactor,实际改变的是跨 Go service、Worker 参数、TypeSpec 变体和 Workflow output 的“规范化输入”边界。它把“已从文件权限检查中解析出的值”和“原始请求值”合并为一套内部字段,降低新增格式继续复制状态的成本。

下文区分代码事实、基于代码的推断和仍待确认的问题。所有正式关联 PR 的 merge commit 都已通过 git merge-base --is-ancestor <sha> origin/main 验证。

今日锚点#

项目内容
PR#5683 refactor(export): simplify export job data flow
作者luin
合并时间2026-08-06 13:24:55(Australia/Melbourne,03:24:55Z)
Voyager merge commit497b25ed10a789afd090b842cfbfbc532f1800cb
origin/main 快照8543dac6d775632acf8694660bea1f0269c37599(#5687)
文件数9
选择理由#5683 是 Melbourne 当天已合入、尚未作为锚点学习的跨层枢纽;它能自然串起昨天的多页 PNG/同步路径迁移、今天的 PDF 和 DESIGN 单页 PNG 收口。相邻的 #5687 主要是删除旧链路,单独作为锚点无法讲完整的用户流程。

演进时间线#

阶段PR 与真实代码变化改变的层新边界
既有 Workflow 基础#5235 将 JPG/PPTX 放入 export-jobs;本次作为最近学习记录中的前置背景,不计入正式 related 集合。Go export job、Worker Workflow、UI polling先建立持久化 job 和统一下载结果的边界,但 PNG/PDF 仍有同步分支。
多页 PNG 进入 Workflow#5580,merge 33092d5d12d7b97ca4d32a61cb01aece32c9c14e。App PNG 从 pageIds 改为 items,上限统一为 100;Worker 按页建立 R2 page source,再按 item 渲染;缺失资源用 NonRetryableErrorexport_assets_unavailable 终止;同步 images-zip、selection 路由删除。TypeSpec、Go validation、export Worker、site export API同页多个选区共享一次 page/API 物化,重试固定在同一页版本;失败不会白烧 3 次 Workflow render retry。
删除同步多图分叉#5600,merge 8f6075776187aa9db9b50f468c6405817073a862。删除 executeImagesExport/api/export-images 的多图 handler 和同步 ZIP 的 32 MiB source cap;选区图片下载改为创建 image job。Worker handler、Workflow、site hooks/UI多页和选区不再依赖一次 HTTP 请求的内存与执行时长;同步路径只留下还没有 durable 输入模型的场景。
PDF 接入同一 durable job#5668,merge e071046ca19d81ae1a65fecd9ad083043039f2c0。新增 ExportPdfWorkflow,删除同步 PDF route/handler;每页先写 R2,多个 PDF 按请求顺序逐个合并,单页直接写最终结果。TypeSpec、Go job client、Worker Workflow、site panelPDF 与 PNG/JPG 共用异步创建/查询/下载契约;页面渲染并发受控,合并输入不一次性全部读入。
今日锚点:规范化内部输入#5683,merge 497b25ed10a789afd090b842cfbfbc532f1800cb。删除 CreateInput.ResolvedItems / ResolvedPageIDs,Go service 在权限和页面校验后直接回写 Items / PageIDs;TypeSpec 把 PNG/JPG options 提成 aliases;Workflow archive/merge step 只负责副作用,最终 output 由 run 统一返回。Go model/service/client、TypeSpec、Workflow output从“raw + resolved”两套内部状态收敛到一套 worker-ready input,同时不改变已经发布的 Platform page-list wire shape。
DESIGN 单页 PNG 收口#5686,merge b077fcae3c62007b7a5de93b5ab8f46e1767464fExportButton 不再把 DESIGN 单页 PNG 送同步 route;/export/images 只保留 CODE PNG,DESIGN 单页和多页都调用 export job。site export UI/lib、CODE route用户看到的 DESIGN PNG 不再因页数选择另一套资源失败和错误提示;CODE 是明确保留的例外。
删除遗留 Stage 渲染链#5687,merge 8543dac6d775632acf8694660bea1f0269c37599。移除 stage.ts contract、handler、stage.html、StageRenderer、dev script 和 @repo/stage-entities 依赖;/api/stage-export 返回路径不再注册。export Worker render/contract/package已无仓库内或组织内调用方的旧兼容链不再和 Workflow renderer 并存;外部未声明调用方仍是发布风险。

这条线不是把所有格式强行做成同一套渲染器,而是统一 job 生命周期、资源物化和结果下载;图片、PDF、PPTX 仍各自有符合格式的 Workflow。同步例外被收窄为 CODE PNG/MP4,并在 UI 和 route 中显式表达。

当前架构与数据流#

flowchart LR
  U["ExportButton / selection hook"] --> S["downloadExportJob + poll"]
  S --> A["POST /api/export/jobs"]
  A --> G["Go handler + exportjob.Service"]
  G -->|"App image items"| W["internal export-jobs"]
  G -->|"Platform/PDF/PPTX pageIds"| W
  W --> I["image Workflow"]
  W --> P["pdf / pptx Workflow"]
  I --> R["R2 immutable page source"]
  P --> R
  R --> B["browser render steps"]
  B --> O["R2 result / ZIP / PDF"]
  S --> Q["GET job status"]
  Q --> D["Go rechecks access + signs URL"]
  C["CODE PNG"] --> X["synchronous export-code-image"]
plaintext
  1. App UI 的 ExportButton 将 DESIGN PNG/JPG 转成 { format, fileId, items, scale },PDF/PPTX 转成 { format, fileId, pageIds },统一调用 downloadExportJobpackages/site/src/app/(main)/files/[id]/_components/ExportButton.tsx:145-253
  2. exportRequest.ts 创建 job 后先等待 3 秒,再以 300ms 到 5 秒的递增间隔轮询;连续 5 次 status request failure 才认为状态不可知。Workflow 仍运行时,客户端不会硬编码总超时去伪造失败。packages/site/src/app/(main)/files/[id]/_lib/exportRequest.ts:104-175
  3. Go handler 依据 format 解联合体:App PNG/JPG 的 items 变为 exportjob.ExportItem,PDF/PPTX 的 pageIds 保持页面列表;workspace member、file/page 类型和上限在 job 创建前校验。backend/go/apps/api/handler/export_jobs.go:80-145
  4. Go service 在 worker 调用前做默认值和边界检查,并读取一次文件权限快照。若输入已经是 Items,只校验每个 page;若是 page list,则按文件中的 page order 得到 PageIDs,平台图片再展开为 whole-page Itemsbackend/go/internal/exportjob/service.go:48-114
  5. Worker 的内部 HMAC endpoint 用 Zod 再验证 discriminated union。items 走同一个 image Workflow,pageIds 通过 workflows[request.format] 选择 PDF 或 PPTX;每个 Workflow instance 保留成功/失败状态 1 天。backend/workers/export/worker/exportJobs.ts:56-145,165-197
  6. image Workflow 为每个不同 page 建一个 sources/page-NNN.json,其中保存 materialized design value、所需 assets 和 fonts;prepare 最多 10 个并发,render 最多 3 个并发。多个 item 只在 render 阶段做 node selection,最后用 R2 multipart 逐项写 ZIP。backend/workers/export/worker/workflows/exportImageWorkflow.ts:93-167backend/workers/export/worker/workflows/pageSources.ts:130-221
  7. PDF Workflow 也先准备 page source,再每页写 PDF;多页才进入 merge pdf document step,并按请求顺序逐个从 R2 读入 pdf-lib。单页直接使用 result.pdf,避免无意义的中间页对象。backend/workers/export/worker/workflows/exportPdfWorkflow.ts:58-109,147-165
  8. 查询完成 job 时,Go 重新读取结果文件并检查 workspace/page 仍可见,然后才为 objectKey 签发 URL;Worker 的任意失败默认映射为 workflow_failed,只有明确的 export_assets_unavailable 对外保留。backend/go/internal/exportjob/service.go:262-314backend/go/apps/api/handler/export_jobs.go:268-292

关键代码#

1. #5683:raw request 与 resolved request 合为一套内部模型#

位置:PR #5683 diff,backend/go/internal/exportjob/model.go;当前上下文 backend/go/internal/exportjob/service.go:87-109

 type CreateInput struct {
-    Items           []ExportItem
-    PageIDs         []uuid.UUID
-    ResolvedItems   []ExportItem
-    ResolvedPageIDs []uuid.UUID
+    // Platform image requests arrive as PageIDs and are normalized to Items
+    // before the worker client receives this input.
+    Items       []ExportItem
+    PageIDs     []uuid.UUID
     Format      Format
 }
plaintext

代码事实是:Go service 仍先做 workspace/file/page 校验;改变只在校验后的内部传递方式。#5683 同步更新 client.go,PDF/PPTX 发送 input.PageIDs,图片发送 input.Items,不再存在“调用方原始值”和“worker 实际值”两套字段。合理推断是新增格式时只需要维护一次 normalization,而不是继续增加 Resolved* 对。

2. #5683:TypeSpec 复用 options,但保留 App/Platform wire boundary#

位置:idl/typespec/services/export.tsp:55-169;PR #5683 diff。

alias PngExportJobOptions = {
  scale?: float64;
  transparent?: boolean;
};

model CreatePngExportJobRequest {
  format: "png";
  ...ImageExportJobTarget;
  ...PngExportJobOptions;
}

model PlatformCreatePngExportJobRequest {
  format: "png";
  ...WorkflowExportJobTarget;
  ...PngExportJobOptions;
}
plaintext

这不是把两种调用方强行合并:App 图片需要 items,Platform 仍需要 pageIds;共享的是 options 校验和生成逻辑。PR diff 没有改 idl/openapispecs/api.yml,说明这次 refactor 的目标是源模型去重和内部数据流收口,而不是破坏已发布的 Platform contract。

3. #5580/#5600:先固定 page source,再并行 item render#

位置:backend/workers/export/worker/workflows/exportImageWorkflow.ts:110-167backend/workers/export/worker/workflows/pageSources.ts:130-221

await pMap(
  pages,
  ({ pageId, pageItems, pageNumber, sourceKey }) =>
    step.do(`prepare image page ${pageNumber}`, stepConfig, () =>
      ensurePageSource(this.env, sourceKey, {
        workspaceId,
        pageId,
        items: pageItems.map(({ nodeIds }) => ({ nodeIds })),
      }),
    ),
  { concurrency: MAX_CONCURRENT_PREPARE_STEPS },
);

await pMap(
  workflowItems,
  ({ pageId, itemNumber, objectKey, nodeIds }) =>
    step.do(`render image ${itemNumber}`, stepConfig, () =>
      renderImageFromSource(this.env, sourceKeysByPageId.get(pageId)!, objectKey, nodeIds, options),
    ),
  { concurrency: MAX_CONCURRENT_RENDER_STEPS },
);
plaintext

设计点有两个:同页多个 selection 只做一次 token/page/assets/fonts 物化;render retry 读取不变的 R2 source,而不是重新从 Go API 取一个可能已经变化的页面。#5600 删除旧的 executeImagesExport 后,这个边界也成为多图 ZIP 的唯一实现。测试在 exportImageWorkflow.test.ts:206-251 断言同页资源请求只发生一次,在 :299-360 断言已有 source 时不再请求 API、某个 item 失败后仍复用 source。

4. #5668:PDF 将并发渲染和顺序合并拆成不同的 step#

位置:backend/workers/export/worker/workflows/exportPdfWorkflow.ts:64-109,147-165

await pMap(pages, ({ pageNumber, sourceKey }) =>
  step.do(`render pdf page ${pageNumber}`, stepConfig, () =>
    renderPdfPage(this.env, sourceKey, pdfObjectKey),
  ),
  { concurrency: MAX_CONCURRENT_RENDER_STEPS },
);

if (shouldMerge) {
  await step.do("merge pdf document", stepConfig, () =>
    mergePdfPages(this.env.EXPORT_BUCKET, pageObjectKeys, resultObjectKey),
  );
}
return { objectKey: resultObjectKey, fileId, pageIds };
plaintext

代码事实是多页 PDF 的 page render 可以并发,merge 则按请求顺序逐个读取 R2;单页不创建 pages/page-NNN.pdf 中间对象。测试 exportPdfWorkflow.test.ts:93-148 用不同页面尺寸验证顺序,:150-166 验证单页直写。合理推断是把“可并发的浏览器工作”和“必须有序的文档组装”拆开,降低峰值输入内存和重试耦合。

5. #5683:Worker 选择 pipeline,外部格式不再复制分支#

位置:backend/workers/export/worker/exportJobs.ts:165-213

await span({ name: "start workflow", op: "export.submit" }, () => {
  if ("pageIds" in request) {
    return startWorkflow(workflows[request.format], instanceId, {
      ...target,
      pageIds: request.pageIds,
    });
  }
  return startWorkflow(workflows.image, instanceId, {
    ...target,
    items: request.items,
    encoding: request.format === "jpg" ? "jpeg" : "png",
    quality: request.quality,
    scale: request.scale,
    transparent: request.transparent,
  });
});
plaintext

png/jpg 的差异在 encoding 和 options,复用 image Workflow;pdf/pptx 的差异在 Workflow binding,复用 document params。状态查询因为 job id 不编码 format,按 image、pdf、pptx 依次探测同一 instance id;这是一种小而明确的兼容设计,也留下了格式增加后的 fan-out 问题。

6. #5686:同步例外在用户入口处显式收窄#

位置:PR #5686 diff,packages/site/src/app/(main)/files/[id]/_components/ExportButton.tsx:178-204

// CODE pages cannot use the durable pipeline, so their PNG capture stays
// synchronous while DESIGN pages use the same job flow at every page count.
if (selectedPageId && isCodePage) {
  void runRequest("/files/${fileId}/export/images", { ... });
  return;
}
exportImageJob("png", pageIds, scale);
plaintext

这段分支证明“统一 Workflow”不是无条件重写所有导出:CODE 的 HTML screenshot 仍依赖 workspace-scoped token 和同步 route;只有 DESIGN PNG 取消单页 shortcut。对应 route 的测试不再测试 DESIGN worker 资源预取,而测试改为 CODE page 的 worker token 和 export-code-image 请求。

工程取舍#

边界与复用#

  • 可信边界在 Go service:它先做 workspace membership、file ownership、DESIGN page 类型和 100 item/page 上限,再调用内部 Worker。Worker 仍做 HMAC 和 Zod 校验,但不重新承担文件权限语义。
  • 复用发生在正确的层:pageSources.ts 复用 page/version/resource 物化;image、PDF、PPTX 保留不同的最终 artifact 组装;App 与 Platform 保留不同 target model。这样减少重复,又没有把 ZIP、PDF merge 和 deck render 伪装成同一业务。
  • #5683 删除 Worker 侧 image file-name collision check,把 caller error 前移为 Go 400。收益是单一校验源,代价是内部 endpoint 对调用方契约更敏感。

兼容性与发布#

  • TypeSpec 维持 discriminated union 的平面 wire shape;Platform PNG/JPG 的 page-list 请求仍由 Go 展开成 whole-page item,外部 Platform SDK 不需要改成 App 的 item contract。
  • App API 新增 PDF 变体,删除同步多图/PDF 路由属于站内调用方迁移;#5686 还明确保留 CODE PNG route,避免把 CODE 的资源模型强行塞进 DESIGN Workflow。
  • #5580 的发布说明承认旧/新 Go/site 短暂共存时,创建请求可能触发一次 CLIENT_OUTDATED reload;这需要发布流程和真实部署验证,而不是只看生成的 TypeScript client 能否编译。

性能与可靠性#

  • Workflow 将 prepare 并发限制为 10、browser render 限制为 3;同页 selection 共享 source;ZIP 使用 R2 multipart 和 5 MiB part buffer,避免把所有渲染结果一次性装进 Worker 内存。
  • 缺失 asset 由 NonRetryableError 直接变成 export_assets_unavailable,前端显示“重试或移除图片”;未知失败收敛为 workflow_failed,避免把内部错误文案变成公开 API vocabulary。
  • PDF 多页中间页落 R2,按顺序读入 merge;单页直接写 result。这降低同步请求的内存/时长压力,但最终 PDF 库对象仍需内存,不能把 100 页当成无限安全阈值。
  • Job 创建与 status 查询由 Workflow instance 负责持久化;客户端只做有限的 status fault tolerance,不自行判断长任务超时,避免生成完成但前端已把它标成失败。

测试策略#

  • Worker workflow test 使用真实的本地 bucket/step harness,mock 浏览器和 API,验证 page source 重用、selection 裁剪、ZIP entry naming、缺失 asset non-retryable、PDF 页序和单页直写。
  • Go service/handler/contract test 验证 100 页边界、App items 透传、Platform page list 展开、PDF 请求不携带图片 options,以及完成后重新检查页面访问权。
  • Site test 验证 DESIGN 单页 PNG、多页 PNG ZIP、PDF、PPTX、JPG、超过 20 页、status transient failure 和 export_assets_unavailable 的用户提示;CODE PNG 仍验证同步 route。
  • 尚未被这些测试证明的是真实 Cloudflare Workflow retry semantics、R2 multipart 大对象、真实 browser/render worker、Go/site rolling skew、PDF 超大结果和生产端 R2 清理。

和最近学习记录的关系#

  • #4938 学到的是 PNG scale 从 Worker fallback 提升为调用方显式 contract;本线继续把 scale/quality/transparent 作为 TypeSpec variant options,并在 Go service 按 format 只接受能被该格式消费的字段。
  • #5235 学到的是 JPG/PPTX 先进入 export-jobs;本次把多页 PNG、PDF 和 DESIGN 单页 PNG 继续搬入同一 job lifecycle,完成“格式接入”到“数据流收口”的阶段转换。
  • #5320 学到的是 image Workflow 的 prepare/render 并行化;本次的 page source 复用和 10/3 concurrency 保留这个性能方向,同时把 retry 输入固定成 R2 source。
  • 与昨天 #5591 的图片参考图业务线不同,本次不把 Agent image refs 作为 related PR;只有 #5580/#5600/#5668/#5686/#5687 在 export job、page source、同步/异步边界上有直接代码关系。

我会怎么吸收#

  1. 在权限和 schema 校验完成后立即把多种外部请求归一化为一套内部模型;不要同时携带 raw/resolved 两份状态。
  2. 对可重试的长任务,先持久化不可变输入,再并行执行副作用;render retry 不应重新读取会变化的业务源。
  3. 共享生命周期和资源物化边界,保留格式特有的 artifact 组装;“统一管线”不等于“统一所有渲染实现”。
  4. 将业务上不可重试的资源缺失编码为稳定错误码,把其他内部失败压缩为受控的公开 vocabulary。
  5. 只保留有明确运行时理由的同步例外,并在用户入口、route、测试和文档中同时标出例外边界。

边界、风险与未解问题#

已由代码确认#

  • App image requests 使用 items,Platform image requests 使用 pageIds;Go service 在发送到 Worker 前把 Platform 图片展开为 whole-page items。
  • image/PDF Workflow 都先经过 page source prepare;image 对同页多个 selection 只请求一次页面、asset、font 数据;PDF 单页不做 merge。
  • #5683 删除 ResolvedItems/ResolvedPageIDs,但保留了公共 TypeSpec union、Go 权限检查和 Worker-ready 的 Items/PageIDs 分流。
  • #5686 之后 /files/[id]/export/images 只服务 CODE PNG;DESIGN PNG(单页和多页)从 ExportButton 进入 job polling。
  • #5687 删除旧 Stage contract、handler、renderer、HTML、dev script 和 package dependency;当前 worker.ts 不再注册 /api/stage-export

合理推断#

  • page source 将资源解析从每个 item 提升到每个 page,主要收益是同页 selection 的网络和权限读取去重;代价是一个页面任一资源缺失会使该页所有 item 一起失败。
  • startWorkflow 在 create 报错后尝试 get(instanceId),是在没有真正使用 Idempotency-Key 的情况下用确定性 instance id 做一次有限的重复创建恢复;它不等价于完整的跨客户端幂等协议。
  • 用 job id 探测三个 Workflow binding 简化了公开 status contract,但把“格式路由”从标识符中拿掉,未来格式数量增加会放大 status latency 和错误诊断复杂度。

尚待确认#

  • Workflow retention 到期、失败中间对象和 multipart abort 后的 R2 对象是否由统一 lifecycle policy 清理;当前 diff 没有给出存储回收证明。
  • #5683 之后如果有非 Go 的内部调用方直接调用 /api/internal/export-jobs,文件名默认冲突、file/page authorization 和 public error mapping 是否仍与 Go service 完全一致。
  • 多页 PDF 在真实 100 页、大图片和异常字体情况下的 pdf-lib 峰值内存、Workflow step timeout 和最终结果大小上限。
  • Cloudflare production 中 pMap 并发、Workflow step retry、浏览器 binding 与 R2 consistency 是否保持单测假设;尤其是页面 delta 在 source materialization 期间继续写入的竞态。
  • 发布期间旧/新 generated API client、Go handler 和 Worker schema 短暂共存时,哪些格式会触发 reload/502;需要 deployment-level contract smoke test。
  • Idempotency-Key 被 schema 标为 deprecated 但忽略,创建请求在网络超时后是否允许重复扣取资源/占用 Workflow 配额,仍需产品级决策。

候选说明#

候选窗口使用 Australia/Melbourne 2026-08-06 当天的 merged PR;没有扩大到更早日期寻找新的锚点。当天有多条 export 变更,#5687 是删除旧 Stage 路径的收尾,#5686 是 DESIGN PNG 的入口收口,#5683 则同时触及 Go service、Worker params、TypeSpec 和 Workflow output,且能自然连接 8 月 5 日的 #5580/#5600 与 8 月 6 日的 #5668。因此选择 #5683 作为学习锚点,而不是把同日 cleanup PR 拼成主线。

正式关联集合只包含有代码证据的同一条 export job 线:#5580 的 page source/items 契约是 #5600 的前提;#5600 与 #5668 都删除同步格式路径并复用 Workflow 生命周期;#5683 收敛它们共享的输入/output;#5686 依赖这一 job contract 收口 DESIGN PNG;#5687 删除已被新管线取代的旧 renderer。#4938、#5235、#5320 仅作为最近学习记录中的前置背景,不冒充本次正式 related PR。

验证记录:

  • Voyager 初始和报告生成前状态均为 jh-vision-outsource-eval...origin/main [ahead 10, behind 427],未修改工作区;远端刷新使用 git fetch --prune origin
  • Voyager 工作区没有运行格式化、代码生成、安装或测试命令;代码证据来自 PR merge commit 的 git showorigin/main 文件上下文。
  • #5580 33092d5d12d7b97ca4d32a61cb01aece32c9c14e、#5600 8f6075776187aa9db9b50f468c6405817073a862、#5668 e071046ca19d81ae1a65fecd9ad083043039f2c0、#5683 497b25ed10a789afd090b842cfbfbc532f1800cb、#5686 b077fcae3c62007b7a5de93b5ab8f46e1767464f、#5687 8543dac6d775632acf8694660bea1f0269c37599 均通过 git merge-base --is-ancestor <sha> origin/main
  • 目标仓库在写入前已从 origin/main fast-forward 且工作区干净;本报告和索引是本次唯一待提交的目标仓库改动。

GitHub 文档#

  • 报告文件:outputs/voyager-daily-pr-study/2026-08-06-pr-5683-export-workflow-unification.md
  • 锚点 PR:https://github.com/adastralab-ai/voyager/pull/5683
  • 关联 PR:55805600566856865687
  • Voyager 锚点 commit:497b25ed10a789afd090b842cfbfbc532f1800cb
  • Voyager origin/main snapshot:8543dac6d775632acf8694660bea1f0269c37599
  • 目标仓库:joyehuang/ai-agent-field-notes;本次推送后的目标仓库 commit 会写入外部 study-log.jsonl

本报告的持久化来源只有 GitHub Markdown;metadata 中的 feishu_doc_url 固定为 null

🗂️ 这是知识库中的🔬 研究。

内容可能仍在补充或修订中。

← Back