oficina / sheets Public

Connect existing Sheets accounts to Home open

On branch feat/home-connect

User approved bringing other apps into working Home launcher. Explicit base20c269129733dcd57f5d1c4f03bcccd5f5f4ff2f preserves deployed workbook chrome/theme/durable saves; discussion25 records Fable approval, exact CI, live version. Base is not yet merged to main4aea; root owns separate integration/merge plan, do not drop preview work. Consume shared Home/connect adapter (Home52) for transport, proof linking, navigation, rendering and config; Sheets only supplies current member lookup, admission and sessions. Opaque Home subjects must never reach memberFor or seed sample content. Independently verified local account + fresh Home proof + explicit confirm links stable existing member ID. Current grants/roles and allowlist remain authority on every request. User copy-review waived. Root owns Fable/deployment; no config or deployment by implementation agent.

Home adapter candidate a0227cd preserved deployed base20c and passed full make test. Fable claude-fable-5-1 APPROVE source review, conditional exact CI/runtime canary. Eight synthetic actual-handler browser checks pass Chromium/WebKit 320/1280 with JavaScript on/off, including shared Keymail continuation POST-to-GET with no Referer or CSP errors. Follow-up1baa40e refuses retained Home sessions when Home is disabled and preserves sessions on transient link storage failures; tests pass, full gate and narrow Fable follow-up running. No Home proof creates members/content; explicit independently verified local completion may create its ordinary local member. Local sessions in enabled mode now recheck current admission too. No deployment or merge by this agent; root orchestrates final shared Estilo pin and release.

Exact candidate6916ac88c473a83f63860f61fcca05f39d1c1bd6, terminal make test + exactCI green + release build. Eight refreshed browser cases passed and Keymail scriptless/CSP navigation cases passed. Fable final adoption verdict:

Verdict: APPROVE all five (Calendar 6874345, Slides 6ed0951, Memoria 0ee92ed, Meet 34fc367, Sheets-core 6916ac8). Reviewer: Claude Fable 5.1 (claude-fable-5-1), read-only, 2026-09-10. No files written, no commands run.

What each delta contains

Every branch.diff is confined to go.mod, go.sum, and the private-module build configuration. No Go source, template, migration, CSS, or route changed in any app. Base vs source was cross-checked through the diffs plus the CI logs on the exact binding.json SHAs.

| App | connect | estilo | GOPRIVATE | Extra | |---|---|---|---|---| | Calendar | 868f189 → 5b8d841 | new indirect dec3908 | Makefile | none | | Slides | 868f189 → 5b8d841 | new indirect dec3908 | Makefile + 10-gate | none | | Memoria | 868f189 → 5b8d841 | new indirect dec3908 | Makefile | stale go.sum rows dropped | | Meet | 868f189 → 5b8d841 | new indirect dec3908 | Makefile | connect/handoff moved indirect → direct | | Sheets-core | 868f189 → 5b8d841 | 672a447 → dec3908 direct | already present | none |

All five pin identical h1 hashes for connect 5b8d841 and estilo dec3908, matching the module cache and the approved shared-library review.

Contract compatibility, verified against the module cache

  • Line-count fingerprint of connect 868f189 vs 5b8d841 shows only view.go, oauth.go, go.mod, go.sum, routes_test.go and the two deleted templates differ. bridge.go, routes.go, config.go, api.go, link_handlers.go, navigation*.go and linkstore/ are unchanged. No security or session logic moved.
  • connect.PageData and connect.OAuthData are now type aliases to Estilo's homeconnect structs with identical field sets. Memoria's home_render.go field of type connect.PageData compiles unchanged.
  • connect.Templates() now forwards Estilo's embed FS, which still contains templates/home_link.html and templates/oauth.html. Calendar, Slides, Memoria and Meet parse templates/home_link.html; Sheets parses templates/*.html. Both patterns resolve.
  • No duplicate or omitted definitions. Grep across all five source/ trees finds no local define "home-connect", home-oauth-head or home-oauth. Each app references them exactly once from its own wrapper template. The home-oauth* definitions are only parsed and only used by Sheets.
  • Meet's move of connect and handoff from // indirect to direct is a go mod tidy correction. Meet's base source already imported connect directly.

Behavioural change inherited from the library, and why it is safe

The failed page action moved from /account/home to /signin/local. All five apps mount GET /signin/local (Calendar routes.go:40, Slides app.go:71, Memoria app.go:79, Meet app.go:220, Sheets home_routes.go:11), so the link resolves. The template also adds <bdi> around the address and rst-btn="ghost" on the Back link. Rastrillo v0.23.0 ships the ghost twin, per the Estilo review. Purely presentational.

Build configuration

Calendar, Memoria and Meet CI steps exec make <target>, so the Makefile export covers them. Slides' 10-gate is a bash script that also runs make ci, so its own export is needed and present. Sheets already carried the comma-safe form. Every CI log shows go: downloading amadan.net/oficina/estilo.git ...dec3908b237c succeeding, which settles the private-fetch infrastructure question left open by the connect review.

Gate evidence

All five *-ci-final.json files are state: success on the exact binding.json SHAs with vet, fmt, test and migration check green. Sheets adds build and race; Slides adds the full Rust, wasm and Node gate. Memoria's sibling package passed, and LoadSiblings still returns an empty roster on empty MEMORIA_SIBLINGS without requiring MEMORIA_TEAM. No connector or sync code is in the delta.

Low, non-blocking

  1. export GOPRIVATE := $(GOPRIVATE),amadan.net/oficina/estilo.git yields a leading comma when the ambient value is empty. Go ignores empty patterns. Sheets' $(if ...)$(comma) form is cleaner; align in a follow-up.
  2. Calendar's go.mod puts the estilo require in a third require block. Cosmetic tidy output.
  3. Memoria's go.sum drops golang.org/x/net and x/term rows. CI's readonly module mode passed, so nothing needed them.

Bounded config concerns

None of the preview configuration is in these deltas. Origins, consumer IDs, issuer, DEK, allowlists, env and channel are runtime values. The following remain operator-side gates before runtime merge, unchanged by this review:

  • Routes.Middleware 404s any Host that is not the configured origin, so each https://overnight-APP.bds.oncarlos.com origin must match the served hostname exactly. Verify with an unsigned GET /signin returning 303, not /healthz.
  • Each app needs its full *_HOME_* set including a dedicated stable DEK; a partial set refuses boot. Existing instance and content keys are untouched by this delta.
  • Memoria: MEMORIA_SIBLINGS and MEMORIA_TEAM both empty.
  • Slides: any preview database that booted the earlier home_connect ledger namespace must be discarded, per the Slides final verdict.
  • Refresh the synthetic UI check on these exact dependencies and run live canary checks on Sheets canary/overnight-batch, Calendar canary/overnight-recurrence, and the new Slides, Meet and Memoria canary/overnight-home before enabling any Home launch link.

Preview deployed and verified: https://overnight-sheets.bds.oncarlos.com/signin . Exact source 6916ac88c473a83f63860f61fcca05f39d1c1bd6; process /api/version and X-Carlos-Version both 6916ac8. Exact CI and mandatory local gate passed, Fable final adoption review APPROVE. Eight synthetic rendered account-link browser checks plus four live Chromium/WebKit JS on/off redirects passed, exact consumer sheets-overnight and credential origin verified. Home preview launcher destination enabled. No human credentials or mail used for tests; user completes explicit dual-proof local account connection. Existing local content identities preserved. Source branch carries previously deployed baseline work not yet squashed into scaffold/older main. Retained for review of cumulative merge provenance; preview deployment does not claim a main merge.

User-reported Sheets preview sign-in failure at2026-09-10 05:43 UTC diagnosed from real app logs: SMTP530 Authentication required. overnight-batch had explicit empty CARLOSSMTPUSER/PASS masking default authenticated SES config. Removed only these two plain overrides, retaining all application/content/Home keys and default config. Config read-back verifies absence. Scoped preview restart confirmed at05:45:48 UTC; /api/version body+edge6916ac8 and /signin/local email form200. No human email was sent during verification; actual new delivery still needs user retry. This exposes a gap in prior unsigned browser smoke tests: they proved navigation, not SMTP delivery. Evidence saved privately under /home/paulca/.cache/oficina-sheets-signin-incident.

Fable configuration-repair review (based on supplied diagnosis/config evidence, no independent commands):

APPROVE. The diagnosis is sound and the repair is the smallest change that addresses it.

Why it holds up

  • Causal chain is complete: empty user override causes Rastrillo to skip PlainAuth, SES on 587 rejects with 530, the magic-link send fails, the app surfaces a generic sign-in error. The 05:43 log line is direct evidence, not inference.
  • Removing the two overrides is correct rather than setting values in preview. Default already carries a nonempty username, the SES host, and the sealed password key, so layering does the work.
  • Scope is right: only the two offending keys, only the preview layer, only the affected instance restarted. Connection keys and default config untouched. No secrets printed, no human mail sent.

Notes, none blocking

  • Read-back proves the overrides are gone, not that the effective config is correct. After restart, confirm the resolved SMTP user is nonempty and the sealed password resolves in the preview layer, without printing the value. A single sign-in attempt with the 530 absent from logs is the real test.
  • If the overnight-batch layer serves other preview instances, they now have the corrected config on disk but still run the old values until restarted. Fine if the ticket is Sheets only, but say so explicitly.
  • Find out why the empty overrides existed. If they were a deliberate "no real mail from preview" guard, this repair reverses that policy and preview now sends production SES mail to real addresses. If they were template residue, add a check so empty strings cannot land on these keys again.
  • Follow-up for Rastrillo: silently skipping PlainAuth on an empty user is a footgun. It should warn at startup or fail config validation when a host is set without credentials. Out of scope here, but worth a ticket.

Reviewer: Claude Fable 5.1 (claude-fable-5-1), read-only, 2026-09-11. No files written, no commands run. Git ancestry was not re-derived; it is taken from the binding, the history files, and the Sheets merge commit that names main.

Verdicts

| App | Candidate → main | Main source merge | Public promotion | |---|---|---|---| | Sheets-core | 6916ac8 → 4aea8f4 | APPROVE | APPROVE with residual checks | | Slides | 6ed0951 → 298e00c | APPROVE | APPROVE with residual checks | | Calendar | 6874345 → ecb0df9 | APPROVE as provenance-only merge, not release approval | REQUEST CHANGES |

Sheets-core

Provenance closes. Every commit on the branch has a recorded independent verdict: reconciliation merge of main at d1ecaaf (Fable APPROVE at a75c7a9), atomic batch d35cf94 and watermark 1aca44c (Fable APPROVE each, canary-driven on the preview), Estilo theme and durable-paste integration 20c2691 (Fable APPROVE, deployed and verified), Home adapter a0227cd (Fable APPROVE), the 1baa40e follow-up (Fable APPROVE, recorded only in the cache at oficina-sheets-home-review/r2/result.json, not in discussion 26), and the pin-only 6916ac8 delta. Exact CI is green including race. Journey tests exit 0.

Seams verified in frozen source. A Home subject never reaches member creation: members.go:85-93 resolves through the link then reads by id, and page.go:47-50 returns the link label. Member creation happens only at fresh, admitted local completion in home.go:139-147. Admission is rechecked per request, a storage fault answers 503 without sign-out, and a retained Home session with Home disabled answers 403. The person hook mirrors Admits including the live-grant join.

Residual checks, none blocking:

  • Issue 8 (revoking a teammate's viewer grant leaves them editor by team standing) was ruled by Paul to stay. Codex later rated it High. Re-affirm that ruling explicitly for a public instance, since the share screen says "Access removed" while write access remains.
  • The preview SMTP repair removed two empty overrides. No real sign-in has been evidenced since. One user sign-in with no 530 in logs is the proof. Confirm the empty overrides were not a deliberate "no production mail from preview" guard.
  • Advisory items carried from the atomic review: publish-after-unlock reorder, the 1 MiB batch body being below 500 cells at 8 KiB each, and menu Clear still firing concurrent single-cell writes.
  • Reconciliation follow-ups still open: narrow-viewport title wrap, no live region on the refused-drop overlay, rename not propagating live. Issues 11 and 12 remain open and minor.
  • Do not claim multi-worksheet import. Discussions 18, 22 and 23 are separate unmerged branches.

Slides

Provenance closes. Slices 1 to 3 each carry a whole-branch review with fixes recorded. The cold Codex spec review produced six P1 and nine P2 findings; ten are fixed on the branch and five P2s are deferred with spec amendments. The bounded-log-replay fix was ported by the owner and proven load-bearing. The krilla swap kept the ABI byte-identical and the gate rebuilds and compares both wasm artifacts. The Home adapter went REQUEST CHANGES at 433e39c, APPROVE at 6f01b94 with every Medium closed, then the pin-only 6ed0951 delta. The full gate is green on the exact SHA.

Seams verified in frozen source. currentUser revokes only on a refusal or identity error and logs plus 500s on a transient fault (auth.go:383-393). Both lookup hooks honour the ErrNoRows contract. The ledger uses the shared namespace. Offboarding deletes Home sessions through the link join and ends presentations. Export ownership is restored before render.

Residual checks, none blocking:

  • Three Fable-approved side branches are not in the candidate: the Estilo theme at 2c5ac01, the save-status adoption at 6d4c90a, and the CI support at 998534c. The gate already runs the JS, bundle and browser checks, so only theme consistency is deferred.
  • Uploads answer 503 until an object store is bound. That is a platform action, not code.
  • Moving to the public origin requires the CNAME and an origin flip together. The shared middleware 404s any Host that is not the configured origin, so verify with an unsigned GET /signin returning 303 after the flip.
  • The release condition about a preview database that booted the earlier ledger namespace is moot. No 433e39c deployment is recorded. Confirm rather than assume.
  • Test gaps carried forward: sign-out clearing the Home cookies, Host mismatch, and bridge construction failing after migration.

Calendar

Home adapter closes. The r2 review at 20e6dee is APPROVE with three conditions, all since met: the shared connect revision was approved, exact CI is green at 6874345, and the preview canary passed. The seams hold in source: person.go:31-43 resolves a Home subject only through the link and never calls person setup, the local branch rechecks the allowlist, and home.go:93 cross-checks id and address.

The cumulative branch is not release-approved, and this is substantive. The whole-release Fable review at 1a6dcd0 returned CHANGES REQUIRED with three code blockers in the initial calendar code. The candidate was cut from d961b65 and contains none of the fixes. I confirmed each absence in the frozen source:

  1. Fail-open expansion. occurrences.go:59-66 returns complete on a zone or start parse error, and the walk has no exhaustion or horizon distinction. The strict reader therefore treats a malformed or overlong busy series as complete with no occurrences, and a public booking page offers that time as free. This violates ruling B23. The approved fix (b0ef4fa, delta 1a6dcd0, Fable APPROVE, CI green) is on fix/recurrence-stepping-bounds only.
  2. DST gap and reminder repair. No inverted-span handling in ExpandComplete, and updateEventOn returns without rescheduling reminders. The approved patch 95dc52d (Fable APPROVE, patch scope) is on fix/edit-reminder-reschedule only.
  3. Feeds and exports emit a master per occurrence. store_feed.go:127-150 reads the expanded range and ics_export.go:87 writes RRULE on every view. Unfixed on any branch.
  4. No DEK correctness check at boot. app.go only resolves the key. Required by the whole-release review before any rotation or restore rehearsal.

Also not in the candidate: the Estilo theme adoption at 040eb25 and the themed 404 at cb87c75, both side branches.

What I recommend. Merge 6874345 to main now as provenance reconciliation, recorded in discussion 7 as explicitly not a release approval. Then rebase the two approved fix ranges onto main, take a delta review of the rebased result, redeploy the preview, and only then promote. Blocker 3 and the DEK check need new work before public promotion. Conflict risk is low: the Home commits touch auth, person, routes, app and config, while the fixes touch occurrences, event write, override and reminder files.

Low, for the record. A de-allowlisted Home person gets 404 per request but the Home session row is never revoked in Calendar, unlike Sheets and Slides.

Prior 1baa40e review receipt: Verdict: APPROVE for Sheets delta a0227cd → 1baa40e. Reviewer: Claude Fable 5.1 (claude-fable-5-1). Read-only; nothing mutated, no commands, no network. Write is disabled this session, so this message is the review record.

What was reviewed

  • r2/branch.diff applied over source/, whose files match every diff pre-image hunk exactly (app.go:277, home.go:103-106, page.go:1-8,52-58, test file ending at line 100). source/ is the a0227cd snapshot; 1baa40e is source plus diff.
  • rastrillo v0.23.0 auth/handlers.go and sessions/sessions.go; connect 868f189c5c77 api.go, bridge.go, linkstore/store.go.
  • Gate log r3, UI evidence results.json plus check.mjs and browser.log.
  • r2/result.json (the prior approval) is empty on disk, so its text could not be cross-checked.

Findings

  1. Disabled-Home refusal is correct. auth.RequireSession only resolves the cookie and stashes it in context; it never calls Authorize or creates rows. rejectDisabledHome reads sessions.Current and answers 403 before any handler runs. identity() now refuses Home-method sessions when a.home == nil, so the index page renders anonymously and actor() yields no member. Every runtime memberFor caller is unreachable from a Home session in this mode. The test asserts 403 and an unchanged member count.
  2. 503 without revocation is correct. ResolveSession → links.Resolve maps every DB error, including the LookupPerson hook, to ErrStorage. The new branch emits no Set-Cookie and calls no SignOut. The test renames the links table on the shared on-disk SQLite file, asserts the cookie value is unchanged, restores the table, and asserts 200. ErrRefused still falls through to sign-out plus redirect, which is the intended revocation path.
  3. Gate log is terminally green and matches the .amadan/ci.d order (fmt is silent on success). It carries no commit hash and no -v, so it does not by itself prove it ran on 1baa40e or that the two new tests executed. Activation stays gated on green gate, exact CI, and runtime canary.
  4. UI evidence passes 8 of 8 (overflow, Continue button, OAuth GET with no Referer, CSP console errors). Its source/ snapshot predates this delta. That is acceptable because the delta changes only status codes on guarded routes, with no template or asset changes.
  5. gofmt: the page.go import regrouping and adjacent test functions without blank lines are gofmt-clean.

Non-blocking notes

  • The disabled-Home 403 on GET is bare text with no link, and the session row stays alive until TTL, a local sign-in (which rotates it), or POST /signout. Consistent with the stated identity-boundary design; a sign-in link would be a UX follow-up.
  • The 503 path resolves the session twice (ErrStorage probe, then identity). Harmless extra query. No Retry-After header.
  • A sessions-table outage, as opposed to a links outage, still yields a redirect via rastrillo's not-stale handling with the cookie preserved. Pre-existing and outside this delta.

Suggested verification for the parent

go test -run 'TestHomeDisabledRefusesRetainedOpaqueSession|TestHomeStorageFailureDoesNotRevokeSession' -v ./internal/sheets/

Run on 1baa40e alongside the full gate so the two new tests appear by name, then the runtime canary before activation.

Execution evidence 2026-09-11: a fresh local email sign-in request returned sent=1 with no SMTP error. Inbox arrival and link completion remain awaiting the user. Public rollout will preserve the previously recorded issue 8 ruling; no new user ruling is claimed. Multi-worksheet import remains outside this branch.