feat: transaction attachments/receipts (#7) #15

Open
agent-int wants to merge 1 commit from feat/issue-7-attachments into main
Collaborator

Transaction attachments / receipts

Second item of the P1 double-entry tier (#7): Beancount document-style attachments.

Attach a reference (URI or file path) to a transaction, plus an optional mime_type and
note. The ledger stores only the reference — never an embedded blob — so the database stays
lean and Litestream-replication-friendly (relevant to #1). No money is involved; pure metadata.

Surfaces (server-depth, mirroring the payees feature)

  • Migration 0009: attachments table (transaction_id FK ON DELETE CASCADE) + index +
    changelog trigger (entity_kind = 'attachment').
  • Store: Commands.attachmentAdd/attachmentList/attachmentRemove + LedgerStore SQL.
  • gRPC: AddAttachment / ListAttachments / RemoveAttachment (+ proto messages).
  • Client: LedgerClient.addAttachment/listAttachments/removeAttachment + AttachmentInfo.
  • MCP: direct add_attachment / list_attachments / remove_attachment tools — no money
    moves, so no propose/commit split; remove_attachment carries destructiveHint.

Notes

  • attachmentAdd trims and rejects an empty URI, and rejects an unknown transaction (the FK would
    also reject it; pre-checked for a clean error).
  • Optional mime_type/note round-trip as SQL NULL (not "") via the .text(nil)→.null
    binding and nilIfEmpty on the client.
  • make test green (197 package + app tests, 0 failures; 8 new attachment tests across the store,
    gRPC, and MCP layers). Independent critic + architect review.

Scope / merge note

Delivers exactly one item of #7 (does not close it). Branches off main and touches the same
core files as the still-open #14 (opening-balance pad); the changes are additive, so the two should
merge cleanly (rebase whichever lands second). Remaining P1 items — budgets/envelopes,
pending/cleared, bitemporal dates — stay open.

## Transaction attachments / receipts Second item of the **P1 double-entry tier** (#7): Beancount `document`-style attachments. Attach a **reference** (URI or file path) to a transaction, plus an optional `mime_type` and `note`. The ledger stores only the reference — **never an embedded blob** — so the database stays lean and Litestream-replication-friendly (relevant to #1). No money is involved; pure metadata. ### Surfaces (server-depth, mirroring the payees feature) - **Migration 0009**: `attachments` table (`transaction_id` FK `ON DELETE CASCADE`) + index + changelog trigger (`entity_kind = 'attachment'`). - **Store**: `Commands.attachmentAdd/attachmentList/attachmentRemove` + `LedgerStore` SQL. - **gRPC**: `AddAttachment` / `ListAttachments` / `RemoveAttachment` (+ proto messages). - **Client**: `LedgerClient.addAttachment/listAttachments/removeAttachment` + `AttachmentInfo`. - **MCP**: direct `add_attachment` / `list_attachments` / `remove_attachment` tools — no money moves, so no propose/commit split; `remove_attachment` carries `destructiveHint`. ### Notes - `attachmentAdd` trims and rejects an empty URI, and rejects an unknown transaction (the FK would also reject it; pre-checked for a clean error). - Optional `mime_type`/`note` round-trip as SQL `NULL` (not `""`) via the `.text(nil)`→`.null` binding and `nilIfEmpty` on the client. - `make test` green (197 package + app tests, 0 failures; 8 new attachment tests across the store, gRPC, and MCP layers). Independent critic + architect review. ### Scope / merge note Delivers exactly one item of #7 (does **not** close it). Branches off `main` and touches the same core files as the still-open #14 (opening-balance pad); the changes are additive, so the two should merge cleanly (rebase whichever lands second). Remaining P1 items — budgets/envelopes, pending/cleared, bitemporal dates — stay open.
feat: transaction attachments/receipts (#7)
Some checks failed
ci / macos (pull_request) Successful in 4m0s
ci / linux (pull_request) Has been cancelled
ci / proto (pull_request) Has been cancelled
ci / purity (pull_request) Has been cancelled
e2a32b05ec
Add Beancount `document`-style attachments: attach a reference (URI or file
path) plus optional mime type and note to a transaction. Stores only the
reference, never an embedded blob -- keeps the DB lean and Litestream-
replication-friendly. No money is involved; pure metadata.

Server-depth surfaces, mirroring the payees feature:
- migration 0009 (attachments table + changelog trigger; FK ON DELETE CASCADE)
- Store: Commands.attachmentAdd/List/Remove + LedgerStore SQL
- gRPC: AddAttachment / ListAttachments / RemoveAttachment (+ proto messages)
- Client: addAttachment/listAttachments/removeAttachment + AttachmentInfo
- MCP: direct add_attachment / list_attachments / remove_attachment tools
  (no money moves -> no propose/commit; remove carries destructiveHint)
- Tests at the store, gRPC, and MCP layers

Second item of the P1 double-entry tier (#7); the remaining items (budgets,
pending/cleared, bitemporal dates) stay open.
nic referenced this pull request from a commit 2026-06-30 21:14:17 -07:00
nic referenced this pull request from a commit 2026-06-30 21:26:43 -07:00
nic referenced this pull request from a commit 2026-06-30 22:00:26 -07:00
Some checks failed
ci / macos (pull_request) Successful in 4m0s
ci / linux (pull_request) Has been cancelled
ci / proto (pull_request) Has been cancelled
ci / purity (pull_request) Has been cancelled
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin "feat/issue-7-attachments:feat/issue-7-attachments"
git switch "feat/issue-7-attachments"

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch "main"
git merge --no-ff "feat/issue-7-attachments"
git switch "feat/issue-7-attachments"
git rebase "main"
git switch "main"
git merge --ff-only "feat/issue-7-attachments"
git switch "feat/issue-7-attachments"
git rebase "main"
git switch "main"
git merge --no-ff "feat/issue-7-attachments"
git switch "main"
git merge --squash "feat/issue-7-attachments"
git switch "main"
git merge --ff-only "feat/issue-7-attachments"
git switch "main"
git merge "feat/issue-7-attachments"
git push origin "main"
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
nic/commons!15
No description provided.