feat: receipt unlink and attachment withdrawal
Posted entries stay immutable (R-3), but their evidence can now change the
same way the ledger does: by appending events, never by editing or
deleting. New R-13a.
- books.unlink_receipt {attachment_id, reason} journals receipt.unlinked
and leaves the receipt filed, unlinked. The receipt policy holds: an
entry that requires a receipt can't lose its last one (structured
receipt_required error), so a replacement is attach-new, then
unlink-old. Drafts and reversals are exempt, as at posting.
- books.withdraw_attachment {attachment_id, reason} journals
attachment.withdrawn for an unlinked receipt or a filed document: hidden
from list_receipts (unless include_withdrawn), never linkable again. The
blob stays in the store, hash-verified and readable.
- link_receipt now refuses an already-linked or withdrawn attachment.
Before, re-linking silently moved a receipt off a posted entry,
bypassing the policy; evidence now leaves an entry only through a
recorded unlink. dispose_asset checks its receipt ids before posting
instead of after.
- Schema v5 adds attachments.withdrawn_at/withdrawn_reason; existing
stores migrate on open and replay rebuilds the projection.
Tests: store test (errors, reopen, rebuild, verify, journal history) and
an MCP stdio test (policy refusal, replace flow, withdraw, listings,
under-threshold unlink). Dry-run and live use on the Wanderland Labs books:
8 migrated .eml attachments swapped for their PDF renders, trial balance
unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu
This commit is contained in:
1 parent
3bfea47a59
commit
e59e8001c6
13 files changed
+580
-21
No files matched your search
@@ -151,3 +151,13 @@ interface is MCP, not a cloned GUI. The first storage + MCP slice is done
|
||||
(QBO/CSV/email/sheets, conversational). 5 integration tests green; clippy
|
||||
clean. Bootstrap: `mise trust && mise install`, `mise run build`,
|
||||
`mise run mcp-add`, then `claude` in the dir.
|
||||
- 2026-10-11: Receipts are mutable through events, never edits (Stephen:
|
||||
"the journal is just a history of every single thing that happens").
|
||||
`books.unlink_receipt` and `books.withdraw_attachment` (both require a
|
||||
reason) journal `receipt.unlinked` / `attachment.withdrawn`; nothing is
|
||||
erased from the journal or blob store. Unlinking can't strand an entry the
|
||||
receipt policy requires evidence for (attach the replacement first).
|
||||
`link_receipt` now refuses an already-linked or withdrawn attachment, which
|
||||
closes a silent path for moving a receipt off a posted entry; disposals
|
||||
check their receipt ids before posting. Schema v5. First real use: swapping
|
||||
the migrated Wanderland Labs `.eml` receipts for their PDF renders.
|
||||
Reference in new issue
Block a user