Преглед на файлове

docs(office-to-pdf): clarify conversion and engine package ownership

yudshj преди 2 седмици
родител
ревизия
682011adfa

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.md
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.md
-2026-09-15-bounded-office-rendering.md: 242434361e665359ebfa6d934b5257fc0df12ed9
-2026-09-15-bounded-office-rendering.zh.md: b8268a5e9c421bb7b205136f66213acaa7aa8548
+2026-09-15-bounded-office-rendering.md: ae6ac3ec78d826cea69ff9adf9399a555206d022
+2026-09-15-bounded-office-rendering.zh.md: 5dd1c69df16e87819dece7bd00ebc367861177bf

+ 3 - 3
.agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.md

@@ -1,4 +1,4 @@
-# Agent Note: Bounded shared Office rendering
+# Agent Note: Bounded shared Office conversion
 
 
 Status: implemented
 Status: implemented
 
 
@@ -12,7 +12,7 @@ Office preview and explicit document inspection can request the same conversion.
 
 
 The `office-to-pdf` service returns complete PDF bytes. Page rasterization and user presentation remain separate consumers, so conversion naming does not imply image rendering or preview UI.
 The `office-to-pdf` service returns complete PDF bytes. Page rasterization and user presentation remain separate consumers, so conversion naming does not imply image rendering or preview UI.
 
 
-The [Host provider](../../../../packages/document/office-to-pdf/README.md) owns a shared conversion queue and transient content cache. Authorized source metadata enters admission before source bytes are loaded. The source callback receives reserved byte capacity and returns its read version; changed sources fail without publishing aliases. Exact source bytes and Office extension determine the digest. Each renderer lifetime adds a generation so engine/font/configuration replacement invalidates reuse.
+The [Host provider](../../../../packages/document/office-to-pdf/README.md) owns a shared conversion queue and transient content cache. Authorized source metadata enters admission before source bytes are loaded. The source callback receives reserved byte capacity and returns its read version; changed sources fail without publishing aliases. Exact source bytes and Office extension determine the digest. Each converter lifetime adds a generation so engine/font/configuration replacement invalidates reuse.
 
 
 A bounded source-version index avoids repeated reads after authorization; the digest remains the identity for sharing conversion across distinct paths. Ready PDFs use an entry/byte-bounded LRU. Queued jobs contain metadata and deferred callbacks. Reader, queue, source-byte, and conversion limits also apply before work completes. Each active source locator belongs to live readers, so cancellation cannot grow retained source metadata independently of reader admission. Unknown source sizes reserve the input cap; cancellation retains active capacity until actual read/conversion cleanup settles.
 A bounded source-version index avoids repeated reads after authorization; the digest remains the identity for sharing conversion across distinct paths. Ready PDFs use an entry/byte-bounded LRU. Queued jobs contain metadata and deferred callbacks. Reader, queue, source-byte, and conversion limits also apply before work completes. Each active source locator belongs to live readers, so cancellation cannot grow retained source metadata independently of reader admission. Unknown source sizes reserve the input cap; cancellation retains active capacity until actual read/conversion cleanup settles.
 
 
@@ -34,4 +34,4 @@ Foreground preview and explicit QA requests precede background work. Disabling b
 
 
 The cache is transient and cannot bypass source authorization. Oversized PDFs can be returned without retention, and failed or canceled conversions are retried on a later explicit request. Source reservations measure binary bytes; base64 expansion, engine RSS, caller-retained output, and PDF.js page memory remain outside those limits. With one configured conversion slot, foreground work waits for an already-running background conversion to finish.
 The cache is transient and cannot bypass source authorization. Oversized PDFs can be returned without retention, and failed or canceled conversions are retried on a later explicit request. Source reservations measure binary bytes; base64 expansion, engine RSS, caller-retained output, and PDF.js page memory remain outside those limits. With one configured conversion slot, foreground work waits for an already-running background conversion to finish.
 
 
-Controlled source and engine completions verify pre-read admission, content joining, priority, cancellation isolation, delayed resource release, LRU/alias limits, stale versions, and renderer replacement. Loader composition and native conversion checks exercise the shared provider independently of presentation consumers.
+Controlled source and engine completions verify pre-read admission, content joining, priority, cancellation isolation, delayed resource release, LRU/alias limits, stale versions, and converter replacement. Loader composition and native conversion checks exercise the shared provider independently of presentation consumers.

+ 3 - 3
.agents/notes/implemented/architecture/2026-09-15-bounded-office-rendering.zh.md

@@ -1,4 +1,4 @@
-# Agent Note: 有界的共享 Office 渲染
+# Agent Note: 有界的共享 Office 转换
 
 
 Status: implemented
 Status: implemented
 
 
@@ -12,7 +12,7 @@ Office 预览和显式文档检查可能请求相同转换。仅缓存已完成
 
 
 `office-to-pdf` 服务返回完整 PDF 字节。页面栅格化和用户展示由独立消费方负责,因此转换命名不隐含图片渲染或预览 UI。
 `office-to-pdf` 服务返回完整 PDF 字节。页面栅格化和用户展示由独立消费方负责,因此转换命名不隐含图片渲染或预览 UI。
 
 
-[宿主提供方](../../../../packages/document/office-to-pdf/README.zh.md)拥有共享转换队列和临时内容缓存。已授权的源文件元数据在加载字节之前进入准入流程。源回调接收预留的字节容量并返回读取版本;源文件变化会导致失败,不发布别名。确切的源字节和 Office 扩展名决定摘要。每个渲染器生命周期附加代次,因此引擎、字体或配置替换会使复用失效。
+[宿主提供方](../../../../packages/document/office-to-pdf/README.zh.md)拥有共享转换队列和临时内容缓存。已授权的源文件元数据在加载字节之前进入准入流程。源回调接收预留的字节容量并返回读取版本;源文件变化会导致失败,不发布别名。确切的源字节和 Office 扩展名决定摘要。每个转换器生命周期附加代次,因此引擎、字体或配置替换会使复用失效。
 
 
 有界的源版本索引在授权后避免重复读取;摘要仍是不同路径间共享转换的身份。已就绪 PDF 使用按条目与字节限制的 LRU。排队任务包含元数据和延迟回调。读取方、队列、源字节与转换限制在工作完成前也适用。每个在途源定位信息归属于活跃读取方,因此取消操作不能让保留的源元数据脱离读取方准入限制增长。未知源大小预留输入上限;取消后仍保留活动容量,直至实际读取、转换与清理结束。
 有界的源版本索引在授权后避免重复读取;摘要仍是不同路径间共享转换的身份。已就绪 PDF 使用按条目与字节限制的 LRU。排队任务包含元数据和延迟回调。读取方、队列、源字节与转换限制在工作完成前也适用。每个在途源定位信息归属于活跃读取方,因此取消操作不能让保留的源元数据脱离读取方准入限制增长。未知源大小预留输入上限;取消后仍保留活动容量,直至实际读取、转换与清理结束。
 
 
@@ -34,4 +34,4 @@ Office 预览和显式文档检查可能请求相同转换。仅缓存已完成
 
 
 缓存为临时数据,不能绕过源授权。超出缓存上限的 PDF 可返回而不保留;失败或取消的转换在后续显式请求时重试。源预留按二进制字节计量;base64 膨胀、引擎 RSS、调用方保留的输出和 PDF.js 页面内存不计入这些限制。仅配置一个转换槽位时,前台工作等待已运行的后台转换结束。
 缓存为临时数据,不能绕过源授权。超出缓存上限的 PDF 可返回而不保留;失败或取消的转换在后续显式请求时重试。源预留按二进制字节计量;base64 膨胀、引擎 RSS、调用方保留的输出和 PDF.js 页面内存不计入这些限制。仅配置一个转换槽位时,前台工作等待已运行的后台转换结束。
 
 
-受控的源读取和引擎完成验证读取前准入、内容合并、优先级、取消隔离、延迟资源释放、LRU 与别名限额、过期版本及渲染器替换。Loader 组合与原生转换检查独立于展示消费者验证共享提供方。
+受控的源读取和引擎完成验证读取前准入、内容合并、优先级、取消隔离、延迟资源释放、LRU 与别名限额、过期版本及转换器替换。Loader 组合与原生转换检查独立于展示消费者验证共享提供方。

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md
-2026-09-15-platform-office-engines.md: a3d1365bf33006c31d53c54f1255f72c4663cb5d
-2026-09-15-platform-office-engines.zh.md: dfffd1b9de33a2b138e0450f8a60d940fadcefd4
+2026-09-15-platform-office-engines.md: 4272054672c6163f22b910ab64b6472a9dbdfb4c
+2026-09-15-platform-office-engines.zh.md: d0a317333049734415ab9c0c05a5624b63b27447

+ 2 - 0
.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md

@@ -26,4 +26,6 @@ Python sidecar assembly copies only the selected engine and its dependency closu
 
 
 Distributions with a declared native target omit WASM assets. Other targets retain WASM resource and font requirements. The pinned kit declares macOS/Windows ARM64 and x64 native packages, so current Linux distributions select WASM; a kit release can add a native Linux target without changing Harness’s selection rule. This does not expand Harness’s supported release platforms. Harness sidecar, wheel, and runtime-resolution tests cover both declared native targets and WASM selection, including missing native packages. New package bytes require kit qualification and matching dependency integrity records before publication.
 Distributions with a declared native target omit WASM assets. Other targets retain WASM resource and font requirements. The pinned kit declares macOS/Windows ARM64 and x64 native packages, so current Linux distributions select WASM; a kit release can add a native Linux target without changing Harness’s selection rule. This does not expand Harness’s supported release platforms. Harness sidecar, wheel, and runtime-resolution tests cover both declared native targets and WASM selection, including missing native packages. New package bytes require kit qualification and matching dependency integrity records before publication.
 
 
+A new engine package identity also requires updates to `LIBREOFFICE_PACKAGES` in `scripts/gen-third-party-notices.ts`, any applicable `minimumReleaseAgeExclude` entry in `pnpm-workspace.yaml`, and the package list in the [kit ownership note](2026-09-14-independent-libreoffice-kit.md). The license allowlist remains explicit.
+
 The [public Python release workflow](../../../../.github/workflows/python-release.yml) rejects any wheel at or above 100,000,000 bytes. Selecting one engine reduces payload size but does not establish that a runtime wheel meets this limit; npm engine publication and local conversion are separate from wheel upload eligibility.
 The [public Python release workflow](../../../../.github/workflows/python-release.yml) rejects any wheel at or above 100,000,000 bytes. Selecting one engine reduces payload size but does not establish that a runtime wheel meets this limit; npm engine publication and local conversion are separate from wheel upload eligibility.

+ 2 - 0
.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md

@@ -26,4 +26,6 @@ Python sidecar 组装仅复制所选引擎及其依赖闭包。wheel 打包和
 
 
 声明了原生目标的分发物省去 WASM 资源,其余目标保留 WASM 的资源和字体要求。锁定的 kit 声明了 macOS/Windows ARM64 和 x64 原生包,因此当前 Linux 分发物选择 WASM;kit 发布版本可以新增 Linux 原生目标,无需修改 Harness 的选择规则。这不扩展 Harness 支持的发布平台。Harness 的 sidecar、wheel 和运行时解析测试覆盖已声明原生目标与 WASM 选择,包括原生包缺失。新包字节在发布前需要 kit 资格验证和匹配的依赖完整性记录。
 声明了原生目标的分发物省去 WASM 资源,其余目标保留 WASM 的资源和字体要求。锁定的 kit 声明了 macOS/Windows ARM64 和 x64 原生包,因此当前 Linux 分发物选择 WASM;kit 发布版本可以新增 Linux 原生目标,无需修改 Harness 的选择规则。这不扩展 Harness 支持的发布平台。Harness 的 sidecar、wheel 和运行时解析测试覆盖已声明原生目标与 WASM 选择,包括原生包缺失。新包字节在发布前需要 kit 资格验证和匹配的依赖完整性记录。
 
 
+新增引擎包标识还需要更新 `scripts/gen-third-party-notices.ts` 中的 `LIBREOFFICE_PACKAGES`、`pnpm-workspace.yaml` 中适用的 `minimumReleaseAgeExclude` 条目,以及 [kit 归属记录](2026-09-14-independent-libreoffice-kit.zh.md)中的包列表。许可证允许列表仍使用明确的包标识。
+
 [公开 Python 发布工作流](../../../../.github/workflows/python-release.yml)拒绝任何大于等于 100,000,000 字节的 wheel。只选择一个引擎会减少载荷,但不能据此认定运行时 wheel 已满足此限制;npm 引擎发布、本地转换与 wheel 上传资格是不同的验证。
 [公开 Python 发布工作流](../../../../.github/workflows/python-release.yml)拒绝任何大于等于 100,000,000 字节的 wheel。只选择一个引擎会减少载荷,但不能据此认定运行时 wheel 已满足此限制;npm 引擎发布、本地转换与 wheel 上传资格是不同的验证。

+ 1 - 0
apps/desktop/scripts/prepare-package-set.ts

@@ -52,6 +52,7 @@ function dependencyNames(manifest: Readonly<Record<string, unknown>>, section: s
 
 
 /**
 /**
  * Select workspace dependencies rooted at dsh and its private Host; npm resolves external packages.
  * Select workspace dependencies rooted at dsh and its private Host; npm resolves external packages.
+ * Reads the repository workspace manifest and package manifests to distinguish required local packages from npm-resolved externals.
  * @param available - Packed packages indexed by package name.
  * @param available - Packed packages indexed by package name.
  * @returns Selected packages sorted by name.
  * @returns Selected packages sorted by name.
  */
  */

+ 1 - 1
packages/document/office-to-pdf/src/queue.ts

@@ -92,7 +92,7 @@ export class ConversionQueue {
       const abort = (): void => {
       const abort = (): void => {
         this.release(reader)
         this.release(reader)
         const reason: unknown = signal?.reason
         const reason: unknown = signal?.reason
-        reject(reason instanceof Error ? reason : new Error('Document rendering cancelled', { cause: reason }))
+        reject(reason instanceof Error ? reason : new Error('Office conversion cancelled', { cause: reason }))
         if (reader.job.readers.size === 0) this.cancel(reader.job)
         if (reader.job.readers.size === 0) this.cancel(reader.job)
         // Cancellation or demotion can unblock queued background work.
         // Cancellation or demotion can unblock queued background work.
         this.drain()
         this.drain()

+ 1 - 1
packages/document/office-to-pdf/tests/output.spec.ts

@@ -23,7 +23,7 @@ afterEach(() => { io.opened = undefined; vi.restoreAllMocks() })
 const pdf = Buffer.from('%PDF-1.7\npreview\n%%EOF\n')
 const pdf = Buffer.from('%PDF-1.7\npreview\n%%EOF\n')
 
 
 async function output(): Promise<{ directory: string; path: string; handles: FileHandle[] }> {
 async function output(): Promise<{ directory: string; path: string; handles: FileHandle[] }> {
-  const directory = await mkdtemp(join(tmpdir(), 'dsh-render-output-'))
+  const directory = await mkdtemp(join(tmpdir(), 'dsh-office-to-pdf-output-'))
   const handles: FileHandle[] = []
   const handles: FileHandle[] = []
   onTestFinished(async () => {
   onTestFinished(async () => {
     try { await Promise.all(handles.map(file => file.close())) }
     try { await Promise.all(handles.map(file => file.close())) }