Token-scoped exposure: the origin enforces, the wiki obeys (Wiki Facet For Beads)

**Note** from Bead: Wiki Facet For Beads · [canonical source](https://redfish.acequia.io/guerin/.agents/3a66d5c4-b77c-48e1-9a02-a6f924b76f2f/2026-07-09/notes/03-token-scoped-exposure.md) · session 2026-07-09 · discussion: Talk: Wiki Facet For Beads

Stephen (2026-07-09): "we need to think about beads are exposed to the wiki based on requester tokens. we want to keep beads that require authenticated for the domain/origin are enforced."

## The rule **The wiki facet must never grant a requester more than the origin would grant the same requester.** The origin (nephele on redfish.acequia.io, `.htaccess` fail-closed on acequia.org, tokenized URLs) is the single policy point; the wiki derives its exposure from the origin's enforcement instead of re-implementing policy. v0.1.0 violated this: it read local disk (which holds everything) and rendered into a fully public wiki, with only a hand denylist between restricted beads and the internet.

## Measured exposure (anon probe, 2026-07-09) Probe artifact: [../artifacts/anon-probe-2026-07-09.txt](../artifacts/anon-probe-2026-07-09.txt). Of 191 local beads, anonymous GET of canonical `about.md`: - **138 → 200**: public at origin; safe on the public wiki. - **6 → 302** to `register-user.html` (account-gated tier): `578b155c`, `6a4d9aa5`, `abd79922`, `bc2bd3b1` (simtable-pipeline), `cef6a5a8` (calpoly-slo), `d3b7b28c`. - **47 → 404**: never published to the origin (local-only; includes the persona beads Frank `b2d9f6e1` and Debbie `c4a8e2f1`, `aa0ecff7`, `545c1ab1`, and others). Unpublished is also unexposed: the origin's silence is a policy statement.

## v0.2.0 (implemented, verified) The generator now gates on **anonymous vantage**: fetch canonical `about.md` with no credentials, `redirect: manual`, 15 s timeout; only HTTP 200 renders. 302/401/403/404/timeout all **fail closed**. A cleanup pass backs up and deletes previously generated pages whose bead no longer passes (100 pages removed on first run; live 404s confirmed externally). The denylist survives as belt-and-braces under the gate. Dangling `Bead: X` links from public prose may still name a restricted bead; the naming text was already public at origin, and the wiki page behind the link does not exist.

## Tiers: one static wiki host = one audience A static fedwiki page store can encode exactly one audience. So requester-token exposure across audiences means **per-audience wiki hosts**, which the wiki config's `allowed` list already anticipates: | Tier | Wiki host | Ingress auth | Generation vantage | |---|---|---|---| | public | wiki.harvardviz.live (live) | none | anonymous (implemented) | | team / account-gated | wiki.redfish.com (reserved) | Caddy `forward_auth` verifying an acequia JWT against our `.well-known` key (interim: Cloudflare Access) | probe with a minted read-only short-TTL token; render what THAT token can GET | | owner-private | wiki.guerin.acequia.io (reserved) | owner token only | owner-token vantage | Same generator, three vantage credentials, three page stores. The wall in front of each wiki host is the same wall the origin uses: one trust root, many gates (see [04-oauth-wellknown-signing.md](04-oauth-wellknown-signing.md)).

## Phase 3: request-time facet (true per-requester exposure) Static tiers approximate the requester-token model; the exact form is a **dynamic handler**: a route (Caddy handle or fedwiki page-store adapter) that builds page JSON at request time by fetching the bead **with the requester's own token forwarded**. Origin says 403, requester sees no page; per-requester, per-request, zero policy duplication. This also makes the Talk page live (dock deposits appear immediately) and gives edit-back a verified depositor identity. Cost: a running service instead of flat files; fedwiki neighborhood/sitemap semantics need care.

## Known limits (v0.2.0) - Gate granularity is the bead root: one probe of `about.md` stands for the whole bead. Mixed-visibility beads (public about, restricted artifacts) render their about; deep links still enforce at origin per request, so nothing restricted is disclosed beyond what the bead's own public about.md discloses. - Two beads sharing a display name collide to one slug; last writer wins (observed: 100 removals where 102 were expected). Fix candidate: suffix `-<guid8>` on collision. - The fedwiki sitemap (`status/sitemap.json`) regenerates from the page store, so removed pages leave the sitemap on the next server restart/edit cycle, immediately after cleanup it may briefly list ghosts.