# Mount the directory-owned Docs entry

Status: base9ccdd2b independently APPROVED by configured claude-fable-5 (Home29); the expired-session continuation clarification below requires a follow-up verdict. Base is reviewed, unmounted chooser07043915daebae10512a710915263093ed793b5b and directory store4c0d84811a018c53a47b604a0de5885baec0771e. Home discussion24 approved the parent directory designfa64e28. Docs Home linking is now deployed1e862f8 and merged5fe0447. This plan does not make that existing-account pilot into authoritative team admission.

## Existing source and boundary

`runtime.go` applies only sessions and shared handoff migrations in the directory role. Credential and legacy roles use BootSchema. `newDirectory` constructs its own sessions, renderer and shared consumer, then `directoryRouter` mounts exact-host validation, session middleware and a CSRF-protected group. The existing directory page is a signed-in placeholder. No directory operator/provisioning write API exists. `directorydata` already supplies sealed team names, live membership choices and required caller destination validation; `docsChooser` is already reviewed but unmounted.

Mounting this read path adds no membership, team, owner, consumer, queue request or app instance. An empty existing directory remains empty. Do not import credential people migrations, pepper, mail or private assertion keys into the directory process. Do not seed the running preview DB or infer ownership from its first visitor. Platform117 was read again on2026-09-08 and still records no fenced-maintenance implementation. Operator/bootstrap transport and real two-team provisioning remain separate prerequisites.

## Compiled app registry and destination policy

Introduce a small compiled registry type with slug, display name, integrated and provisionable fields, plus a foreign origin allowed only for unintegrated entries, matching Home spec section11. As of 2026-09-11, the registry has six integrated entries: Docs, Sheets, Slides, Calendar, Meet and Memoria. All remain provisionable false. Integrated means the app has a reviewed assertion consumer; it does not assert public release readiness. Each entry uses the shared team chooser and templates. The proposed app grid and New action remain unimplemented. Preview launcher overrides continue to take precedence. Further registry entries require consumer evidence and a separate provisioning decision.

The directory store receives the integrated slug set copied from this registry. Its route validator accepts only an integrated registered app and an exact origin equal to https://<stored-label>.<registered-slug>.oficina.works. The existing store validates the stored label, sqid, consumer ID and canonical HTTPS origin before calling this policy. No request supplies a hostname, foreign URL, URL suffix, app registry entry or policy override. Origin matching uses constructed string equality, not suffix matching. Test lookalike domains, prefix tricks, case, ports, paths, credentials and external origins.

A live team_instances row is an authoritative directory record created only by the separately reviewed operator transaction after exact credential-consumer registration is verified. This read policy does not pretend to independently query the credential process or turn a syntactically valid consumer ID into proof of registration. It validates the canonical route while the future audited lifecycle writer owns the registration check. There is currently no exported network writer that can mark an instance live. Consumer ID is not arbitrarily re-derived here: the row stores the operator-registered ID, whose existing bounded structural validation remains required.

Do not add per-team environment pins, wildcard destination policies or an immutable duplicate of all team records. This runtime slice does not accept the current overnight-docs platform hostname as a team origin. A separately reviewed pilot manifest or real canonical aliases with an audited registration record is needed before live synthetic team rows can point there. Tests can use canonical alpha.docs.oficina.works and beta.docs.oficina.works with loopback TLS routing; those are fixtures, not live provisioned teams.

## Construction and route mounting

Merge directorydata.Schema after existing directory sessions/handoff sets in directorySchema only. Keep both credential and legacy BootSchema unchanged. Construct a directorydata.Store inside newDirectory using the existing directory DB writer and sealer, then construct the Docs chooser. Keep these as private App fields or in a private directory runtime bundle; any constructor error refuses boot with a fixed error, without logging stored names or routes. Role validation remains mandatory even for direct constructor tests.

Mount GET /apps/docs and POST /apps/docs/team under the directory's existing exact-host and session middleware, inside the normal CSRF group. Do not add another login form or require users to authenticate separately on the directory. Anonymous GET uses the chooser's existing fixed shared-consumer return to /apps/docs. Selection keeps its own reviewed CSRF check as well. A POST with neither valid Origin/Referer nor accepted fetch metadata must refuse; test the real framework behavior rather than assuming all missing-Origin requests are forbidden. A real same-origin browser POST supplies accepted fetch metadata. Foreign origins, including the credential sibling, must refuse.

Before chooser handlers consult the session, reject duplicate actual session cookies for these two chooser routes. Do not change the existing assertion callback or global session flow in this slice. Constructor tests exercise the actual App.Handler and role routing so credential and legacy hosts cannot reach the chooser. Every chooser response, including errors and unknown-host refusal at the app boundary, remains no-store; use the narrow route boundary as needed without weakening shared host checks.

Zero choices renders the reviewed empty page; one choice uses the existing303 GET redirect to the registered app's root / (amended 2026-09-23 from /auth/start with local return /; the successful POST continuation targets the same root). Multiple choices render the reviewed form. The successful POST response depends on separately reviewed continuation amendment95dcd682ac106ab725d9a85f141285709ab3659c: return200, immediate meta-refresh plus a plain fallback link to the revalidated stored destination. Preserve CSRF and the post-selection membership re-query. This avoids an unbounded outgoing CSP list; no wildcard or global form-action change. Do not implement or deploy the successful POST integration before that amendment is approved and its browser/fallback tests pass.

A source-backed browser check exposed a second redirect-chain case: an expired directory session on an otherwise same-origin chooser POST currently returns303 to /auth/start and then to the credential origin, where form-action self blocks it. Handle this inside the chooser subject helper after Select has applied its existing method/path checks and CSRF protection. When that helper finds no current session on POST, render the same200 continuation template to the fixed validated directory origin plus /auth/start?return_to=%2Fapps%2Fdocs. An anonymous GET still uses the existing303. Refactor only the common final rendering/header step; the successful-choice caller validates its stored Docs origin, while the reauthentication caller constructs its destination entirely from validated runtime origin and fixed constants. No submitted team, arbitrary URL, query parameter or resource path enters that destination. Both set no-store and no-referrer and keep a real plain fallback link. This starts authentication only; it does not retain the stale submitted selection or create a membership/session/proof. After sign-in the chooser re-queries current membership and the person selects again if several teams remain. Test real expired-session POST in Chromium/WebKit with scripts on/off, fallback navigation, fixed Home return and unchanged CSRF/unknown-host refusal; do not treat the old blocked behavior as accepted integration.

Generic app entry never transfers a document path through Home or guesses which team's resource it is. Direct resource returns stay in the original Docs instance's sealed handoff. No global selected-team cookie is introduced; two tabs select independently. The public bare docs.oficina.works alias remains its original instance until the separately reviewed compatibility cutover. This slice adds only the directory /apps/docs entry path, not a new bare-host gateway or deployment route.

## Evidence and release sequence

Use actual LoadRuntime/NewRuntime/App.Handler tests, not only the unmounted chooser fixture. Assert directorySchema gains only the approved namespace; credential/legacy schema and secret loading remain unchanged. Reopen a disposable migrated directory DB with exact old live Home02f9ac0 code and show existing directory sessions/handoff/signout survive, then reopen with new code without data loss. No platform restore claim follows from that test.

Seed synthetic teams only through explicit local test fixtures; test zero/one/two choices, canonical route rejection, disabled/pending instances, membership removal after rendering, duplicate cookies, wrong hosts, wrong methods, extra query/form fields, real missing-Origin/fetch-metadata cases, fixed anonymous return and two independent tabs. Extend existing privacy gates over newly mounted schemas, sealed names and errors. Repeat actual Chromium/WebKit narrow/wide and scripts on/off flows with the shared issuer and canonical synthetic Docs origins, including successful POST continuation and plain fallback; no test-only CSP relaxation.

After full Home gate, actual exact-head CI and independent whole-branch Fable approval, deploy the binary to the existing paired Home previews with original roles, keys, consumers and DB paths unchanged. Verify both API body and edge header. The existing directory will expose the empty chooser until audited team provisioning exists; announce exactly that scope. Live unsigned entry and authenticated empty-state checks must be distinguished; do not fabricate a human session to claim the latter. Obtain canary evidence review before normal Amadan merge. This incremental mount does not complete roster admission, team creation, owner step-up, guest/ex-member app tiles, production gateway cutover or restore acceptance.

Consumer evidence for this extension: Docs main a8f5c0e; Sheets 6916ac8, Slides 6ed0951, Calendar 6874345, Meet 34fc367 and Memoria 0ee92ed passed TestHome journeys on 2026-09-11. Reviewed preview deployment receipts are in /home/paulca/.cache/oficina-native-home-live/release.json; journey receipts are in /home/paulca/.cache/oficina-rollout-20260911/journey-tests.json. The extension review by Claude Fable 5.1 (claude-fable-5-1) approved source and preview scope at dbccdae. Home discussion53 holds the merge review record. This evidence does not approve public app promotion, enable roster consumers, or supply the missing platform operator dispatch.
