wip: pause checkpoint — P6 feature complete, gate interrupted (not for merge)

This commit is contained in:
swaits committed 2026-10-27 14:41:34 -06:00
1 parent cc4f104f06
commit ce36640874
68 files changed
+4592 -374

No files matched your search

+80 -16
View File
@@ -347,7 +347,7 @@ Tool descriptions are at most 700 characters; eight longer ones are listed
in `tool_descriptions_fit_700_characters`, to be shortened in P5.
**Annotations.** Every tool has a `title` and a `readOnlyHint`; every tool
that changes anything also has a `destructiveHint`, true for these 29:
that changes anything also has a `destructiveHint`, true for these 30:
`books.post_entry`, `books.reverse_entry`, `books.set_setting`, `books.retire_convention`, `books.unlink_receipt`,
`books.unlink_attachment`, `books.withdraw_attachment`,
`books.correct_asset`, `books.dispose_asset`, `books.run_depreciation`,
@@ -358,7 +358,7 @@ that changes anything also has a `destructiveHint`, true for these 29:
`books.set_tin` (both overwrite a vault record), `books.purge_tin`,
`books.create_invoice`, `books.create_bill`, `books.receive_payment`,
`books.pay_bill`, `books.void_document`, `books.run_payroll`,
`books.remit_payroll_taxes`; and an
`books.remit_payroll_taxes`, `books.consolidate_duplicates`; and an
`idempotentHint` on the ten that a repeat with the same arguments leaves
the journal and the vault as they were: `books.verify`,
`books.run_depreciation`, `books.run_schedules`, `books.run_recurring`,
@@ -1577,22 +1577,82 @@ and no control or invisible (Unicode format) characters; a MIME type
subject it is linked to, its filing entry first). Withdrawn attachments
are omitted unless `include_withdrawn` is true. `entry_id` lists the
files filed on the entry and linked to it.
- `books.get_receipt { attachment_id, view?: "meta" | "bytes" }` → `{
attachment_id, sha256, entry_id?, category, file_name, mime_type,
size_bytes, description?, links, file? }`. `meta` (the default) returns
no file. `bytes` adds `file`, the file handed over (a `HandedFile`, read
hash-verified, whatever its size: D-46), whose `download` saves a copy
as a new file in a folder the human names. The stored file is also
fetched by `wanderbooks download --book B <id> --to <a folder>` (`GET
- `books.get_receipt { attachment_id, view?: "meta" | "text" |
"page_image" | "bytes", page?, max_px? }` → `{ attachment_id, sha256,
entry_id?, category, file_name, mime_type, size_bytes, description?,
links, text?, text_truncated, text_status, pages, receipt_number?,
annotation?, file? }`. `meta` (the default) returns no file: the first
8 KiB of the file's extracted text (`text`, `text_truncated` when cut),
its page count (`pages`), and `text_status` (`ok`, `none`, `failed`, or
`not_extracted` for books filed before this version: `wanderbooks books
extract`). `text` gives the whole extracted text (past 64 KiB it comes
as a handed-over `.txt` instead). `page_image` with `page` (1 is the
first) and `max_px` (default 1600, longest side) gives one page as a
PNG, handed over (D-46; D-15: no OCR, so a picture-only receipt is read
from its pages). Read the text first; a page picture only when the text
is empty or looks wrong; never `bytes` into context. `bytes` adds
`file`, the file handed over (a `HandedFile`, read hash-verified,
whatever its size: D-46), whose `download` saves a copy as a new file
in a folder the human names. The stored file is also fetched by
`wanderbooks download --book B <id> --to <a folder>` (`GET
/b/{book}/a/{attachment}`, books:read, "Transports").
- `books.file_document { category: "formation" | "legal" | "tax" |
"accounting" | "statement" | "contract" | "photo" | "warranty" |
"import_source" | "other", file_name?, mime_type?, upload_id,
description?, link_to? }` → `{ attachment_id,
sha256, deduplicated, links }`. The bytes, name and type as in
`books.attach_receipt`. The Drive-folder replacement (today's 01
Formation / 02 Legal / 03 Tax / 04 Accounting). Never a W-9: it shows
the full TIN (`books.set_w9 w9_upload_id`).
"import_source" | "notices" | "correspondence" | "plans" | "history" |
"other", file_name?, mime_type?, upload_id, description?, link_to? }` →
`{ attachment_id, sha256, deduplicated, links }`. The bytes, name and
type as in `books.attach_receipt`. Every company record has a home
(D-20): the Drive folders' replacement, and formation papers,
registrations, plans, notices, correspondence and history besides what
the CPA asks for. Never a W-9: it shows the full TIN (`books.set_w9
w9_upload_id`).
- **One file, one record** (plan P6 step 4): filing bytes already stored
(and not withdrawn) returns that attachment's `attachment_id` with
`deduplicated: true` and links it to the new subject, instead of filing
a second record. Records an older version filed twice stay as written
until `books.consolidate_duplicates`.
## Tools — evidence
What the books know about a filed file beyond its bytes (plan P6): its
extracted text, the facts a human read off it, and which entries a payment
settles. Extraction is journaled as it is made (`attachment.text_extracted`,
content-addressed, one record per attachment, extractor and version): an
extractor upgrade adds a record, never replaces one, and `books.verify`
checks the text blob's presence and hash.
- `books.find_attachment { sha256 | receipt_number | text }` → `{
attachments: [...] }`. Exactly one of the three: the file's exact
SHA-256 (64 hex digits), the number recorded with
`books.annotate_attachment`, or words its extracted text says
(case-insensitively). Withdrawn files are listed too, marked, so a
duplicate is found before it is consolidated. Each attachment is as in
`books.list_receipts`, with its links.
- `books.annotate_attachment { attachment_id, fields { receipt_number?,
payee?, amount?, date?, instrument?, category?, source?,
reimbursable_to? }, note? }` → `{ attachment_id, fields,
next_receipt_number }`. The closed list of what a human reads off a
file: `amount` an exact decimal string, `date` YYYY-MM-DD, `instrument`
a payment method's id or label. It changes no amount, no file and no
posting; a later annotation replaces it, journaled as
`attachment.annotated`. `next_receipt_number` is one past the highest
number annotated, so numbers run without reuse. At least one field or
the note is required; an unknown or withdrawn attachment is refused.
- `books.set_entry_settles { entry_id, settles }` → `{ ok, entry_id,
settles }`. Records which entries an entry pays back (a reimbursement
settling the expenses a member paid personally), journaled as
`entry.settles`; an empty list clears it. Every named entry must exist,
none may be the paying entry itself or be named twice. It changes no
amount. "The member was paid back" is read from this record only, never
guessed (IZ-18); Needs attention asks until it is recorded.
- `books.consolidate_duplicates { confirmed? }` → `{ status: "preview" |
"consolidated", groups: [{ sha256, keep, withdraw, links }], withdrawn
}`. For every set of attachments holding the same bytes it keeps the
oldest (`keep`), links it to every subject the others had, unlinks the
others and withdraws them as duplicates (`attachment.withdrawn` with
`duplicate_of`; nothing is deleted). A posted entry that would be left
with no receipt the policy requires refuses the whole call, journaling
nothing. Without `confirmed`, a preview that changes nothing.
## Receipt enforcement
@@ -1841,8 +1901,12 @@ Hand-rolled on `ServerHandler` (rmcp 3.x has no resource macros):
this month so far
- `books://handbook` (the index) and `books://handbook/{section}` (one per
section, Markdown)
- `books://receipts/{id}` — receipt metadata (as `books.get_receipt`
- `books://receipts/{id}` (an alias of `books://attachments/{id}`) —
receipt metadata and the first 8 KiB of its text (as `books.get_receipt`
view `meta`; no bytes)
- `books://attachments/{id}/page/{n}` — page `n` (from 1) as a PNG blob,
the first binary resource (plan P6; D-46: the bytes are the resource's
contents, never a path)
Resources exist so read-only clients (dashboards, the viewer,
auditors) never need tool credentials. Tested end to end through a real