oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

Review: escritorio's visibility model

I read the home design spec in full, both AGENTS.md files, the relevant sections of the Sheets design spec (§2, §8.2–8.3, §9.1.1, §10.2), the home repo's actual state, and the memory notes on open Oficina decisions. Findings first, severity-ordered; recommendation at the end.

Two things the brief gets wrong about its sources

The outbox belongs to the instance, not the home, and the home does not hibernate. The brief says "The home's spec drains its outbox 'on wake and on each share mutation, not on a ticker'". The spec's sentence (§6) is about the instance's outbox: "The outbox drains on wake and on each share mutation, not on a ticker — a hibernating instance runs no timers." And §2.1: "The home does not hibernate. It is the one app on the estate where cold-wake latency is a suite-wide outage." This matters for Option C: the thing that would carry a revoke is Sheets' outbox, whose stated semantics are "For a quiet team a failed upsert may wait weeks for an organic wake, which is acceptable precisely because the directory is a hint" (Sheets spec §9.1.1). Confirmed.

Aside, not the brief's fault: home/AGENTS.md is stale against its own spec — it still says "never stores your address", "some unidentified person", and "Assertion expiry is seconds", all three of which the spec explicitly retracted (§0: "the first draft's absolute phrasing … was false"; §3.3: "§0 says 'some person' rather than 'some unidentified person'"; §5.3: "Expiry is 120 seconds, not 'seconds'"). AGENTS.md's own rule is "Where it disagrees with the design spec, the spec wins and this file is wrong — fix it in the same commit." Nobody did. Confirmed.

Findings

1. Per-team escritorio answers a question nobody asked (Q6) — the most important finding

The stated want was "a list of MY sheets and docs". A per-team escritorio gives a person in three teams three lists. That is not a partial answer; it is the same answer the suite already gives, one hop closer.

What already exists, per the specs:

  • Sheets spec §10.2: "Home is your sheets by recency and a New button." Every app instance already has a per-team recency list.
  • Home spec §11: "The directory is the suite's front door: your teams grouped by app, and a New button."

So escritorio's job 1 (launch links) is the home's job by the home's own spec, and job 2 (per-team merged list) is a per-team union of four screens that each already exist. Its only new value is "one page instead of four, per team". That is a modest UI convenience, not a new capability.

The thing actually asked for — one cross-team list — is exactly what the home spec forbids the home from holding, and says so in words: §5.4, "'Which documents you can see' passes (a) and fails (b), handing the home a document-level graph it has no business holding." A per-team escritorio does not solve this; it routes around the appearance of it while leaving the want unmet. If the want is real, the honest options are (i) relitigate ruling 11 / §2.2 at the home with eyes open, or (ii) tell the user the architecture rules it out and why. Building escritorio does neither. Confirmed against both specs.

2. Titles are first-class sealed content in this family; "pointers only" is not a cheap concession (Q2)

Sheets spec §2.1 lists under Sealed: "Sheet titles, tab names, comment text." Its README-published leak sentence (§2.2) is that publishing Sheets' database yields "who works with whom, how big their spreadsheets are, how often they edit and what colour they painted things. You learn no value in any cell" — and no title. sheets-core/AGENTS.md bans a sheet title from any log line alongside cell content.

The escritorio index as settled ("title, app, item ref, URL, touched_at, actor person refs") therefore takes a value that Sheets seals under its own DEK and writes it into a second SQLite file, in a second process, replicated to a second bucket. And "Mail" is in the app list: an email's "title" is its subject line, which is body-grade content in anybody's threat model. "Q3 layoffs — final" was the brief's example; "Re: your termination" is Mail's.

Implications:

  • Escritorio cannot be "thin, blind"; it is a content-holding app and must meet the sibling's one rule — its own DEK, sealed titles, the plaintext-grep gate, the no-title-in-logs rule. That is most of a Sheets-grade trust model for a listing page.
  • Every option's custody sentence (finding 6) includes titles unless escritorio seals them. The brief's framing treats sealing as out of scope; it is not.
  • A stale title is itself a leak shape the brief does not name: Docs renames "Acquisition of X — term sheet" to "Project Blue"; A/C show the old name until the next push. Only a revoke was considered.

Confirmed against Sheets spec §2.1/§2.2 and AGENTS.md.

3. Option A's visible_to is not a list, it is the grants join — so A and C make escritorio a second authorization evaluator (Q1, Q5)

The Sheets spec says access is "a grants join, hand-written, expressing 'rows this team can see, plus rows granted to me individually, minus rows I have been removed from'" (§ on data). The brief's Option A ("each pushed entry carries the person refs who may see it") assumes visibility is a per-item property. It is not; it is a function of item grants and team membership. Two ways to make A work, both bad:

  • The app pushes the fully expanded set per item. Then a member joining or leaving the team changes the visibility of every team-visible item, so one members mutation fans out to N item pushes. And a membership change is not a "share mutation" — the outbox's only drain triggers are "wake and each share mutation", so unless the spec is widened, a removed member's list simply does not update until someone shares something. Suspected on the drain-trigger point (the wording is narrow; whether the implementation counts membership as a share mutation is not stated), confirmed on the fan-out.
  • Escritorio holds team membership and evaluates the join itself. Now it holds an authorization model, evaluated by a second implementation of the same rule, in a process that is not the authority. The Sheets AGENTS.md is explicit about why this rule is dangerous to re-implement: getting it wrong "fails as a plausible empty list, not as an error, which is the worst available failure shape". Two evaluators of one rule drift, and the drift shows a title to the wrong person or hides one from the right person, silently.

Either way A/C hand escritorio the graph. Option C inherits all of this and adds a fast path for one of the several event types (revoke) while leaving membership removal, rename, and delete on the slow path. Confirmed against the grants-join wording.

4. Option C's "seconds" is a median, and the tail is unbounded (Q4)

Trace a revoke under C, using only the specs' own mechanics:

  1. Owner un-shares in Sheets. Sheets is awake (it is serving the click).
  2. Priority push = an HTTP call to escritorio. If escritorio is hibernating, that call wakes it — a request wakes an app — so the receiver hibernating is not by itself fatal. But the cold wake costs what §5.3 sizes it at: "process start plus decrypt plus migrate-check can exceed a few seconds", against a 120 s budget chosen for exactly that worst case. So the push either sits in the request path (the revoke response blocks on another app's cold wake — the Sheets spec forbids this shape: call 2 goes "through an outbox in the instance, never a call in the request path") or it is asynchronous after the response.
  3. If asynchronous, the goroutine dies at "a deploy, a hibernation eviction or a panic" (Sheets spec, verbatim on a different subsystem, same platform fact). The revoke then sits in the outbox.
  4. The outbox retries only "on wake and on each share mutation". For a quiet team that is "weeks", by the Sheets spec's own admission.
  5. Restore-from-backup: an escritorio restored from replication holds visible_to from before the revoke, with no mechanism to learn it missed one. The home spec had to invent a pepper epoch plus full re-announce for precisely this class of problem, and generalised it into a binding rule in both AGENTS.md files: "Any 'prune when all consumers have caught up' is unbounded in a hibernating fleet."

So C needs, to be honest: per-item version numbers, idempotent pushes, an epoch, a full re-announce verb, and a fail-closed rule when escritorio suspects it is stale. That is the home's directory machinery rebuilt per team — and unlike the directory, escritorio's copy is rendered, so the "acceptable because it's a hint" argument that makes the weeks-lag tolerable for the home does not transfer. "Seconds" describes the case where nothing goes wrong. Confirmed on every step's mechanics; the tail length is the Sheets spec's own number.

5. Option B is not "secretly A"; its real problems are a new oracle endpoint and a blocking zero-JS page (Q3)

The brief asks whether a short-TTL cache turns B into A. No — the two stalenesses are different in kind. A's is a persisted graph copy with an unbounded tail (finding 4). B's cache would be in-RAM, per viewer, bounded by TTL, and evicted at hibernation for free. Bounded-and-volatile is categorically better than unbounded-and-replicated. Suspected (reasoning).

B's actual costs, which the brief does not list:

  • Latency shape. The front door for a quiet team — the team most likely to have all four apps asleep — blocks on the slowest of four parallel cold wakes, tens of seconds by §5.3's sizing, on every first load of the day. The zero-JS baseline (home spec §11, both AGENTS.md) means no progressive render: the page is blank until the last app answers. It is not disqualifying if escritorio renders what it has after a deadline and marks the rest "waking", but that is a design the brief does not describe.
  • A visibility oracle. B requires each app to expose "of these refs, which may person P see?" to a bearer-authenticated caller. That is four new per-consumer tokens per team (Pegamento's contract: hashed bearer, allowlisted origin, body cap) and, if escritorio's token leaks, an endpoint that answers membership questions about arbitrary persons and refs. It must be designed to answer only refs the caller supplied and to 404 rather than distinguish "no such ref" from "not yours", or it becomes an enumeration oracle. Not fatal, but unpriced.
  • B still holds titles. Finding 2 applies in full. B fixes the ACL custody, not the content custody.

Suspected on the oracle risk; confirmed on the zero-JS and cold-wake facts.

6. The blast-radius claim is half right, and the half that is wrong matters (Q1, Q5)

The claim: a per-team instance may hold what the home may not "because it shares a blast radius with the content it indexes". The home spec's own principle cuts against the framing: §2, "Blast radius is bounded by what a service holds, not by how many processes it runs in." Per-team boundary is necessary but says nothing about what escritorio may hold. What it holds is titles from four apps plus (A/C) four sharing graphs. Compromising one escritorio yields wider breadth than any single app instance (every title across four apps) at shallower depth (no bodies). It does not exceed the team, so the cross-team aggregation §0 fears is genuinely avoided. But the home spec's feature test — "Every proposed feature is weighed against 'does this belong in an instance instead?'" — is not passed by escritorio; escritorio is the proposal to create a new instance whose sole purpose is aggregation.

The "publish the database and see" sentence, per option:

  • A and C: Publish a team's escritorio database and you learn the title of every sheet, document, event and mail subject the team has, who touched each and when, and for every item exactly which people — members and outside guests — may see it: the team's complete sharing graph across four apps in one file. Titles are readable unless escritorio seals them with its own DEK.
  • B: Publish a team's escritorio database and you learn the title of every item across four apps, who touched each and when, and four hashed bearer tokens. You learn nothing about who may see what.

Note Sheets already publishes its grants in plaintext (§2.2: "Membership, grants, and roles — including readable email addresses"), so A's per-item ACL is not a new category relative to one app; the new things are the union across four apps and the titles. Confirmed against §2 and Sheets §2.2.

7. Guests and link viewers do not fit any option cleanly (Q1, Q5)

Sheets §8.2: a guest "see[s] exactly the sheets granted to them and learn[s] nothing about the team beyond §9.5's disclosure". Sheets §8.3: "A view link mints nothing — read-only against a capability token; no member, no directory entry." So a link viewer has no person ref, cannot appear in visible_to, and cannot have an escritorio session — fine. But a guest with one grant does have a person ref and will land on escritorio: escritorio must produce a page showing that one item and nothing else, including no hint of the other three apps' existence in that team, and the guest needs a directory row of kind: guest for escritorio itself (or escritorio's sign-in must be derived from the granting app's row — unspecified). Suspected; the specs do not address a 5th app's sessions for guests.

8. Escritorio is blocked on work that has not started, and on a decision Paul has not made (Q8)

  • The home repo at /home/paulca/amadan.net/oficina/home is four Go files, 191 lines, "one placeholder page" per its own commit message. §13 steps 2–4 (sign-in, assertions, directory with person refs) have not begun. Every escritorio option keys on person refs — visible_to, "may this person see", and escritorio's own sessions all need sub — and the home spec says "step 4 must land before either ships, because call 2's person ref is what their grant rows key on." Escritorio is a third consumer of a call that does not exist. Confirmed.
  • The memory note records an open question "asked twice, not yet answered": whether the central host is a directory that links or an authenticator that mints assertions. Escritorio's whole identity story depends on that answer. Confirmed against the memory file.
  • Of the four apps escritorio would index, mail has no repo and calendar has a directory with no spec. A merged list across four apps, two of which do not exist, is a merged list across two — and those two already have per-team recency screens (finding 1). Confirmed by listing the repos.

9. A fourth option exists, and it is "don't build escritorio; put the merged list where the data already is" (Q7)

Two concrete alternatives, both dropping the new app:

  • The home's directory as the only front door (already specified, §11), linking to each app's own per-team recency list (already specified, Sheets §10.2). Zero new custody, zero new contract calls, zero new evaluators of the grants rule. Cost: one extra click per app. This is what the specs already say the suite does.
  • If a per-team merged list is genuinely wanted later: a pull-based, stateless page owned by one existing app (e.g. Sheets' own home page gains a "Docs" section fetched from the sibling with the viewer's identity, fail-soft on cold wake, nothing stored). This is Option B with the escritorio process deleted — one fewer database to seal, one fewer blast radius, and B's oracle endpoint is still needed but at least the caller is an app that already meets the one rule.

Neither yields the cross-team "MY sheets" list. Nothing in A, B, C or these does, because the architecture forbids it at the only place that could hold it. That should be said plainly to the user rather than absorbed into a new app.

Recommendation

Do not build escritorio now; if a merged list must exist, it is Option B's mechanism without Option B's process. The per-team justification is technically sound for cross-team aggregation but does not survive the home spec's own "what does it hold, not how many processes" test: escritorio holds titles that Sheets seals as first-class content, and under A or C it also holds a second, drifting copy of the grants join whose revoke path has the same weeks-long tail the specs already accept only because the directory is a hint — and escritorio's copy is not a hint, it is rendered. B is the only option where a stale escritorio cannot show a title to the wrong person; its costs (four cold wakes on a scriptless page, a visibility endpoint that must not become an oracle) are real but bounded and priceable, and its residual custody is titles only. But B still needs the home's person refs (§13 step 4, not started) and still does not give the user the cross-team list they asked for. So: answer the actual question first — the suite's architecture rules out "one list of MY things" at the home by §5.4, and either that ruling is reopened deliberately or the want is declined — and until the home's steps 2–4 land, point people at the directory the home spec already calls "the suite's front door".

The strongest argument against this: the directory-plus-one-click answer scales linearly in clicks with the number of apps, and "your stuff, in one place" is the single thing every office suite's users actually use the front door for. If Calendar and Mail do ship, four separate recency screens per team is a real usability cost that no amount of architectural tidiness pays for, and a stateless B-shaped page will feel slow on exactly the quiet teams that are the product's likely customers. That cost is genuine; my position is only that it should be paid once the identity layer exists, not designed around before it does.