# Exploit: Obsidian Vault Knowledge Base with PDF-to-Markdown

## Winning Approach

Exploit promoted the explore decision:

- Direct filesystem writes are the durable Obsidian vault path.
- BioMCP-native structured frontmatter search is the reliable retrieval path.
- JATS/XML remains the primary clean article ingest path.
- Open HTML via readability-rust + html2md remains the secondary ingest path.
- PDF-to-Markdown remains a rough fallback for agent readability, with `unpdf` as the default Rust winner and `pdf_oxide` retained as a secondary candidate behind strict timeout handling.
- Obsidian CLI and URI handling are optional handoff layers only.

The full-scale implementation is `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/scripts/run_exploit.py`, which drives the Rust extractor crate under `scripts/rust_probe/`, writes a corrected vault with unique note files, computes checksums, runs validation, and writes contract JSON.

## Full-Scale Results

Full scale is the complete ticket workload: 2 PMC JATS articles, 3 biomedical HTML page types, 3 biomedical PDF document types, Python PDF calibration, Obsidian-compatible vault output, structured vault search, and Obsidian CLI/URI probing.

Measured full-scale results:

| Metric | Value |
| --- | ---: |
| Total elapsed | 141,041 ms |
| JATS success count | 2/2 |
| HTML success count | 3/3 |
| Rust PDF success count | 5/6 |
| All PDF engine success count | 8/9 |
| Vault note records | 8 |
| Vault unique note files | 8 |
| Obsidian CLI working commands | 0 |

PDF winners by document:

| Document | Winning engine | Score | Notes |
| --- | --- | ---: | --- |
| `pmc_oa_article_pdf` | `unpdf` | 4 | Best Rust default for PMC OA article |
| `dailymed_keytruda_label` | `pdf_oxide` | 3 | Better label structure than `unpdf` |
| `cdc_sti_guideline` | `unpdf` | 4 | `pdf_oxide` still timed out at 90 s |

Structured frontmatter search over the exploit vault:

| Query | Matches |
| --- | ---: |
| `type: article` | 4 |
| `type: preprint` | 1 |
| `pmcid: PMC9984800` | 3 |
| `tags: source/pdf` | 3 |
| `doi:` | 8 |

The exploit corrected an explore bug: the JATS and HTML versions of the PMC article used the same title slug, so one note file overwrote the other. The exploit vault now preserves both by appending a deterministic suffix, producing 8 unique note files instead of 8 records backed by 7 unique paths.

## Regression Control

Regression control used the same source dataset, same 12-page PDF limit, and same Rust extractor crate as explore. Result: pass with 0 hard failures.

Success/quality checks all matched or improved:

- JATS: 2/2 success, scores unchanged at 5 and 4.
- HTML: 3/3 success, scores unchanged at 5, 4, and 5.
- Rust PDF: 5/6 success, unchanged; the known `pdf_oxide` CDC guideline timeout remains a controlled failure.
- PDF winners unchanged.
- Vault count improved: 8 unique note files versus the explore duplicate-path mismatch.

Waivers recorded under the regression rule:

| Metric | Explore | Exploit | Rule/cause |
| --- | ---: | ---: | --- |
| `jats.PMC9984800.elapsed_ms` | 23 ms | 43 ms | Measurement noise on sub-500 ms operation, absolute delta <=25 ms |
| `jats.PMC9891841.elapsed_ms` | 64 ms | 78 ms | Measurement noise on sub-500 ms operation, absolute delta <=25 ms |
| `html.pmc_article_page.elapsed_ms` | 258 ms | 313 ms | Same source and unchanged quality/shape metrics; no output-quality regression |
| `html.biorxiv_preprint_page.elapsed_ms` | 141 ms | 150 ms | Measurement noise on sub-500 ms operation, absolute delta <=25 ms |
| `html.nih_news_release.elapsed_ms` | 104 ms | 111 ms | Measurement noise on sub-500 ms operation, absolute delta <=25 ms |
| `pdf.pmc_oa_article_pdf.unpdf.elapsed_ms` | 111 ms | 118 ms | Measurement noise on sub-500 ms operation, absolute delta <=25 ms |
| `pdf.pmc_oa_article_pdf.pdf_oxide.elapsed_ms` | 744 ms | 808 ms | Same source and unchanged quality/shape metrics; no output-quality regression |
| `pdf.cdc_sti_guideline.pymupdf4llm.elapsed_ms` | 3,177 ms | 7,093 ms | Same output metrics; direct rerun produced 3,583 ms, indicating process/environment noise |
| `frontmatter_search.doi:` | 7 | 8 | Correctness improvement from preserving both same-title PMC notes |
| `frontmatter_search.pmcid: PMC9984800` | 2 | 3 | Correctness improvement from preserving both same-title PMC notes |
| `frontmatter_search.type: article` | 3 | 4 | Correctness improvement from preserving both same-title PMC notes |
| Extraction checksums | changed | changed | Allowed because mismatch count improved from 1 duplicate-path mismatch to 0; quality metrics did not regress |

Checksum diff evidence:

- HTML checksum differences are attribute-order-only changes in generated `<img>` tags from the same parser/output shape.
- CDC `unpdf` checksum difference changes heading wrapping for the title page while preserving score 4 and the same winner.
- Vault checksums intentionally changed because unique filenames preserve both PMC article notes and frontmatter timestamps are regenerated.

The regression control JSON is `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/results/kb_regression_control.json`.

## Validation

Ticket validation passed:

- Temporary Obsidian-compatible vault created and populated with 8 unique Markdown notes.
- Source diversity present: JATS, HTML, and PDF notes.
- Proposed frontmatter schema present on all generated notes.
- Structured frontmatter search works for type, PMCID, DOI presence, and tags.
- JATS samples generated for 2 PMC articles.
- HTML samples generated for 3 page types.
- PDF quality matrix generated for 3 documents x 3 engines/calibration paths.
- Obsidian CLI matrix generated for `help`, `search`, `create`, `read`, and `tags`.
- Obsidian URI examples generated without forcing desktop UI.
- Regression control passed.

Remaining gaps:

- Obsidian desktop/CLI remains optional because `/snap/bin/obsidian` still behaves like a desktop wrapper and all tested commands failed noninteractively.
- PDF extraction remains rough. It is acceptable for agent readability, not figure/table-faithful publication reconstruction.
- `pdf_oxide` needs strict timeout/page controls before any default use on large guidelines.

Validation JSON is `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/results/kb_validation.json`.

## Produced Artifacts

Tracked deliverables:

- `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/scripts/run_exploit.py`
- `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/results/kb_full_scale_results.json`
- `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/results/kb_regression_control.json`
- `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/results/kb_validation.json`
- `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/results/kb_contract_numbers.json`
- `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/exploit.md`

Persistent untracked artifacts:

- Vault: `/home/ian/workspace/worktrees/244-spike-obsidian-vault-knowledge-base-with-pdf-to-markdown/architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/work/exploit/obsidian-vault`
- Extracted Markdown/source working data: `/home/ian/workspace/worktrees/244-spike-obsidian-vault-knowledge-base-with-pdf-to-markdown/architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/work/exploit`

No `/tmp/` artifacts are required.

## Contract Numbers

Downstream spikes can depend on these exploit-measured values:

| Contract | Value |
| --- | ---: |
| JATS success | 2/2 |
| HTML success | 3/3 |
| Rust PDF success | 5/6 |
| Overall PDF success including Python calibration | 8/9 |
| Vault note records | 8 |
| Vault unique note files | 8 |
| Duplicate path mismatches | 0 |
| Structured `type: article` matches | 4 |
| Structured `type: preprint` matches | 1 |
| Structured `pmcid: PMC9984800` matches | 3 |
| Structured `tags: source/pdf` matches | 3 |
| Structured `doi:` matches | 8 |
| Obsidian CLI working commands | 0 |

PDF fallback contract:

- Use `unpdf` by default for generic PDFs.
- Prefer `pdf_oxide` only for document shapes where it is known to help, such as the DailyMed label, and always behind timeout/page controls.
- Keep Python `pymupdf4llm` as calibration only, not a Rust product dependency.

Contract JSON is `architecture/experiments/244-obsidian-vault-knowledge-base-with-pdf-to-markdown/results/kb_contract_numbers.json`.

## Conclusions

The winning architecture held at full ticket scale. A local Obsidian vault can be populated directly with durable Markdown, and BioMCP-owned structured frontmatter search is the reliable way to retrieve it.

The exploit run found and fixed one correctness issue from explore: same-title notes could overwrite each other. The corrected vault now has 8 unique files and stronger structured search counts.

JATS and HTML are build-ready paths. PDF is viable only as bounded fallback extraction with clear UX expectations. Obsidian CLI integration should remain optional until a real desktop environment can prove reliable command behavior.
