{"record":{"id":"d4839775d8e6576a","repo":"odysseus-dev/odysseus","slug":"document-pdf-marker-references-an-upload-you-do-no","errorCode":null,"errorMessage":"Document PDF marker references an upload you do not own","messagePattern":"Document PDF marker references an upload you do not own","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"routes/document/document_helpers.py","lineNumber":203,"sourceCode":"\n\ndef _assert_pdf_marker_upload_owned(\n    request: Request,\n    content: str,\n    user: Optional[str],\n    upload_handler: Any,\n) -> None:\n    \"\"\"Reject document content whose pdf_source marker points at another user's upload.\"\"\"\n    if upload_handler is None:\n        return\n    from src.pdf_form_doc import find_source_upload_id\n\n    upload_id = find_source_upload_id(content or \"\")\n    if not upload_id:\n        return\n    auth_manager = getattr(getattr(request.app, \"state\", None), \"auth_manager\", None)\n    if not _resolve_user_upload_path(upload_handler, upload_id, user, auth_manager):\n        raise HTTPException(\n            400,\n            \"Document PDF marker references an upload you do not own\",\n        )\n\n\ndef _derive_title(content: str) -> str:\n    \"\"\"Derive a title from document content.\"\"\"\n    import re\n    if not isinstance(content, str):\n        return \"Untitled\"\n    text = content.strip()\n    if not text:\n        return \"Untitled\"\n\n    # Markdown header\n    md = re.match(r'^#{1,3}\\s+(.+)', text, re.MULTILINE)\n    if md:\n        title = md.group(1).strip()","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_helpers.py#L185-L221","documentation":"Raised (HTTP 400) by _assert_pdf_marker_upload_owned when a document's content contains a pdf_source marker (extracted via src.pdf_form_doc.find_source_upload_id) whose referenced upload cannot be resolved for the requesting user through _resolve_user_upload_path (checks the upload handler plus the app's auth_manager). It prevents persisting a document that embeds another user's uploaded PDF — an authorization check at the content level, not the row level.","triggerScenarios":"Creating or updating a document whose content string carries a pdf_source marker pointing at an upload_id that was deleted, expired, or belongs to a different user. Commonly happens when copying/cloning document content (with its markers) across accounts, or when an upload was garbage-collected between load and save.","commonSituations":"User pastes raw document markdown including an internal pdf_source marker from someone else's doc; upload retention job removed the referenced file; cloning between users copies markers verbatim.","solutions":["Re-upload the PDF under your own account and let the app regenerate the marker, instead of reusing a copied marker.","If the upload was deleted, restore it (or re-import the PDF) so the ID resolves again for you.","Strip stale markers from the content before saving if the PDF linkage is no longer needed."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before saving, confirm every pdf_source marker in the content resolves for this user\nconst markerIds = [...content.matchAll(/pdf_source[:=]\\s*([\\w-]+)/g)].map(m => m[1]);\nfor (const uploadId of new Set(markerIds)) {\n  const ok = await head(`/api/upload/${uploadId}`); // 200 only if owned & present\n  if (!ok) throw new Error(`Upload ${uploadId} unavailable — re-upload the PDF`);\n}","typeGuard":null,"tryCatchPattern":"try { await post('/api/document', { content }); } catch (e) { if (e.status === 400 && /PDF marker/.test(e.message)) { stripStaleMarkers(content); return saveAgain(); } throw e; }","preventionTips":["Never paste raw content containing pdf_source markers between accounts — re-upload instead.","Strip or refresh internal markers when cloning content across users."],"tags":["documents","pdf","uploads","ownership","content-validation"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}