From 834ab66b11a6f6fddeb9f221e3c79d6d5839fe95 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:34:30 +0900 Subject: [PATCH 1/4] fix(data): align PDF DOM upload budget with sidecar --- CHANGELOG.md | 4 ++ backend/api/data.py | 10 +-- backend/tests/test_data_api.py | 4 ++ .../0005-bounded-pdf-dom-upload-contract.md | 61 +++++++++++++++++++ docs/adr/README.md | 1 + .../bounded-pdf-dom-upload-contract.md | 24 ++++++++ 6 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 docs/adr/0005-bounded-pdf-dom-upload-contract.md create mode 100644 docs/doctoring/bounded-pdf-dom-upload-contract.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f31c701a5..3363717bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ ## [Unreleased] +- 수동 PDF DOM 인식 업로드도 이메일 첨부 및 NewsDOM sidecar와 같은 64MiB + bounded transport 계약을 사용합니다. 한도를 넘으면 PDF를 저장하거나 + sidecar에 전달하지 않고 `413`을 반환하므로 고객은 파일을 분할하거나 + 대용량 저장소 경로를 사용할 수 있습니다. - EmailDetail 테스트가 지원하지 않는 스레드 병합/분리 버튼을 `textContent`뿐 아니라 `aria-label`과 `title` 접근 가능 이름으로도 검출하도록 바꿔, 아이콘 전용 버튼 회귀를 놓치지 않습니다. ### 캘린더 충돌 (Status-weighted conflicts) diff --git a/backend/api/data.py b/backend/api/data.py index dccd85890..edf587a27 100644 --- a/backend/api/data.py +++ b/backend/api/data.py @@ -40,11 +40,11 @@ router = APIRouter(prefix="/api/data", tags=["data"]) DATA_VECTOR_DIMENSIONS = 1536 -# Upper bound for the binary PDF DOM recognition upload variant. Kept in step -# with the NewsDOM sidecar's own MAX_PARSE_UPLOAD_BYTES (20 MiB): accepting more -# would let a caller stash a pending document the configured sidecar will always -# reject while the base64 copy inflates the database. -_MAX_PDF_DOM_UPLOAD_BYTES = 20 * 1024 * 1024 +# Upper bound for the binary PDF DOM recognition upload variant. Keep this in +# step with NewsDOM's MAX_PARSE_UPLOAD_BYTES and the signed email-import +# transport ceiling so large customer PDFs are not accepted by one path and +# rejected by the next. +_MAX_PDF_DOM_UPLOAD_BYTES = 64 * 1024 * 1024 ATTACHMENT_PARSE_BREAKDOWN_EVIDENCE_SOURCE = ( "email_attachments.content_type, " "email_attachments.parse_content_type, " diff --git a/backend/tests/test_data_api.py b/backend/tests/test_data_api.py index cd0b7bf37..7b8d3c7b2 100644 --- a/backend/tests/test_data_api.py +++ b/backend/tests/test_data_api.py @@ -2898,6 +2898,10 @@ def test_data_pdf_dom_upload_persists_signed_organization_scope(mock_db): assert stored_document.document_status == "pdf_dom_recognition_pending" +def test_data_pdf_dom_upload_budget_matches_newsdom_transport_contract(): + assert data_api._MAX_PDF_DOM_UPLOAD_BYTES == 64 * 1024 * 1024 + + def test_data_pdf_dom_upload_rejects_invalid_signature_and_size(mock_db, monkeypatch): token = _signed_session_token(_valid_session_payload()) client, previous_secret, original_overrides = _with_signed_auth(mock_db, token) diff --git a/docs/adr/0005-bounded-pdf-dom-upload-contract.md b/docs/adr/0005-bounded-pdf-dom-upload-contract.md new file mode 100644 index 000000000..096551971 --- /dev/null +++ b/docs/adr/0005-bounded-pdf-dom-upload-contract.md @@ -0,0 +1,61 @@ +# ADR-0005: Bounded PDF DOM upload contract + +**Status:** Accepted +**Date:** 2026-08-20 +**Decision owner:** Naruon maintainers +**Scope:** Signed `POST /api/data/documents/pdf-dom-recognition` uploads + +## Context + +Naruon email imports and the NewsDOM sidecar are being aligned to a 64MiB +bounded PDF transport budget. The direct Data workspace upload still used an +independent 20MiB limit, so a customer could upload a large PDF through email +but receive an avoidable `413` when using the equivalent manual workflow. + +## Decision + +Set the direct PDF DOM upload and its pending-payload decoder to 64MiB. Keep the +signed-session boundary, PDF signature validation, one-byte-over-limit read, +base64 persistence contract, and `413` response unchanged. This ADR records +Naruon's consumer-side contract; the NewsDOM sidecar has its own ADR and must +pass its own checks before deployment. + +## Consequences + +- Email and manual PDF ingestion present the same bounded size expectation to + customers. +- The endpoint can persist more temporary database content, so existing + workspace quotas, background worker limits, and database capacity monitoring + remain required. +- No unbounded upload is introduced, and malformed or non-PDF payloads continue + to fail closed before recognition. + +## Alternatives rejected + +### Keep a separate 20MiB manual-upload limit + +Rejected because it creates a customer-visible workflow inconsistency without a +different safety property after the sidecar contract is raised. + +### Remove the upload limit + +Rejected because request and database resource use must remain bounded at the +authenticated trust boundary. + +## References (APA 7th) + +Internet Engineering Task Force. (2022). *HTTP semantics (RFC 9110).* RFC + Editor. https://www.rfc-editor.org/rfc/rfc9110 + +RFC 9110 supports retaining an explicit `413 Payload Too Large` response when a +request exceeds the server's permitted content size. + +National Institute of Standards and Technology. (2025). *Secure software + development framework (SSDF) version 1.2* (NIST Special Publication 800-218 + Rev. 1, Initial Public Draft). https://doi.org/10.6028/NIST.SP.800-218r1.ipd + +The SSDF supports auditable input bounds, regression tests, and operational +controls for untrusted-input boundary changes. + +No customer data or source PDF is included; the standards remain linked to their +authoritative publishers. diff --git a/docs/adr/README.md b/docs/adr/README.md index 4d461fff6..e857e04c2 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -13,6 +13,7 @@ govern implementation. | [ADR-0002](0002-fitted-topic-artifact-consumption.md) | Conditionally consume only a versioned fitted topic artifact through a fail-closed adapter | Proposed | Target `PLANNED`; runtime `BLOCKED-UPSTREAM` | | [ADR-0003](0003-separate-topic-measurement-from-agenda-generation.md) | Keep statistical measurement separate from agenda generation | Proposed | Target and future capability `PLANNED`; no implementation authorization | | [ADR-0004](0004-status-weighted-calendar-conflicts.md) | Evaluate CalDAV VEVENT overlaps by occupying status; cancelled does not occupy | Accepted | `ACCEPTED-NARUON-POLICY`; advisory evaluate API only | +| [ADR-0005](0005-bounded-pdf-dom-upload-contract.md) | Keep direct PDF DOM uploads aligned with the 64MiB NewsDOM transport contract | Accepted | `ACCEPTED-NARUON-POLICY`; sidecar deployment remains separately governed | The complete topic-intelligence requirements, architecture, contract, UML, conceptual ERD, security, test, and operability graph is indexed at diff --git a/docs/doctoring/bounded-pdf-dom-upload-contract.md b/docs/doctoring/bounded-pdf-dom-upload-contract.md new file mode 100644 index 000000000..aaf40044d --- /dev/null +++ b/docs/doctoring/bounded-pdf-dom-upload-contract.md @@ -0,0 +1,24 @@ +# Doctoring record: bounded PDF DOM upload contract + +**Observed gap:** The signed Data workspace PDF upload rejected files above +20MiB even though the aligned email-import and NewsDOM transport contracts +accept up to 64MiB. + +**Correction:** The upload endpoint and pending-payload decoder now share a +64MiB bound. Signature validation, signed-session authorization, base64 storage, +worker deferral, and `413` rejection remain fail-closed. + +**Evidence:** `backend/tests/test_data_api.py` asserts the 64MiB contract and +keeps the over-limit test monkeypatched to a small fixture. The NewsDOM sidecar +alignment is tracked separately in its own ADR and PR. + +**References (APA 7th):** + +- Internet Engineering Task Force. (2022). *HTTP semantics (RFC 9110).* RFC + Editor. https://www.rfc-editor.org/rfc/rfc9110 +- National Institute of Standards and Technology. (2025). *Secure software + development framework (SSDF) version 1.2* (NIST SP 800-218 Rev. 1, Initial + Public Draft). https://doi.org/10.6028/NIST.SP.800-218r1.ipd + +No customer or private reference data was read, and no source PDF is +redistributed. From e43cd00f322f945dc05f025a1c41af0c9eb575b8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:46:08 +0900 Subject: [PATCH 2/4] docs: normalize pdf upload ADR formatting --- docs/adr/0005-bounded-pdf-dom-upload-contract.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/adr/0005-bounded-pdf-dom-upload-contract.md b/docs/adr/0005-bounded-pdf-dom-upload-contract.md index 096551971..36dbd2147 100644 --- a/docs/adr/0005-bounded-pdf-dom-upload-contract.md +++ b/docs/adr/0005-bounded-pdf-dom-upload-contract.md @@ -1,8 +1,8 @@ # ADR-0005: Bounded PDF DOM upload contract -**Status:** Accepted -**Date:** 2026-08-20 -**Decision owner:** Naruon maintainers +**Status:** Accepted +**Date:** 2026-08-20 +**Decision owner:** Naruon maintainers **Scope:** Signed `POST /api/data/documents/pdf-dom-recognition` uploads ## Context From 29be15e4ec5e29dc1f62ac636928c9307a6f520f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 06:33:45 -0700 Subject: [PATCH 3/4] docs(adr): record PDF DOM design traceability --- docs/adr/0005-bounded-pdf-dom-upload-contract.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/adr/0005-bounded-pdf-dom-upload-contract.md b/docs/adr/0005-bounded-pdf-dom-upload-contract.md index 36dbd2147..c3a0f22a3 100644 --- a/docs/adr/0005-bounded-pdf-dom-upload-contract.md +++ b/docs/adr/0005-bounded-pdf-dom-upload-contract.md @@ -4,6 +4,7 @@ **Date:** 2026-08-20 **Decision owner:** Naruon maintainers **Scope:** Signed `POST /api/data/documents/pdf-dom-recognition` uploads +**Figma File ID:** N/A — backend upload contract; no visual surface. ## Context From ecf023ce3ad4efd804dd373834883a1e9632c391 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 5 Sep 2026 10:58:46 +0900 Subject: [PATCH 4/4] docs(pdf): require immutable NewsDOM release Co-Authored-By: Codex Signed-off-by: Seongho Bae --- CHANGELOG.md | 3 ++- docs/adr/0005-bounded-pdf-dom-upload-contract.md | 16 ++++++++-------- docs/adr/README.md | 2 +- .../doctoring/bounded-pdf-dom-upload-contract.md | 8 +++++--- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d88dd2cb6..887d6edb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ - 수동 PDF DOM 인식 업로드도 이메일 첨부 및 NewsDOM sidecar와 같은 64MiB bounded transport 계약을 사용합니다. 한도를 넘으면 PDF를 저장하거나 sidecar에 전달하지 않고 `413`을 반환하므로 고객은 파일을 분할해 - 다시 업로드할 수 있습니다. + 다시 업로드할 수 있습니다. 이 변경은 NewsDOM의 64MiB immutable release를 + Naruon이 exact pin한 뒤에만 병합합니다. - PostgreSQL bootstrap now creates the owner/date index only on the canonical `email_records` table after email-model reconciliation, and real smoke seeds provide the required `is_read` state. Fresh-schema bootstrap and data-quality diff --git a/docs/adr/0005-bounded-pdf-dom-upload-contract.md b/docs/adr/0005-bounded-pdf-dom-upload-contract.md index c3a0f22a3..b866de3ba 100644 --- a/docs/adr/0005-bounded-pdf-dom-upload-contract.md +++ b/docs/adr/0005-bounded-pdf-dom-upload-contract.md @@ -1,6 +1,6 @@ # ADR-0005: Bounded PDF DOM upload contract -**Status:** Accepted +**Status:** Proposed **Date:** 2026-08-20 **Decision owner:** Naruon maintainers **Scope:** Signed `POST /api/data/documents/pdf-dom-recognition` uploads @@ -15,16 +15,16 @@ but receive an avoidable `413` when using the equivalent manual workflow. ## Decision -Set the direct PDF DOM upload and its pending-payload decoder to 64MiB. Keep the -signed-session boundary, PDF signature validation, one-byte-over-limit read, -base64 persistence contract, and `413` response unchanged. This ADR records -Naruon's consumer-side contract; the NewsDOM sidecar has its own ADR and must -pass its own checks before deployment. +After NewsDOM publishes an immutable 64MiB transport release and Naruon pins +that exact release, set the direct PDF DOM upload and its pending-payload +decoder to 64MiB. Keep the signed-session boundary, PDF signature validation, +one-byte-over-limit read, base64 persistence contract, and `413` response +unchanged. Until then this consumer change remains Draft and must not be merged. ## Consequences -- Email and manual PDF ingestion present the same bounded size expectation to - customers. +- After the owner release is pinned, email and manual PDF ingestion present the + same bounded size expectation to customers. - The endpoint can persist more temporary database content, so existing workspace quotas, background worker limits, and database capacity monitoring remain required. diff --git a/docs/adr/README.md b/docs/adr/README.md index e857e04c2..41d1762d6 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -13,7 +13,7 @@ govern implementation. | [ADR-0002](0002-fitted-topic-artifact-consumption.md) | Conditionally consume only a versioned fitted topic artifact through a fail-closed adapter | Proposed | Target `PLANNED`; runtime `BLOCKED-UPSTREAM` | | [ADR-0003](0003-separate-topic-measurement-from-agenda-generation.md) | Keep statistical measurement separate from agenda generation | Proposed | Target and future capability `PLANNED`; no implementation authorization | | [ADR-0004](0004-status-weighted-calendar-conflicts.md) | Evaluate CalDAV VEVENT overlaps by occupying status; cancelled does not occupy | Accepted | `ACCEPTED-NARUON-POLICY`; advisory evaluate API only | -| [ADR-0005](0005-bounded-pdf-dom-upload-contract.md) | Keep direct PDF DOM uploads aligned with the 64MiB NewsDOM transport contract | Accepted | `ACCEPTED-NARUON-POLICY`; sidecar deployment remains separately governed | +| [ADR-0005](0005-bounded-pdf-dom-upload-contract.md) | Keep direct PDF DOM uploads aligned with the 64MiB NewsDOM transport contract | Proposed | `BLOCKED-UPSTREAM`; requires an immutable NewsDOM 64MiB release and exact Naruon pin | The complete topic-intelligence requirements, architecture, contract, UML, conceptual ERD, security, test, and operability graph is indexed at diff --git a/docs/doctoring/bounded-pdf-dom-upload-contract.md b/docs/doctoring/bounded-pdf-dom-upload-contract.md index aaf40044d..17cc1d330 100644 --- a/docs/doctoring/bounded-pdf-dom-upload-contract.md +++ b/docs/doctoring/bounded-pdf-dom-upload-contract.md @@ -4,9 +4,11 @@ 20MiB even though the aligned email-import and NewsDOM transport contracts accept up to 64MiB. -**Correction:** The upload endpoint and pending-payload decoder now share a -64MiB bound. Signature validation, signed-session authorization, base64 storage, -worker deferral, and `413` rejection remain fail-closed. +**Proposed correction:** After NewsDOM publishes an immutable 64MiB release and +Naruon pins it exactly, the upload endpoint and pending-payload decoder will +share a 64MiB bound. Signature validation, signed-session authorization, base64 +storage, worker deferral, and `413` rejection remain fail-closed. The Naruon PR +stays Draft until that owner evidence exists. **Evidence:** `backend/tests/test_data_api.py` asserts the 64MiB contract and keeps the over-limit test monkeypatched to a small fixture. The NewsDOM sidecar