Skip to content

fix: map guest paths on document payloads and diagnostics - #281

Merged
robcohen merged 1 commit into
mainfrom
fix/document-guest-paths
Aug 10, 2026
Merged

fix: map guest paths on document payloads and diagnostics#281
robcohen merged 1 commit into
mainfrom
fix/document-guest-paths

Conversation

@robcohen

Copy link
Copy Markdown
Member

Closes #277.

The bug

_rewrite_guest_paths mapped meta.filename and the include list. But a document directive has a second, independent path — the file it points at — and that one was never mapped.

For entries produced by discovery the rewriter was a complete no-op: they are synthesized, so they carry no source location at all (meta.filename is <unknown>), and the only real path on them is exactly the one being skipped.

Every auto-discovered document was therefore unreachable:

  • statement_path resolves a transaction's document: value to an absolute host path, then looks for a Document whose filename equals it. A /work/… filename can never match, so /statement/ always raised StatementNotFoundError.
  • /document/ is gated on the same values, and send_file was handed a path with no host file behind it.

Hand-written document directives were unaffected — rledger stores directive filenames verbatim and they never transit the sandbox. That's what isolated the failure to discovery, and what hid it.

Reported on Windows; I reproduced it on Linux, so nothing about it is platform-specific.

Fix

Rewrite the document payload path, and guest paths embedded in diagnostic messages (surfaced in the UI):

before  Document.filename: /work/docs/Expenses/Foo/2026-07-07 account-shaped.pdf
after   Document.filename: /…/probe/docs/Expenses/Foo/2026-07-07 account-shaped.pdf   (exists: True)

Two details worth flagging:

  • The wire key is path, not filename. filename is the attribute name on the typed Document; the raw result dict uses path. The issue said "payload filename", which is right about the object and wrong about the key.
  • Messages are relocated by prefix, not parsed. A path inside a message is a substring with no delimiter, and a document filename may contain spaces — so there's no reliable way to find where one ends. Replacing the mount prefix sidesteps that entirely. The end-to-end test deliberately uses a filename with a space in it.

Gated on the directive type even though path is unique to document-dir among output directives today, so a future directive that gains a path meaning something other than a host file isn't silently rewritten.

Tests

Two tests — one on the raw shapes, one end-to-end through a real component asserting the mapped path actually exists on disk.

Sabotage-checked, each failure direction landing on a distinct assertion:

sabotage fails
drop the document payload rewrite unit and end-to-end
drop the message rewrite the message assertion only
remove the type gate the non-document path case only

Full suite against the pinned v0.21.0 asset: 669 passed, 1 skipped (667 before, +2 new).

🤖 Generated with Claude Code

https://claude.ai/code/session_018bGRsKA42peqSnz4VMreBG

_rewrite_guest_paths mapped meta.filename and the include list, but a
document directive has a second, independent path - the file it points
at - and that one was never mapped. For entries produced by discovery
the rewriter was a complete no-op, because they are synthesized and
carry no source location at all (meta.filename is <unknown>), so the
only real path on them is the one being skipped.

Every auto-discovered document was therefore unreachable:

  - statement_path resolves a transaction's document: value to an
    absolute host path and looks for a Document whose filename equals
    it. A /work/... filename can never match, so /statement/ always
    raised StatementNotFoundError.
  - /document/ is gated on the same values, and send_file was handed a
    path with no host file behind it.

Hand-written document directives were unaffected, since rledger stores
directive filenames verbatim and they never transit the sandbox. That is
what isolated the failure to discovery and hid it.

Guest paths embedded in diagnostic messages are mapped too. They are
surfaced in the UI, and they are substrings rather than whole fields, so
the mount prefix is relocated rather than the path parsed out - a
document filename may contain spaces, so there is no reliable way to
find where one ends.

The wire key is path, not filename; filename is the attribute name on
the typed Document. Gated on the directive type even though path is
unique to document-dir among output directives today, so a future
directive that gains a path meaning something other than a host file is
not silently rewritten.

Two tests, one on the raw shapes and one end-to-end through a real
component, both sabotage-checked: dropping the payload rewrite fails
both, dropping the message rewrite fails the message assertion, and
removing the type gate fails the non-document case.

Closes #277.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bGRsKA42peqSnz4VMreBG
@robcohen
robcohen merged commit 5fd67fd into main Aug 10, 2026
33 checks passed
@robcohen
robcohen deleted the fix/document-guest-paths branch August 10, 2026 02:17
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.

Discovered documents keep the /work/… WASI guest path in Document.filename, so they cannot be opened

1 participant