Skip to content

docs(plan): store sources instead of parsing them, and let the agent read them - #24

Merged
protonspy merged 1 commit into
mainfrom
feat/sources-stored-not-parsed
Aug 1, 2026
Merged

docs(plan): store sources instead of parsing them, and let the agent read them#24
protonspy merged 1 commit into
mainfrom
feat/sources-stored-not-parsed

Conversation

@protonspy

@protonspy protonspy commented Aug 1, 2026

Copy link
Copy Markdown
Owner

The correction

The application takes four extensions and refuses the rest, and for two of them runs a
text extractor whose output is what every downstream reader consumes. That was decided
task by task — 3.2, 3.3, 3.4 each named a format and an adapter — and never as one
decision anybody looked at whole.

Looked at whole, it contradicts plans/open-wiki.md's own out-of-scope clause:

Extraction, summarisation or page writing by the application. That is the agent's.

So: the application stores the original and records what happened to it. Reading it is
the agent's job.
An agent that opens a PDF as a document and an image as an image
keeps layout, tables, diagrams and figures — exactly what a text extraction drops. And
the set of things a source can be stops being a list somebody maintains.

Two findings that shape the work

Document provenance was never validated. store/provenance.ts:37 is /^p\d+$/ — it
checks that a fragment looks like p12, never that page 12 exists. src://report.pdf#p999
resolves today. The gap 3.4 recorded for DOCX was never specific to DOCX. Worth stating
plainly, because "this removes document provenance validation" would be a reasonable
objection and it is not true — there is none to remove.

retitleSource is in the desktop main process (apps/desktop/src/main/edit.ts:408),
not in @open-wiki/access. So the CLI cannot mutate a manifest, and the status verb
would be a second mutator in a second place, against 9.1. The move comes with this work
rather than after it.

Also: the extension table exists twiceupload.ts:35-40 and
apps/desktop/src/main/ingest.ts:24-29. Collapsing it is task 2.1, before anything else
edits ingest.

The one persisted field, and why 6.1 survives

6.1 decided source state is derived, never persisted, "because the copy is the one that
goes stale". This does not override it. It observes that one fact is not derivable:

  • Agent read the source and wrote pages → derivable, it is cited.
  • Agent read the source and found nothing worth writing → no trace anywhere.

Today that second case is indistinguishable from a source nobody opened, so 6.6 reports
every deliberately-discarded source as a permanent finding. A check that cries wolf is a
check people stop reading.

So: one declared fact, everything else stays derived. processed is a judgement,
which is the class of thing a filesystem cannot observe. Nothing derivable moves into it.

Where risk moves

Narrows for the application. pdf.ts parses a stranger's bytes in the privileged
main process today — the exact risk 3.7 refused for the inbox, running on the drop path.
Storing bytes removes a parser from the trusted process.

Widens for the agent. It already reads untrusted source text — 4.13 found a
fabricated ## 3:00 heading inside a transcript that survived the provenance check.
Handing it PDFs and images extends that to files nobody looked at. Not a reason to
refuse; a reason task 5.2 makes the skill say a source is evidence, not instructions.

What it supersedes in plans/open-wiki.md

3.2/3.3/3.4 (adapters), 3.5 (the drop report), 6.1/6.6 (state and the uncited check),
9.10 (MCP handing back text.md). Not un-ticked — they shipped and were correct against
what was decided then.

Left open, deliberately

Document provenance stays unvalidated, and the application will no longer even hold the
page count. Closing it later means a metadata probe that reads structure without
extracting text, or accepting that a document is cited whole. Recorded now, because "we
removed the parser" is otherwise the story of how it became unfixable.

How it was verified

npx @protonspy/scc validate — no findings. prettier --check — clean. Markdown only.

🤖 Generated with Claude Code

https://claude.ai/code/session_018SiqL2owT9tBkcpVhu423N

Summary by CodeRabbit

  • Documentation
    • Added planning documentation for preserving uploaded source files and improving source-status handling.
    • Added templates for documenting source-status requirements, design decisions, and implementation tasks.
    • Included guidance for requirement formatting, task sizing, validation, workflow practices, risks, and out-of-scope items.
    • Documented potential future workflows for handling different file formats and preserving provenance.

…read them

The application takes four extensions and refuses the rest, and for PDF and DOCX
runs a text extractor whose output is what every downstream reader consumes.
That was decided task by task — 3.2, 3.3, 3.4 each named a format — and never as
one decision. Looked at whole it contradicts the plan's own out-of-scope clause:
"Extraction, summarisation or page writing by the application. That is the
agent's."

An agent that opens a PDF as a document and an image as an image keeps layout,
tables, diagrams and figures — exactly what a text extraction drops. So the
application stores the original and records what happened to it.

Two findings that shape the work:

- Document provenance was never validated. FILE_FRAGMENT is /^p\d+$/ and checks
  the shape only, so src://report.pdf#p999 resolves today. The gap 3.4 recorded
  for DOCX was never specific to DOCX. This plan does not weaken document
  provenance, because there is none to weaken.
- retitleSource lives in the desktop main process, not in @open-wiki/access, so
  the CLI cannot mutate a manifest. A status verb would be a second mutator in a
  second place, against 9.1 — so the move comes with this work.

Reconciles the one persisted field with 6.1's derived-not-persisted rule rather
than overriding it: "the agent read this and found nothing worth writing" leaves
no trace on the filesystem and is not derivable, while everything else still is.
Without it every deliberately discarded source is a permanent 6.6 finding, and a
check that cries wolf stops being read.

Also notes where the risk moves: the application stops parsing hostile bytes in
its privileged main process, and the agent starts opening files nobody looked
at — so the skill has to say a source is evidence, not instructions.

Scaffolds specs/source-status/, which the plan references.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018SiqL2owT9tBkcpVhu423N
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds an implementation plan for preserving uploaded sources and introduces requirements, design, and task templates for source-status work.

Changes

Source storage and status planning

Layer / File(s) Summary
Raw source lifecycle plan
plans/sources-stored-not-parsed.md
Defines storage under raw/, Markdown and text copying to text.md, persisted processed status, CLI and agent workflows, UI and MCP updates, provenance limits, risks, and task order.
Source-status specification templates
specs/source-status/requirements.md, specs/source-status/design.md, specs/source-status/tasks.md
Adds templates for source-status requirements, design decisions, and implementation tasks with traceability, EARS, TDD, verification, and sizing guidance.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main planned change: storing original sources instead of parsing them and assigning source reading to the agent.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sources-stored-not-parsed

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plans/sources-stored-not-parsed.md`:
- Line 154: Update the provenance viewer requirement in section 6.4 to restrict
previews and external opening to an explicit allowlist of safe file types; do
not send arbitrary stored files to the system handler. Treat unsupported or
potentially active types as download/reveal-only, requiring explicit user
confirmation before any external opening.
- Around line 76-79: Define explicit upgrade semantics for existing source
directories: specify how manifests missing the persisted processed field are
defaulted or backfilled, and how legacy application-generated text.md files are
retained, ignored, or removed. Align sources/state.ts text-ready derivation and
packages/mcp/src/tools.ts reporting with that policy so equivalent sources
expose consistent state and content regardless of ingestion time.
- Line 120: Update the storage plan around item 2.2 and the related
status-specification sections to explicitly define replacement behavior: either
reject an already-used source name or create a new source and manifest,
consistently. Add acceptance criteria requiring raw files to remain immutable
after writing and requiring atomic writes for both raw-file creation and
manifest mutations.
- Line 122: Define a concrete, testable size policy for checklist item 2.4:
specify the maximum accepted file size, how size is confirmed before dropping,
the refusal behavior when the limit is exceeded, and the information reported
for both drop and inbox outcomes. Ensure the policy constrains the unbounded
file acceptance described by item 2.3 and has observable behavior suitable for
unit tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e9a758b-df6f-4c44-b534-480cef71890b

📥 Commits

Reviewing files that changed from the base of the PR and between e40bc7b and 79e5c85.

📒 Files selected for processing (4)
  • plans/sources-stored-not-parsed.md
  • specs/source-status/design.md
  • specs/source-status/requirements.md
  • specs/source-status/tasks.md

Comment on lines +76 to +79
So: **one declared fact, and everything else stays derived.** `processed` is a
judgement somebody made, which is exactly the class of thing a filesystem cannot
observe. It is not a cache of `text.md`, and nothing derivable moves into it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Define upgrade semantics for existing source directories.

The plan adds a persisted processed field and stops application-generated text.md for new PDF/DOCX sources. It does not define how existing manifests without processed or existing extracted text.md files are handled. sources/state.ts:120 still derives text-ready from text.md, and packages/mcp/src/tools.ts:98 still returns it. Equivalent sources could therefore expose different state and content based only on when they were ingested. Define the default or backfill for processed and the policy for legacy text.md files.

Also applies to: 102-109

🧰 Tools
🪛 LanguageTool

[style] ~77-~77: Consider an alternative for the overused word “exactly”.
Context: ... is a judgement somebody made, which is exactly the class of thing a filesystem cannot ...

(EXACTLY_PRECISELY)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plans/sources-stored-not-parsed.md` around lines 76 - 79, Define explicit
upgrade semantics for existing source directories: specify how manifests missing
the persisted processed field are defaulted or backfilled, and how legacy
application-generated text.md files are retained, ignored, or removed. Align
sources/state.ts text-ready derivation and packages/mcp/src/tools.ts reporting
with that policy so equivalent sources expose consistent state and content
regardless of ingestion time.

## 2 — Accept any file

- [ ] 2.1 (Unit) Collapse the two adapter tables into one place in `@open-wiki/access`. The desktop's copy is drift waiting to happen, and this work would otherwise edit both
- [ ] 2.2 (TDD) Store any file: the original is preserved under `raw/<id>/`, the id is still derived and frozen per `adr:0011`, and nothing is refused for its extension. Test-first because it is the write path where a mistake is silent — a file accepted and stored under the wrong name, or outside `raw/`, is not visible until somebody goes looking

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Resolve replacement semantics and preserve atomic storage writes.

docs/adr/0002-workspace-as-a-local-markdown-folder.md defines raw sources as immutable after writing and requires atomic writes. This plan says the original is preserved, but it does not make those guarantees acceptance criteria. It also asks the status specification to define what happens when a source is replaced, while the plan refuses an already-used name. Decide whether replacement is rejected or creates a new source and manifest. Require atomic writes for raw files and manifest mutations.

Also applies to: 123-123, 129-134, 138-139

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plans/sources-stored-not-parsed.md` at line 120, Update the storage plan
around item 2.2 and the related status-specification sections to explicitly
define replacement behavior: either reject an already-used source name or create
a new source and manifest, consistently. Add acceptance criteria requiring raw
files to remain immutable after writing and requiring atomic writes for both
raw-file creation and manifest mutations.

- [ ] 2.1 (Unit) Collapse the two adapter tables into one place in `@open-wiki/access`. The desktop's copy is drift waiting to happen, and this work would otherwise edit both
- [ ] 2.2 (TDD) Store any file: the original is preserved under `raw/<id>/`, the id is still derived and frozen per `adr:0011`, and nothing is refused for its extension. Test-first because it is the write path where a mistake is silent — a file accepted and stored under the wrong name, or outside `raw/`, is not visible until somebody goes looking
- [ ] 2.3 (Unit) Stop extracting on ingest. `.md` and `.txt` still get a `text.md`, because copying text that is already text is not extraction; PDF and DOCX no longer do
- [ ] 2.4 (Unit) A size stance, said out loud at the moment of the drop rather than discovered in `git status`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make the size policy testable.

Line 122 requires “a size stance” but does not define a limit, confirmation step, refusal behavior, or drop/inbox report. Because Line 120 accepts any file, an implementation could consume unbounded disk space. Specify the policy and its observable failure behavior before implementation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plans/sources-stored-not-parsed.md` at line 122, Define a concrete, testable
size policy for checklist item 2.4: specify the maximum accepted file size, how
size is confirmed before dropping, the refusal behavior when the limit is
exceeded, and the information reported for both drop and inbox outcomes. Ensure
the policy constrains the unbounded file acceptance described by item 2.3 and
has observable behavior suitable for unit tests.

- [ ] 6.1 (Unit) The sources pane distinguishes stored, unprocessed, processed and cited — and offers the one action that is not the agent's, which is marking something processed by hand
- [ ] 6.2 (Unit) 6.6's uncited check reports only what is unprocessed *and* uncited, so a source somebody read and discarded stops being a permanent finding
- [ ] 6.3 (Unit) MCP says what it has: a source with no `text.md` reports its status and its filename rather than returning nothing, so a consulting agent knows the difference between empty and unread. It cannot read the original — that project is not on its disk — and saying so is the honest answer
- [ ] 6.4 (Unit) The provenance viewer opens what it is given: an image as an image, a PDF at its page, anything else named and offered to the system handler. The renderer's CSP is `default-src 'none'` and `img-src 'self' data:`, so this is a real constraint and not a formality

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Do not send arbitrary stored files to the system handler.

Line 154 offers “anything else” to the system handler while Line 120 accepts any extension. A malicious .lnk, .url, script, executable, or active document could launch an external application. The renderer CSP does not constrain that handler. Restrict previews to safe types. Make unsupported types download/reveal-only, with explicit confirmation before external opening.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plans/sources-stored-not-parsed.md` at line 154, Update the provenance viewer
requirement in section 6.4 to restrict previews and external opening to an
explicit allowlist of safe file types; do not send arbitrary stored files to the
system handler. Treat unsupported or potentially active types as
download/reveal-only, requiring explicit user confirmation before any external
opening.

@protonspy
protonspy merged commit 33e6d48 into main Aug 1, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant