016 - Confluence attachment conversion (pull-side)
Purpose: Extend mdd confluence sync and export so non-image attachments are run through the converter registry, producing markdown siblings.
Status: Implemented (2026-05-08)
Introduction
Section titled “Introduction”During mdd confluence sync (S14) and export (S09),
non-image attachments — .docx, .pptx, .pdf, .svg — are downloaded and
run through the converter registry (S15), producing markdown
siblings inside the page’s <page-name>-attachments/ directory. The original
binary stays for fidelity; the markdown sibling lets readers grep, diff, and
review in the git mirror without leaving the browser.
This is the pull side of the bidirectional Office story described in research note R02. The push side (rendering markdown to office-format attachments) is S17.
Requirements
Section titled “Requirements”Attachment download
- During
mdd confluence sync, after the page body and image attachments are fetched (per S09), enumerate all attachments viaGET /wiki/api/v2/pages/{id}/attachments. - For each attachment whose
mediaTypeand/or filename extension is in the converter registry, download it to<page-name>-attachments/<filename>. - Stream downloads (do not buffer the whole body) to support large PDFs.
- Honour
Retry-Afteron 429 responses (overrides the S09 fixed schedule when the header is present, capped at 60s). - Default concurrency cap of 4 in-flight downloads (config:
confluence.attachment_concurrency).
Attachment conversion
- After download, call
converter_for(filename)from S15. If a converter is found, run it; the converter writes<filename>.md(or its declaredoutput_suffix) next to the original. If no converter is found, leave the binary alone. - Cache by SHA-256 of the source file: skip conversion if the
recorded
attachments_manifestentry’s SHA matches the current file and the converter’s version hasn’t changed.
Attachment manifest
S09’s frontmatter attachments manifest grows a per-entry
converted_to: <filename> field when conversion ran. The manifest is
the authoritative cache; sync uses it to skip unchanged attachments.
attachments: - filename: ArchitectureSpec.docx sha256: 9f86d081... version: 3 converted_to: ArchitectureSpec.docx.md converter: docling-docx converter_version: "2.4.0" - filename: BudgetSheet.xlsx sha256: e3b0c442... version: 1 # no converted_to: not in registryPush-side reference rewriting
- When
mdd confluence update-page(S09) renders markdown back to storage XHTML, references to attachment files (e.g.[Spec](attachments/ArchitectureSpec.docx)) continue to point at the binary on Confluence — not the.mdsibling. The.mdis a mirror-side artifact only. - Note: this spec does not rewrite SVG references. Per user
decision, SVGs push to Confluence as
.svg, not as.svg.png. The PNG sibling exists for Office-side renders only.
Error handling
- If a converter raises, sync logs the failure with the page ID and attachment filename, skips the attachment, and continues. The page itself is still synced; the attachment-converted markdown is simply absent for this run. Reported in the run summary; sync exits non-zero per S14’s best-effort policy.
- Conversion failures do not block subsequent attachments on the same page.
Size limits
- No hard cap by default; rely on Confluence’s per-attachment 100 MB limit upstream.
- Add
--max-attachment-size <MB>to skip downloads above the threshold, with a warning naming the attachment. Useful for bandwidth-constrained first runs. - Per-attachment file size is shown in the run summary if it exceeds 10 MB.
Design Approach
Section titled “Design Approach”- The hook into S09’s existing
mdd.confluence.attachmentsmodule is minimal: after each attachment download, look upconverter_for(local)and run it if registered, updating the manifest with the converted output path and the converter’s version. - The conversion-needed predicate checks SHA mismatch, converter version bump, and missing output file — so a deleted markdown sibling regenerates next run.
- Bound concurrent downloads with
asyncio.Semaphore(N)(or a thread pool of size N if we stay synchronous). Default 4. Conversion itself is synchronous and sequential — converters are CPU/IO mixed and don’t benefit from concurrency at our scales. - HTTP retry layer is a small extension to S09’s client: on
429, prefer the
Retry-Afterheader value over the static schedule, capped at 60 seconds, falling through to the schedule if the header is absent. - One summary line per page after attachment sync (e.g.
12 attachments synced (3 converted, 1 skipped, total 18 MB)), aggregated into S14’s structured run summary.
Related upstream specs
Section titled “Related upstream specs”- 000-specs — shared conventions
- 009-confluence-command — confluence export/sync command
- 014-confluence-sync — incremental sync and best-effort policy
- 015-converter-registry — pluggable converter registry
- 017-confluence-office-publishing — push side of the bidirectional Office story
- 024-svg-rasterization — SVG rasterization
Out of scope
Section titled “Out of scope”- Push-side rendering of markdown to office-format attachments (spec 017 handles this).
- SVG rasterization to
.svg.png(S24 handles this; this spec uses the converter registry which S24 will populate). - Adopting an existing Confluence attachment as the canonical source
(e.g. discovering a
.docxand turning it into a markdown master with the office file as a derived render). One-shot migration feature; deferred. - Live re-conversion when the converter version bumps but the file
hasn’t changed: handled by including
converter_versionin the cache key — next run picks it up automatically. - Custom per-page converter overrides: registry dispatch is sufficient.
Site built 2026-07-30.