oficina / sheets Public

Design: the directory is a hint, and what that buys

Four gaps found by docs-e6 reviewing §9 as a declared consumer.

The first is the one that mattered: §9 never said what happens when the
instance-to-home directory call fails, and the obvious implementation
would have put a synchronous upsert in the share request path — making
the home load-bearing for sharing, the exact failure the blueprint names
when it says losing glue costs a convenience, never content. Now ruled:
failed upserts never fail a share, call 2 goes through an outbox with
retry, and instances re-check authorization on every request whatever
the directory says.

That ruling then answers a question it was not made for. Rotating the
HMAC pepper kills every directory row at once, which looked like it
forced "never rotate it" and a permanent secret with no recovery story.
Under the hint ruling it is a convenience outage instead: access is
unaffected, and the directory refills as instances re-upsert.

Also: email change is admitted as a fifth call rather than discovered
later, with the readable-address question flagged for the home spec;
guest grants disclose the granting team's name, kept but now disclosed
in the share UI rather than emergent; and directory rows carry a
member-or-guest discriminator so a guest's row stops implying an org
membership they do not have.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APtNStXDZ5H2fAc1ZuGqtS
Paul Campbell eafee264f2ba1044dd4ab0e52397477c12ab583c parent 1ba1575
1 file changed, +71 −1
  • docs/superpowers/specs/2026-08-30-sheets-design.md +71 −1
diff --git a/docs/superpowers/specs/2026-08-30-sheets-design.md b/docs/superpowers/specs/2026-08-30-sheets-design.md
index 5759b05..f1878e2 100644
--- a/docs/superpowers/specs/2026-08-30-sheets-design.md
+++ b/docs/superpowers/specs/2026-08-30-sheets-design.md
@@ -59,7 +59,8 @@ titles; tab names; comment text.
- Cell coordinates — therefore the **shape and size** of every sheet.
- Format records, **including fill colours**.
- Timestamps, actor ids, and the full edit log's shape.
-- Membership, grants, and roles.
+- Membership, grants, and roles — **including readable email addresses**
+ where a grant carries one (see §9.1.2, which is deciding whether it must).
Publish our database and you learn who works with whom, how big their
spreadsheets are, how often they edit, and what colour they painted things.
@@ -293,6 +294,75 @@ The home never receives a readable address. It learns one transiently at
sign-in to compute the HMAC and never stores it — Kass's pattern, where the
readable address lives solely in unclaimed sign-in links.
+**Call 3's rows carry a kind discriminator — member or guest.** A guest is
+not "in" a team the way a member is, and a row that implies an org membership
+they do not have is a lie the directory tells on the org's behalf. Cheap now,
+awkward once rows exist. (docs-e6, 2026-08-30.)
+
+### 9.1.1 The directory is a hint, never an authority — RULED 2026-08-30
+
+The blueprint's position on glue is that losing it **costs a convenience,
+never content**. §9 as first written did not say what happens when call 2
+fails, and the obvious implementation — a synchronous upsert inside the share
+request — would have made the home load-bearing for sharing, which is exactly
+the failure the blueprint names. (Caught by docs-e6 reviewing as a consumer,
+2026-08-30.)
+
+So, as a rule rather than an implementation detail:
+
+- **A failed directory upsert never fails the share.** The person is granted
+ access; the instance is the authority; the directory catches up.
+- Call 2 goes through an **outbox in the instance with retry**, never a call
+ in the request path.
+- **Every instance re-checks authorization on every request**, whatever the
+ directory says. The directory answers "where might I have things", never
+ "may I have this".
+
+A home that is down means people reach their teams by bookmark instead of by
+list. Nothing becomes unreachable and nothing becomes unshareable.
+
+### 9.1.2 Re-keying: email change and pepper rotation
+
+The directory is keyed on `HMAC(pepper, lowercased email)`. Two ordinary
+events invalidate keys, and neither had a procedure. (docs-e6, 2026-08-30.)
+
+**Email change.** The person's HMAC changes and every directory row they own
+is orphaned. The home cannot repair this alone — it holds no readable address
+to re-hash. This is **a fifth call, admitted rather than discovered**: a
+cursor-based rekey journal that instances pull, carrying `old_hmac →
+new_hmac` and nothing else. The home learns both addresses transiently at the
+change (it must, to hash them) and stores neither.
+
+**Open, and the home spec resolves it:** whether instances hold readable
+addresses at all. They need one to render "shared with alice@example.com" in
+a sharing UI; they may not need one for auth, because §9.2 puts sign-in on
+the credential origin rather than the instance. That single question decides
+whether the rekey journal must also carry the new readable address, and it is
+the home spec's to settle, not this one's. Flagged here so it is not
+rediscovered.
+
+**Pepper rotation, and §9.1.1 is what makes it survivable.** Rotating the
+pepper kills every row in the directory simultaneously. Under the hint
+ruling that is a **convenience outage, not a data loss**: access is
+unaffected because instances are authoritative, and the directory refills as
+instances re-upsert through their outboxes. So the pepper is rotatable
+rather than a permanent secret with no recovery story — which is the better
+answer, and it falls out of a ruling made for a different reason.
+
+### 9.1.3 A guest grant discloses the granting team's name
+
+Sharing one sheet with an outside guest puts a row in their directory
+carrying **the granting team's name**. Somebody shown a single file learns
+the name of the org that shared it.
+
+**Ruled: keep the behaviour, disclose it in the share UI.** Google does the
+same and it is not wrong; the alternative — guest grants producing no
+directory row — removes the reason the directory exists, since the original
+case was a guest shared into three orgs needing somewhere to land. What is
+wrong is an emergent property nobody decided. The share dialog says who will
+be able to see the team's name, in words, at the moment of sharing.
+(Raised by docs-e6, 2026-08-30.)
+
### 9.2 Split origins — RULED 2026-08-30
A team name is account-authored, so a home that renders team names on its