# Connect existing Docs accounts to Home

Status: approved design at2e69825 by configured claude-fable-5, recorded in Docs discussion46 and /tmp/oficina-docs-home-link-review/r2-review.ndjson. Integration started 2026-09-08. Prerequisite for the user's accepted common app entry and team selection flow, not a substitute for the full two-team acceptance. Existing Docs production base87662f5 owns documents through people.id; legacy sessions and address grants use verified email. Home exchange is merged0dc863c and enabled on isolated previews. Fresh-assertion design is in Home branch feat/home-fresh-assertion, discussion21.

Preserve existing people.id, every OwnerID, grants, content encoding and encryption key. No assertion carries email; no lookup guesses that an opaque Home subject corresponds to an email; no fake email is created. Standalone deployments keep local authentication under Docs design §8.1. Suite authentication is explicitly configured and refuses partial issuer/key/consumer configuration at startup.

Use the reviewed shared handoff module for exact issuer/audience verification, browser pending state and replay consumption. Extract the existing Home purpose-bound sealing implementation into a shared package before reuse rather than writing another crypto adapter or using the content-encryption key for navigation. This extraction needs separate compatibility tests and review. Configuration must use CARLOS-compatible base64 settings learned from the Home deployment.

An additive identity-link table records exact issuer, opaque subject and existing person_id. Unique constraints cover both (issuer, subject) and (issuer, person_id). A link is created only after fresh authentication of both identities and an explicit same-origin confirmation POST. Concurrent identical requests are idempotent; conflicting links fail without replacing either row. No unlink or reassignment in this stage. Linking itself grants no new team standing or document permission.

Linking starts only with an explicit same-origin POST from an already signed-in, freshly verified local Docs account. It creates a bounded initiation record tied to that exact person_id, the hash of the actual local session cookie, the exact origin and the new shared handoff nonce. A reviewed shared-consumer start hook must bind that nonce before returning the authorization redirect; do not copy the HTTP/cookie protocol into Docs. A Home exchange that was not initiated this way cannot create a link proof. An ordinary unlinked SSO attempt tells the person to sign in locally and initiate a new link; its identity proof is not reused for linking. A completed initiated Home exchange produces a short-lived sealed pending identity proof without replacing the local Docs session. The proof contains verified subject, issuer, actual auth_time and expiry, never the raw assertion. Store it sealed in a pending-link row addressed by the hash of a random 32-byte host-only Secure/HttpOnly/Lax cookie. Keep it at most five minutes, sweep on boot and new starts, and refuse new rows at a hard 10,000-row cap. Confirmation atomically consumes this row with the unique mapping write, so replaying a copied cookie cannot perform another link. Bind sealed payload to its row hash and the exact local origin. The shared consumer consumes the assertion before creating that proof. The completed Home claim nonce must equal the initiation nonce and the current local session hash/person must still match. Confirmation requires both authentication times within five minutes and not in the future. A stale Home proof restarts the explicitly initiated exchange with max_age=300. A changed or stale local session invalidates the initiation and requires local reauthentication followed by a new initiation POST. Confirmation rechecks the same binding before committing; it never links a proof left by an unrelated browser sign-in.

Local freshness uses an explicit method-aware predicate, not generic RequireFreshSession alone. Only local magiclink or keymail methods count as independent local proof; home-assertion never counts. A nonzero original AuthTime must be within five minutes and not in the future. With zero AuthTime, only an actual local magiclink session may use its At, because the verified Rastrillo auth.admit path mints that row only after consuming the local email token. A Keymail session lacking AuthTime is stale and must use forced local magic-link verification. This bounded fallback is necessary: signin.CompleteMagic intentionally returns zero AuthTime and Rastrillo sessions.create stores it empty, so rejecting every zero AuthTime would make the existing email sign-in unable to link at all. Test that SSO re-minting, unsupported methods and old upstream gestures cannot use At as fresh evidence. The wrapper sets force on the local auth.Begin request for this specific reauthentication flow, retaining its normal allowlist and CSRF checks.

The confirmation page must clearly identify the local Docs account being connected. A fresh POST binds the proof to that authenticated local person in one transaction and consumes the pending proof. An already signed-in different account never gets silently replaced. Preserve the intended safe local resource path in bounded sealed browser state; default missing or malformed paths to the app root. Do not use query parameters or plaintext cookies for identity proof, raw assertions or resource paths. Local signin callback returns to the confirmation continuation only while a valid pending link exists; preserve normal signin otherwise.

Subsequent assertion sessions use Method=home-assertion and Subject equal to the opaque Home subject. Their AuthTime is the original verified claim time. A single Docs identity adapter resolves that subject through the exact configured issuer and identity-link row FIRST, loads the linked existing person_id and current local address, and supplies a typed request context to both owner resolution and admission/grant checks. Missing link/person is refusal; this path never invokes EnsurePerson or sample seeding. The legacy authentication path retains its existing EnsurePerson behavior. Replace direct auth.From consumers on suite-authenticated routes with this adapter so the opaque value cannot be treated as an email. Per-request requireAdmission sees the resolved local address; h.owner gets the linked person_id directly. Existing address-grant evaluation uses that already verified local address without disclosing it to Home. Admission must still apply the current roster or existing explicit grants; successful identity verification is not membership. Removal or stale roster state must invalidate standing on existing sessions, not just at sign-in. Membership implementation remains a required companion stage before general suite launch.

New Home-only users require a separately reviewed identity-model migration. The current people.email column is NOT NULL and unique, so empty strings or invented addresses cannot provide that migration. Do not claim all-team readiness by proving only legacy account linking. Any replacement principal model must preserve old IDs and prove forward/rollback behavior without overlapping legacy and new identity allocation.

Acceptance for linking: two local accounts and two Home subjects; successful explicit double-auth link preserves IDs and each user's owned/shared documents; stale/future proof refusal; cross-account session mix-up refusal; unique-link races; no replay after successful or failed completion; real local magic-link callback continuation; tampered/duplicate/missing cookies; hostile return paths; signed-in local account conflict; no raw assertions/proofs/paths in DB/WAL/logs; unchanged content compatibility and standalone signin. Demonstrate a meaningful failing mutation. Full Docs gate, independent Fable review and two isolated team browser runs precede common-host production cutover. Production host aliases and deep links remain unchanged until the existing instance has a verified team alias and explicit compatibility routing.


Review record: first design review at4cb40cb was dispatched explicitly with -m claude-fable-5 through Command Code; its final text could not introspect the model, so provenance is the runner arguments, not a self-identified serving version. Requested decisions were session representation, initiation binding and precise local freshness. The revised design supplies each; the method-specific zero-AuthTime magic-link fallback is justified by actual upstream source and requires re-review rather than silently claiming the review's AuthTime-only suggestion works. No code has been written on this branch.


Integration record: base is reviewed Docs preview8a12672 with Rastrillo0.23. Home issuer fresh-auth is merged3947850; shared sealer is mergedcc61976 and verified on paired Home previews16024be. Shared fresh/bound consumer candidate02f9ac0 has code review APPROVE and full local/actual CI, with preview rollout pending final evidence. Reuse combined nested handoff module481d2d5. Separate isolated storage and identity-adapter changes will be combined here before whole-branch review. No production routing or identity changes have been made.
