| 1 | -- Sharing: a grant is one person's access to one document. |
| 2 | -- |
| 3 | -- Spec §8.2 has described this since the design was written and nothing |
| 4 | -- implemented it: until now `documents.owner_id` was the whole authorization |
| 5 | -- model and every signed-in person saw only their own rows. |
| 6 | -- |
| 7 | -- `subject` is a lowercased, trimmed address, NOT a `people.id`, and that is |
| 8 | -- deliberate. A grant has to be makeable to somebody who has never signed in — |
| 9 | -- that is most of what sharing is for — and `EnsurePerson`'s create branch is |
| 10 | -- where a brand-new person gets seeded a sample library. Minting a Person row |
| 11 | -- to hold a grant would hand an invitee six sample documents they never asked |
| 12 | -- for, before they had even accepted. Keyed on the address, the grant simply |
| 13 | -- waits, and `EnsurePerson` maps the address to an id whenever they arrive. |
| 14 | -- |
| 15 | -- It is also the same subject the suite doctrine's §3 uses in lone shape: a |
| 16 | -- lowercased address. Docs is lone-shaped today, and when a home arrives the |
| 17 | -- subject gains a kind rather than a different meaning. |
| 18 | -- |
| 19 | -- `role` is `reader` or `writer`. Not an integer, because a role read out of a |
| 20 | -- database during an incident should say what it is. Document-scoped only: |
| 21 | -- there is no team in this app yet, so there is nothing a NULL doc_id could |
| 22 | -- mean, and a nullable column whose one legal value is "not that" is an |
| 23 | -- invitation to guess. Spec §8.2's team-wide grant arrives with a team. |
| 24 | -- |
| 25 | -- The UNIQUE index is the whole conflict story: granting twice to the same |
| 26 | -- address on the same document updates the role rather than stacking two rows |
| 27 | -- whose precedence nobody has decided. |
| 28 | -- `created_by` is the person who made the grant, and it is in the spec's own |
| 29 | -- data model (§4) — this table shipped without it, and the omission was never |
| 30 | -- argued, only overlooked while the nullable `doc_id` above was argued at |
| 31 | -- length. It is the one column that answers "who let this address in". |
| 32 | -- |
| 33 | -- That question has teeth because admission is transitive in shape even where |
| 34 | -- it is closed in policy: a guest is admitted by somebody's grant, and if a |
| 35 | -- guest ever gains the power to grant, the chain of who admitted whom is only |
| 36 | -- reconstructable if each link recorded its author. Adding it now, while the |
| 37 | -- table is new, costs a line; adding it after a leak costs the history. |
| 38 | CREATE TABLE IF NOT EXISTS grants ( |
| 39 | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| 40 | doc_id INTEGER NOT NULL, |
| 41 | subject TEXT NOT NULL, |
| 42 | role TEXT NOT NULL, |
| 43 | created_by INTEGER NOT NULL DEFAULT 0, |
| 44 | created_at DATETIME |
| 45 | ); |
| 46 | |
| 47 | CREATE UNIQUE INDEX IF NOT EXISTS idx_grants_doc_subject ON grants (doc_id, subject); |
| 48 | |
| 49 | -- The read path asks "which documents may this subject reach", so the index |
| 50 | -- that matters leads on subject. |
| 51 | CREATE INDEX IF NOT EXISTS idx_grants_subject ON grants (subject); |
| 52 | |