oficina / memoria Public

feat/home-consumer: connect 01166fc, sign-out chain, All apps open

On branch feat/home-consumer

Preparation for switching Memoria to Oficina sign-in (Paul, 2026-09-30: "Definitely switch to Oficina sign-in"). Plan: oficina/polish-audit/SSO-SWITCH.md.

Why: Memoria pinned amadan.net/oficina/home/connect at 5b8d841 (2026-09-10). That is before 01166fc, which added the sign-out chain to id.*, the signed-in /signin shortcut and Credentials.Directory. LoadConfig also builds connect.Credentials by hand, so HOME_DIRECTORY_ORIGIN was never read.

What changed:

  • Bumped connect to 01166fc. The change is additive.
  • LoadConfig reads HOME_DIRECTORY_ORIGIN. It is unprefixed and read only alongside the four MEMORIA_HOME_* values, matching CredentialsFromEnv. DecodeCredentials validates it.
  • a.directory is set in WireHome. The header nav shows "All apps" linking to {directory}/ on every signed-in page: the page, documentPage and connectorPage structs, plus credits.
  • Amended the spec sentence "Sign-out … stops at /signed-out".

Tests: home_consumer_test.go loads config through LoadConfig from the environment, the same way production does. All three tests failed on the old pin:

  • TestHomeSignoutChainsToIssuer got a 303 to /signed-out.
  • TestHomeSigninWhenSignedInReturnsToPage got /auth/start?....
  • TestAllAppsLinksToDirectory found no link.

All three pass now. newHomeRouterFixture was split so a fixture can take an injected config. The existing router test now expects the chain. make ci exits 0 with the pinned toolchain, and credits were regenerated with no change.

Known, not fixed here: /help/open-source sends Referrer-Policy: no-referrer while showing Sign out. Signing out from that page lands on id.*'s "Finish signing out" page, which is recoverable.

Not deployed and not merged. A whole-branch review is required first. Note that other sessions have open memoria branches (fact-box, door-links).

Added commit: in Home mode, a signed-out page request now carries return_to. The local plugin's RequireSession redirected to a bare /signin, so after Home sign-in you landed on the root instead of the page you asked for. TestHomeSigninReturnsToTheExactPage failed first (/connectors went to /signin) and passes now. make ci exits 0.

Reviewer: Astra (GPT-6-Astra via Codex), whole-branch review requested by Paul, 2026-10-01.

VERDICT: APPROVE-WITH-NITS — feat/home-consumer at 6030871d970d189110e6d48d948f2756b0f4292e, one merge-base diff against fetched main 97b8a23. No blocking findings.

  1. P3 / non-blocking — internal/memoria/home.go:84–87 (activated by go.mod:9's connect bump): a revoked Home session still skips Sign in. After real fixture linking/sign-in and removal from owners, /signin redirects to /, which correctly returns 404. Root and connector access fail closed; there is no automatic redirect loop or admission bypass. The ordinary sign-in URL nevertheless cannot start recovery while that cookie remains; /signin/local does remain reachable. Fix: gate the already-signed-in shortcut on currently resolved/admitted identity, preserve storage failures, and provide a terminating recovery/refusal outcome. Cover the still-live-but-no-longer-admitted session through the real router.
  2. P3 / known, non-blocking — internal/memoria/credits.go:30: credits suppresses the sign-out hop's Referer. Home then safely requires its “Finish signing out” button. The 307's policy cannot restore referrer information already removed by the originating page. Fix: use strict-origin on this non-sensitive page, while retaining no-referrer on outbound credits links. SSO-SWITCH.md explicitly accepts the extra click for now.

Security and correctness: LoadConfig reads the unprefixed optional directory only alongside Home credentials, and DecodeCredentials validates it; a partial Home configuration fails closed. Protected signed-out GET/HEAD requests carry an encoded RequestURI to /signin; connect validates it with handoff.SafePath before redirect use/storage, so it is not an open redirect. The destination is sealed and never sent as a raw redirect destination to Home's authorize endpoint. The identity path maps opaque Home subjects through explicit links to the sealed local identity and rechecks Admit; local identity allocation requires independent local proof and is not SSO auto-provisioning. The All apps field reaches the normal page/document/connector/credits render paths.

The connect sign-out chain matches Home main 7a0f19c: same-origin CSRF first hop, local revocation, six handoff-cookie clears, fixed 307 to the validated issuer, no-store, required referrer policy and issuer-inclusive form-action. The normal form has no named payload; the browser resends the POST but selects cookies for id.*, not Memoria's host. Home permits only registered consumer evidence and returns to that registration's /signed-out, never a request-provided destination. Missing evidence and cross-site Lax-cookie cases retain explicit confirmation.

Verification: all Memoria execution went through make with GOTOOLCHAIN=go1.26.5, CGO_ENABLED=0, TMPDIR=/tmp. make ci passed on rerun, including credits and migration checks. The initial concurrent gate run failed the unchanged timing test internal/memoria/fact/fact_test.go:488 at a 3.07 doubling ratio; its isolated rerun passed. I am recording both results, not treating the first as a sign-in regression. Hub CI PASS at 6030871, CLI exit 0.

Extra scratch router probes passed: first-hop hostile/missing-origin and same-site CSRF rejection without ending the session; unsafe absolute/scheme-relative/encoded-slash/backslash/auth/dot/control return paths; preservation of a deep path with query; revoked root/signin/deep navigation terminating at 404; local recovery still reachable. Home sign-out/assertion and connect tests passed. No optional browser SSO stack or production configuration changes.

Mutation proof: removed the directory read from LoadConfig in scratch. New TestAllAppsLinksToDirectory failed on the missing configured link. Restored source; the same uncached test passed.

Overlap/order: #21 (feat/r2-shared-chrome, fetched head 917e2b6) already includes this exact head via 89fcf99. Its overlap is broad: app/home/config/render/handlers/connector/credits/layout and tests. Prefer #20 first, then merge fresh main into #21 using the documented squash-stack procedure; compare content and retain the newer chrome. Its switcher replaces this header link and its shared return wrapper supersedes this Home-only wrapper. Do not duplicate either. If #21 lands first, #20 is already included and should be treated as superseded after a content check. #21 still needs its own whole-branch approval.

Overall order: Docs #73; Calendar #17 → rebased #23; Memoria #20 → #21; Calls #22 before its unfinished chrome branch. Paul handles secrets/config after the code lands.

Review only in detached /tmp worktrees. Mutations restored; no existing checkout moved and no commits, pushes, merges, deployments or config writes. Posted as the agent account under the repository's amadan skill, never -as-human.

Landed as part of the Memoria shared-chrome squash 0cc9d09 (#21), which merged this branch in. Astra reviewed it there and here: approve with nits. P3 #1 (an admission-aware /signin shortcut) and P3 #2 (no-referrer on credits) are follow-ups. This branch is superseded and needs no separate merge.