All seven scenarios hold, and the pinned constraint — roles decided where the decision is actually made — is genuinely met: the three-role table lives once in src/money/people.ts (may(), ALLOWED map), and every mutating route asks it server-side via refuseUnless(c, action) — entries POST/POST-id/DELETE and API PUT/DELETE in src/web/app.ts, all five receipt routes in src/web/receipts.ts, accounts/transfers/rates/home in src/web/currencies.ts, trades/prices in src/web/crypto.ts, and share/remove/settle in src/web/people.ts. I traced each scenario: (1) share: owner-only /books/:id/share upserts book_members; Grace's /api/me then lists Household as editor and POST /entries records with recordedBy=2 (tests/people.test.ts 'a book is shared'); (2) private stays private: the viewer is built only from db.listBooksFor and the book cookie is accepted only for books the user is a member of (src/web/auth.ts authMiddleware), other-book entries 404 via entryInBook/db.bookOfEntry, and the test proves Ada's salary entry is invisible to Grace by page, API, PUT and DELETE while Ada's balance stays 3223.70; (3) authorship: entries carry recorded_by/paid_by (migration 6), shown as 'recorded by X · paid by Y' under the note in shared books (src/web/views.ts who()) and visible in book-editor.png; (4) the 84.00 split is exact bigint arithmetic — splitEvenly(8400n,[1,2]) → [4200,4200], remainder cents go to the first people (test with 1000n/3 → 334/333/333), and both Ada's and Grace's /api/books/2/debts plus the /people page show 'Grace owes Ada 42.00'; (5) settling: POST /books/:id/settle inserts a settlements row, debtsOf nets shares minus settlements to zero, and the settlement stays in history — visible in people-desktop.png ('Grace paid Ada · paid back in cash 42.00 EUR' alongside the new 15.00 debt from the pizza split); (6) viewer refusal: tests/people.test.ts hits the raw requests a hidden button would have sent (POST /entries, PUT/DELETE api, form delete, receipts, crypto, share, settle) and gets 403 with data unchanged, and book-viewer.png shows the read-only screen; (7) removal: /books/:id/members/:userId/remove deletes only the membership — Grace gets 403/no book, her tram entry stays with recordedBy=2 and renders as 'recorded by someone no longer in the book'; owner cannot be removed, editor cannot remove. Carry-forward holds: the 46 earlier tests are unchanged and the probe-delivered run shows 55 pass / 0 fail; earlier modules were only threaded with bookId parameters, defaults preserving prior behavior. The done note is accurate and honestly declares limits (handle-only sign-in, even-only splits, single-currency debts — debtsOf accepts a currency field but never uses it when netting). Two minor coherence gaps keep it from 10: a viewer can still GET /entries/:id/edit (a dead form — the POST is refused, but the page itself is reachable by guessed URL, which sits oddly with 'refuses him even when the buttons are not on the screen'), and the settle API validates the payer's membership but accepts a recipient who is not a member of the book (db.getUser(to) only).