feat: app completion wave — audit-driven fixes + legal corpus#8
Merged
Conversation
… (CR-10 item 3) Source the first obvious estate-planning forms for the launch states from OFFICIAL .gov legislature/revisor publishers ONLY, verbatim (Rule 9 — no fabrication, no paraphrase, no LLM in the text path). Each source captured via deterministic HTML-to-text extraction and sliced between explicit statute markers; per-chunk citations carried by the ingestion pipeline. Sourced (7 sources, 64 chunks, dry-run PASS): - IL 755 ILCS 5/4-3 — will signing & attestation (ilga.gov) - IL 755 ILCS 45/3-3 — short form POA for property (ilga.gov) - IL 755 ILCS 45/4-10 — short form POA for health care (ilga.gov) - MD Health-General § 5-603 — advance directive form (mgaleg.maryland.gov) - MN § 507.071 — transfer-on-death deed + form (revisor.mn.gov) - MN § 145C.05 — health care directive provisions (revisor.mn.gov) - MN § 145C.16 — health care directive suggested form (revisor.mn.gov) Gaps recorded (NOT invented): IL/MN have no single fill-in statutory will form; MD Est.&Trusts POA forms + US estate-tax/HIPAA excerpts deferred to next batch. manifest.md: Source Policy table updated with sourced-forms section, per-source citations, dry-run result, and the Rule 9 gap log. build_manifest.py + extract.py committed for reproducible re-capture. Refs: ADR-044 (Legal RAG Corpus), CR-10 (RAG ingestion / item 3 corpus sourcing) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uard
Two integration-correctness fixes in the Google Photos import handler:
- Dedupe: hasHash() compared err to iterator.Done, which a single-doc
Doc().Get() miss never returns (that sentinel is only for iterator.Next()).
The dead branch was masked by a brittle strings.Contains("NotFound")
fallback. Replace both with the canonical status.Code(err)==codes.NotFound
check used elsewhere in the repo (payments/handlers.go), and drop the
string-match fallback. Removes the unused google.golang.org/api/iterator
import; adds grpc/codes + grpc/status.
- Bucket orphaning: NewHandler silently substituted the literal
"finalwishes-vault" when handed an empty bucket. Under a non-default prod
bucket that silent fallback writes imported heirlooms to a bucket the
signed-URL download path never reads, orphaning the photos. Keep the
literal for dev but emit a high-visibility log.Warn on fallback so a prod
env-var misconfiguration is visible instead of invisibly orphaning imports.
Document the default's sync contract with the rest of the vault pipeline.
(The matching VAULT_STORAGE_BUCKET->VAULT_BUCKET env-name unification in
cmd/api/main.go is owned by the probate-bucket worktree to keep globs
disjoint.)
Refs: api-googlephotos-bucket-dedupe (P1)
Changelog: Fixed Google Photos imports orphaning into a mismatched bucket
under non-default prod config, and hardened the duplicate-photo check to use
the canonical gRPC NotFound status instead of a dead iterator.Done sentinel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ule) email.ts helpers defaulted createdBy to 'system', which the fail-closed mail create rule (createdBy == request.auth.uid) rejects — a latent trap that would silently fail the instant any helper was wired up. - sendEmail: createdBy now required; throws eagerly on a falsy uid instead of writing a doc Firestore will reject. - sendInvitation/Welcome/NotificationEmail: userId param is now required. - sendPasswordResetNotification: runs unauthenticated and cannot satisfy the rule client-side; now throws and documents the server-only (Go API / Admin SDK) path instead of queuing a doomed doc. - useEmailService: resolves the signed-in uid (throws if unauthenticated) and drops the unauthenticated password-reset from its surface. No firestore.rules change — the rule is correctly fail-closed. Refs: web-email-helpers-trap (P2 RC-blocker audit) Changelog: Fixed latent dead-code mismatch where email.ts helpers defaulted createdBy to 'system' and would be silently denied by the fail-closed mail create rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e versions
Rule 30 doc-currency sweep (docs-only, no code change):
- web/src/lib/README.md: expand Files table from 10 to all 26 shipped
modules, each with one-line purpose + governing ADR. Add Flagship
Subsystem Architecture sections for persona (ADR-046), the Shepherd
(ADR-040), and the Illinois Probate Engine (ADR-043). Fix stale
"Firestore Rules v3.0.0" -> v5.0.0 (Architecture + Security sections).
- web/src/routes/README.md: add 4 missing shipped routes (forms,
life-chapters, probate, soul-log) plus events; mark code-split (.lazy.tsx)
routes with a legend; correct the now-false "no route-level code
splitting yet" Known Limitation.
- docs/ADR-INDEX.md: fix header that named the wrong product
("Sirsi Nexus platform" -> "FinalWishes (The Estate Operating System)");
bump Last updated to 2026-06-14.
Refs: RC-blocker docs-rule30-readmes bucket (P2)
Changelog: Docs — lib/routes/ADR-INDEX READMEs reconciled to shipped state; Firestore rules version corrected to v5.0.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n load shared messages
HeirWelcome renders ONLY for non-owner roles (heir/executor). Its soul-log
list query was unconstrained ([orderBy('createdAt','desc')] only), but the
Firestore soul-log read rule permits a non-owner to read an entry ONLY when
visibility=='shared' && request.auth.uid in resource.data.sharedWith. An
unconstrained list query by a non-owner cannot satisfy that resource-data
constraint, so Firestore rejected the ENTIRE query with permission-denied —
the heir's "messages from your loved one" section always failed to load for
the exact users it is built for.
Mirror the canonical non-owner constraints from
estates.$estateId.soul-log.lazy.tsx (ADR-046 #1):
where('visibility','==','shared'),
where('sharedWith','array-contains', profile.uid),
orderBy('createdAt','desc')
The query path is gated on profile.uid (null until known) so no denied
unconstrained read fires while auth resolves. The required composite index
(visibility ASC, sharedWith CONTAINS, createdAt DESC) already exists in
firestore.indexes.json — no index change needed.
Also replaced the personalEntries taggedPeople (display-name) client filter
with a pass-through: the query + rule now guarantee every returned entry is
shared with this uid, and the old name-match was a schema mismatch with the
rule's sharedWith UID gate. Added sharedWith?: string[] to the local
SoulLogEntry type.
Refs: docs/ARCHITECTURE_DESIGN.md, ADR-046 (soul-log per-recipient sharedWith), firestore.rules (soul-log read), firestore.indexes.json
Changelog: vNEXT — Fix: heir/executor welcome screen now loads shared Soul Log messages (constrained query matches Firestore rule + existing sharedWith composite index) instead of permission-failing
Diagrams: §8 (Firestore Data Flow) — non-owner soul-log read path unchanged (rule already enforced; frontend query now conforms)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aling pending-grant state
The accept-invite flow polled estate_users/{uid}_{estateId} for accessGranted
12x/1s, then dropped a cold-start race into status 'error' whose only action was
"Go to Home" — stranding invitees away from their invite on a timing-sensitive
path (autoMatch Cloud Function cold starts routinely exceed 12s).
- Add a distinct 'pending-grant' status, separating the benign timing race from
genuine not-found/revoked errors.
- Attach a live onSnapshot listener on the estate_users junction doc while pending,
so the invitee is taken straight in the instant the grant lands — even after the
fixed poll window expired. The wait is now self-healing.
- Render an "Almost There" screen (Royal Neo-Deco: gold spinner, Cinzel heading,
glass card) with a primary "Try Again" (re-runs acceptInvitation/re-polls),
secondary "Refresh" (window.location.reload), and tertiary "Go to Home".
- Extract finalizeAccess() so the poll-success path and the live listener share one
guarded route-in.
Refs: docs/user-guides/inviting-team-members.md, ADR-031 (Secure Enclave Dashboard)
Changelog: web — accept-invite cold-start race now offers Try Again/Refresh instead of a dead-end error
Diagrams: §Identity (heir-invite accept flow) — accept now has a pending-grant holding state
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point the shadcn semantic tokens at the brand so on-brand is the default, not a per-call override (removes the need for ~177 manual bg-[var(--royal)] overrides): - --primary -> var(--royal), --primary-foreground #fff (default <Button> is Royal Blue, not near-black); fixed default-variant hover (was [a]:-gated, never fired on real buttons) -> hover:bg-primary/90 + shadow. - --ring -> var(--gold): Input/Select/Textarea/Button focus halos are now the gold accent, not grayscale oklch(0.708). Form fields also get a royal-bright focus border for the classic gold-halo + royal-edge interaction highlight. - New --ink / --ink-muted (royal-tinted) tokens; --muted-foreground -> var(--ink-muted) so muted/CardDescription text reads on-brand, not slate grey (Rule 27). Exposed as --color-ink / --color-ink-muted utilities. - Mirrored brand wiring into secondary/accent foregrounds, sidebar tokens, and the .dark surface (primary -> royal-bright, ring -> gold) so dark forms and buttons stay luminous and on-brand. Scope: globals.css + ui/ primitives only (route-level slate replacement runs in parallel without collision). Web build green. Refs: CLAUDE.md (Rule 27 Royal Neo-Deco Fidelity), docs/TECHNICAL_DESIGN.md Changelog: design — shadcn primitive defaults wired to Royal Blue + Gold tokens; brand focus rings; new --ink/--ink-muted text tokens Diagrams: n/a (token/style-only change) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ervice Eliminates all production-reachable fabricated/mock data from the estate ConnectRPC service (FIX BUCKET api-estate-service-mocks, P0). EstateService now implements ONLY the RPC the product actually consumes — GenerateUploadUrl (the signed-URL issuer used by every vault/memoir/obituary/ time-capsule uploader; web/src/lib/client.ts confirms it is the sole caller). The other 16 RPCs had ZERO frontend or server-side callers and shipped hardcoded demo data; they are no longer overridden, so the embedded UnimplementedEstateServiceHandler answers each with a clean connect.CodeUnimplemented — an honest signal, never fabricated data. Findings resolved: - GetEstateMetadata fabricated a "34% complete / Active / OWNER" placeholder on ANY live Firestore read error (production-reachable masking of real failures). Removed entirely — no method, no fabricated fallback. - GetAIInsight returned hardcoded invented AI text branched on EstateId=="estate_lockhart", with no Firestore/AI path. Removed; the real AI engine lives in internal/guidance (advisor.GenerateInsight, Claude Opus + RAG). - 16 of 17 RPCs were dead code with hardcoded Lockhart/Sarah-Johnson demo branches (ListEstates, GetEstateMetadata, ListAssets, AddAsset, ListBeneficiaries, AddBeneficiary, ListVaultDocuments, ListMemoirs, UploadMemoir, GetObituary, SaveObituary, GetAIInsight, GetGovernanceSettings, ListNotifications, RegisterEstate). All removed. - AddAsset hardcoded Status="Verified" with no input validation; AddBeneficiary persisted Share="TBD" with no validation. Both removed (no consumers). - All "estate_lockhart"/"user_tameeka"/"Sarah Johnson"/dollar-value demo literals are gone from the binary. Security hardening: checkEstateAccess no longer silently GRANTS access when the estate store is unconfigured (s.fs == nil); it now fails closed with FailedPrecondition, since membership cannot be proven without the store. Tests (service_test.go) previously asserted the fabricated demo data; replaced with tests that pin the honest contract: GenerateUploadUrl gating, the fail-closed checkEstateAccess, and Unimplemented for every removed RPC. Touched because the suite is a compile-time dependency of the in-scope service.go change and directly codified the mocks being purged. web/src/lib/client.ts unchanged (read-only reference; its sole call, generateUploadUrl, is preserved). Refs: CLAUDE.md (Rule 0 Minimal Code, Rule 2 Implement, ADR-031 Secure Enclave), docs/SECURITY_COMPLIANCE.md Changelog: Unreleased/Fixed — EstateService: removed production-reachable fabricated metadata/AI-insight + 16 dead mock RPCs; checkEstateAccess fails closed without an estate store. Diagrams: §8 (Firestore Data Flow) — estate read/write remains direct-Firestore in web; EstateService surface narrowed to GenerateUploadUrl. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wired-but-inert integrations now fail gracefully and hide their entry points until the owner provisions them, instead of letting users click into guaranteed errors. - Google Photos import (FR-905/ADR-045): export isGooglePhotosImportConfigured() and hide the "Import from Google Photos" button on Heirlooms when VITE_GOOGLE_OAUTH_CLIENT_ID is absent at build time. - Document signing: on a 503 from create-envelope (no signing provider credential configured), the directives UI swaps the "Sign Document" CTA for a calm "E-Signing Coming Soon" badge and tells the owner to export + wet-ink sign in the meantime — provider.go wiring untouched. - Stripe Billing Portal: detect the "Customer Portal not configured in Dashboard" Stripe error and return a clear 503 "contact support" message instead of a bare 500. - Document the three owner provisioning steps inline in web/.env.production. Refs: FR-905, ADR-045, ADR-047 Bucket: web-integration-config-guards (P1) Changelog: Config-gap integrations (Google Photos import, e-signing, Stripe portal) now degrade gracefully and document their owner provisioning steps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e 27/firewall) Scoped per-route color fidelity cleanup (no globals.css or ui/ primitive changes; no Sidebar/AdminHeader). Replaces forbidden Tailwind slate text with the canonical Royal ink tokens, swaps two grey primary CTAs to Royal Blue, and removes the forbidden emerald (Sirsi brand) hue from the Events/RSVP feature. - text-slate-900/800 -> text-[var(--royal)] (primary headings/body) - text-slate-700 -> text-[var(--royal)]/70; -600/-500 -> /60 (secondary) - text-slate-400 -> /40; -300 -> /30; input placeholders -> royal/30-40 (tertiary) - Add Asset (assets.tsx) + Add Credential (lockbox.lazy.tsx) primary CTAs: bg-slate-700 hover:bg-slate-800 -> bg-[var(--royal)] hover:bg-[var(--royal-blue)] - Events RSVP "Attending" + RSVPDialog success: emerald-* -> green-* success token - Neutral slate surfaces/borders (bg-slate-50, border-slate-100, media bg-slate-900) intentionally preserved; emerald now absent from all in-scope files. Refs: bucket design-route-color-fidelity (P2); findings: pervasive slate text (Rule 27), grey primary CTAs (brand consistency), emerald in Events/RSVP (brand firewall). Changelog: Estate routes (dashboard/assets/vault/settings/beneficiaries/lockbox/events) + ShepherdCompanion/RSVPDialog now render Royal ink + Royal Blue CTAs; emerald removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e switcher
Resolves the auth-mfa-identity-gate P0 bucket — invited fiduciaries were
hard-locked out of estates they were invited to, and principal MFA enforcement
was dead code. All findings ship together because they share the same gate.
- persona.ts: grant `settings` to executor/trustee/heir/legal/cpa so the
ONLY MFAEnrollment surface (the settings route) is reachable. The settings
page already gates estate-pref sections behind isPrincipalOrAdmin, so
fiduciaries see only their own profile + MFA cards — no privilege widening.
- IdentityGate.tsx: replace three dead /dashboard/settings <a href> CTAs
(404 — that route does not exist) with TanStack <Link> to
/estates/$estateId/settings, across the principal block screen, the
fiduciary verification wizard, and the MFA grace banner. Fix off-brand
text-slate-900 → text-royal in the banner.
- auth.tsx: increment users/{uid}.loginCount (+ lastLoginAt) on each explicit
credential sign-in, so IdentityGate's principal grace period (24h AND
loginCount>=3, ADR-035) can actually expire. Non-blocking best-effort write.
- Sidebar.tsx: replace the dead Active-Estate button with a real EstateSwitcher
dropdown (useUserEstates) listing every estate the user belongs to, each
navigating to its dashboard, plus a "Manage estates" link to the previously
orphaned /estates/$estateId/estates registry. Wired into both desktop and
mobile sidebars.
- estates.$estateId.estates.tsx: drive each card's status dot/label + subtitle
from the real estate.status (active/death_reported/executor_confirmed/
in_settlement/closed) instead of a hardcoded green "Active"; label the role
via personaLabel() instead of the literal "Owner" fallback.
Refs: auth-mfa-identity-gate (P0); ADR-035 (tiered identity verification)
Changelog: Fixed — invited fiduciaries can now reach MFA enrollment; principal
MFA grace period now expires; estate registry/switcher reachable from nav;
estate cards show real status.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…IL 3606 map
Three RC-blocker fixes in the probate forms surface (bucket
api-probate-forms-valuation, P1):
1. Rule-9 valuation (forms.go): computeTotalAssets stamped
"N assets recorded" into the inventory totalAssetValue and the
small-estate totalPersonalProperty fields — text injected into legal
prefill that gates small-estate qualification. Replaced with a real
parser (parseCurrency/formatCurrency) that sums the recorded "$485,000"-
style values into a true dollar figure ("$609,500"). Small-estate now
uses a personal-property-only total (real property excluded per
755 ILCS 5/25-1). If ANY contributing value is unparseable the field is
left BLANK rather than asserting a fabricated amount — fail-closed Rule 9.
2. Dead stub removed (forms.go + main.go): deleted HandleGetFormData, an
unconsumed stub endpoint (/api/v1/probate/forms/data) that returned a
"use the /forms endpoint" message; no client calls it. Dropped its route
registration from main.go. Public API no longer ships a stub.
3. IL Small Estate Affidavit (Form 3606) map: added the single-claimant
rows of the variable-length schedules — first creditor (name/class/
amount), first heir/legatee (name/relationship/share), total personal-
property valuation, and spousal/child award — covering the common
one-creditor/one-heir estate. Documented the multi-row limitation
explicitly (needs a repeating-row renderer) and the PREVIEW/not-GA status
of the ConfidenceLow coordinates pending a proof-raster tuning pass.
Build: go build ./... + go test ./internal/... green (forms, maps, probate,
formsapi, estate all pass). main.go owned exclusively by this bucket;
googlephotos VAULT_STORAGE_BUCKET env wiring already correct, left as-is.
Refs: bucket api-probate-forms-valuation; findings
- probate small-estate "N assets recorded" valuation injection
- HandleGetFormData dead stub endpoint
- IL Form 3606 coordinate map documented partial
Changelog: Fixed — probate form prefill now stamps a real dollar valuation
(never a count) and leaves legal fields blank when unverifiable; removed the
unused /forms/data stub endpoint; expanded the IL Small Estate Affidavit map
with single-claimant schedule rows and an explicit multi-row/preview-status note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bucket: functions-sms-and-stale-tests (P0). Six findings closed.
SMS dead surface (FR-913): the sendSMS Cloud Function was a TODO stub that
set delivery.state='PENDING_PROVIDER' and never delivered a text, while the
invite UI advertised "Phone Number — for SMS notification" and reported the
invite as sent. No SMS provider exists in the repo, and CLAUDE.md §3 scopes
Functions to autoMatchInvitation + sendMail only. Rather than ship a stub or
add an unkeyed Twilio integration, the honest GA fix is to REMOVE the dead
channel:
- functions/index.js: delete the sendSMS function (+ sms_queue trigger).
- web/src/lib/invitations.ts: drop the phone param + sms_queue write.
- web/src/components/estate/InviteTeamMember.tsx: remove the phone input,
state, and handler wiring; add an email-only delivery note (Royal Neo-Deco).
Reintroduce delivery fn + phone field TOGETHER when a real provider (Sirsi
Sign shared SMS rail per ADR-047, or managed Twilio) is adopted.
Stale tests (no production defense weakened):
- sendMail (4): fixtures now set createdBy + the firebase-admin mock gains
auth().getUser, and a per-suite helper seeds estate_users/estate_invitations
so isRecipientAuthorized (the open-relay 'createdBy' defense) resolves true.
The ERROR assertion is kept current. Added TWO positive tests that LOCK IN
the defense: a mail with no createdBy and a mail to an unauthorized recipient
must both fail closed (status:'rejected', 'recipient not authorized').
- autoMatchInvitation (1): mockCollection now returns a chainable
.where().get() for estates/<id>/heirs|executors|soul-log so the role-flip +
sharedWith-backfill queries resolve instead of throwing.
- Removed the obsolete sendSMS describe block. Suite: 18/18 pass under npm ci.
CI: functions/package.json gains a self-bootstrapping `test:ci`
(npm ci && jest --ci). NOTE: the PR workflow still has no functions-test job
(workflow YAML is out of this bucket's file scope) — flagged separately.
Spec reconciliation (docs/REQUIREMENTS_SPECIFICATION.md, v2.1.0):
- FR-913 ✅→🔮 (email-only; SMS removed, no provider wired).
- FR-401/402/502 ⏳ Deferred(Tier 3)→✅ Implemented(IL): the shipped
api/internal/probate engine does death-cert fact extraction+confirm
(deathcert.go), executor activation+member-notify (executor.go), and IL/Cook
County court-form guidance (forms.go: CCP0315, CCP0312/CCP0313, 755 ILCS 5/
Art. XXV, IL Probate Act §14-1 — verified against forms.go, Rule 9). §6.1
acceptance + Appendix A matrix updated; MN/MD scope-noted as deferred.
Refs: functions-sms-and-stale-tests P0
Changelog: Removed non-functional SMS invitation stub (email-only delivery);
repaired 5 stale Cloud Functions tests against the open-relay + role-flip
hardening; reconciled FR-913 and FR-401/402/502 probate status in the
requirements spec.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
functions/index.test.js (18 tests) ran in NEITHER CI workflow while the functions were still deployed — silent-rot gap. Add a functions-test job (ubuntu-latest, Node 22, ./functions, npm ci -> npm test) to both firebase-hosting-pull-request.yml and firebase-hosting-merge.yml. Gate the PR build_and_preview aggregate on it (alongside api-check/web-test) and all three merge deploys (deploy-hosting/api/functions) so no release ships with red functions tests. Deferred residual of the completion wave (PR #8) — the functions-sms-and-stale-tests bucket globs excluded workflow YAML. Refs: CLAUDE.md (Rule 4 Pipeline), TEST_PLAN.md Changelog: Unreleased — CI runs Cloud Functions Jest suite Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Updates Vitest expectations for email-only invitation delivery and persona settings access so PR CI matches the documented production contract. Refs: AGENTS.md, docs/REQUIREMENTS_SPECIFICATION.md, docs/TEST_PLAN.md Changelog: Unreleased — PR test contract drift
|
Visit the preview URL for this PR (updated for commit 412f03b): https://finalwishes-prod--pr8-integration-completi-b7xns1fr.web.app (expires Sun, 21 Jun 2026 20:01:00 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 6c224b6590e2084ad0c46f0efd6a68ed08b63fa3 |
Updates Vitest expectations for email-only invitation delivery and persona settings access so PR CI matches the documented production contract. Refs: AGENTS.md, docs/REQUIREMENTS_SPECIFICATION.md, docs/TEST_PLAN.md Changelog: Unreleased — PR test contract drift
…timecapsule route DELIVERY_TYPES icon at timecapsule.lazy.tsx:1038 used the undefined var(--color-slate-400) for the unselected state, silently falling back to inherited color. Map it to var(--ink-muted) (#4A5C7A, royal-tinted secondary) to match the design-tokens-css brand mapping, so unselected delivery-trigger icons render the intended Royal Neo-Deco ink. Selected state unchanged (var(--gold)). Bucket: timecapsule-slate-var (P2)
added 7 commits
June 14, 2026 14:32
# Conflicts: # package-lock.json
…ed email
A legal directive/POA must be signed BY the estate principal whose estate
it governs — an executor/admin only INITIATES the ceremony. HandleCreateEnvelope
previously set signerEmail/signerName from the authenticated caller's token
claims. It now resolves the signer server-side from estates/{id}.principalId →
Firebase Auth (verified email + display name) and REJECTS with HTTP 403 if the
principal's email is not verified. The caller is recorded only as initiatedBy in
the server-only signing_envelopes audit record (createdBy retained for back-compat).
- WebhookHandler gains an authClient (userLookup seam over *firebaseAuth.Client);
NewWebhookHandler(fs, authClient) threaded from main.go (authClient hoisted to
outer scope so it's in scope at all 3 call sites; middleware behavior unchanged).
- Principal resolution fronted by a signerResolver interface: production
firebaseSignerResolver (Firestore + Firebase Auth) + a fake in tests, since the
concrete *firebaseAuth.Client cannot be constructed offline.
- New file api/internal/opensign/signer.go. provider.go untouched.
- Tests updated: provider-proxy tests inject a verified principal; added
signer=principal, unverified→403, missing-principalId→400, defensive-nil cases.
- CHANGELOG [Unreleased] Changed entry added.
go build / go vet / go test ./... all green.
Refs: ADR-047; claude-home signer=principal decision 2026-06-14
CLAUDE.md Rule 29 (traceable commit)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace all forbidden slate-* utilities in components/guards with on-brand Royal Neo-Deco classes (text-ink / text-ink-muted / text-royal, bg-[var(--neutral-faint)], border-[var(--neutral-border)]). Heading text maps to text-royal; primary body to text-ink; secondary/helper to text-ink-muted; faint fills/borders to royal-tinted neutral vars. Visual/ className only — no logic or behavior changes; all 208 tests green. Refs CLAUDE.md Rule 27 (§4 Royal Neo-Deco design law). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace all slate-* utilities in web/src/components/ui and web/src/components/skeletons with on-brand Royal Neo-Deco tokens (royal-tinted neutrals + ink/ink-muted), removing forbidden grey. - Borders slate-50/100/200/300 -> border-[var(--neutral-border)] / border-[var(--neutral-faint)] (hairline royal-tinted neutrals) - Faint fills bg-slate-50 -> bg-[var(--neutral-faint)] - Skeleton pulse bg-slate-200/60 -> bg-[color-mix(in_srgb,var(--neutral-border)_60%,transparent)] - select-with-other input value text text-slate-900 -> text-ink - README design-rule note updated to royal-tinted neutrals Visual/className only; logic + behavior unchanged (208 tests green, build green). Refs Rule 27 (Royal Neo-Deco design law). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace all slate-* utilities across the estates.$estateId.* route subtree with on-brand Royal Neo-Deco tokens per DESIGN LAW (Rule 27 / §4): - Headings (Cinzel / h1-h6 / *Title) text-slate-900/800 -> text-royal - Body/value text text-slate-900/800/700 -> text-ink (opacity preserved) - Secondary/helper text-slate-600/500/400/300 -> text-ink-muted - Light-on-dark text-slate-200 -> text-white/70 - Faint fills bg-slate-50/100 -> bg-neutral-faint (#F4F6FB) - Dividers/borders slate-100/200/300 -> --neutral-border (#DCE3EE) - Dark fills bg-slate-800/900 -> bg-royal-blue / bg-royal - Hover border highlights -> gold/40-50 (Neo-Deco gold halo) Visual/className-only sweep: zero logic changes, all 208 tests green, production build passes. No grey/slate or emerald anywhere. Refs: CLAUDE.md Rule 27 / §4 Royal Neo-Deco Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace all forbidden slate-* utilities with on-brand Royal Neo-Deco classes across the shell/onboarding routes (__root 404, dashboard redirect, accept-invite states, estates.create wizard). Slate text -> royal-tinted ink/ink-muted (headings -> royal), slate fills/borders -> neutral-faint/neutral-border var tokens, dark-surface text -> white/70, interactive hover borders -> gold halo. No emerald, no grey. Visual/className only -- zero logic or behavior change. Build green, 208/208 vitest pass. Refs CLAUDE.md Rule 27 / §4 Royal Neo-Deco. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace all slate-* utilities with on-brand Royal Neo-Deco classes across index, about, privacy, terms, and memorial routes (178 occurrences). Cinzel headings + stat displays -> text-royal; primary body -> text-ink; secondary/helper -> text-ink-muted; faint panel fills -> bg-[var(--neutral-faint)]; hairline borders -> border-[var(--neutral-border)]; dividers preserve opacity. No grey, no emerald. Visual/className-only; logic, behavior, and tests unchanged (208 vitest pass, build green). Refs CLAUDE.md Rule 27 / Sec.4 Royal Neo-Deco design law. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace all slate-* utilities with on-brand Royal Neo-Deco classes across the layout, landing, and ErrorBoundary components. Headings -> text-royal, body/values -> text-ink, secondary/labels -> text-ink-muted, dark chrome fills -> bg-royal/bg-royal-blue, faint fills/borders -> --neutral-faint/--neutral-border. No slate or emerald remains. Visual/ className-only; logic and behavior unchanged (208 tests green, build OK). Refs CLAUDE.md Rule 27 (§4 Royal Neo-Deco design law). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace all slate-* utilities in web/src/components/estate components with on-brand Royal Neo-Deco classes per the canonical mapping — royal-tinted ink for text (text-ink / text-ink-muted), text-royal for Cinzel headings, royal/gold accents for borders, and neutral-faint/neutral-border vars for fills and hairlines. Slate/grey is forbidden for FinalWishes per Design Law. Visual-only: no logic or behavior changed; build green, 208 tests pass. Refs Rule 27 (§4 Royal Neo-Deco). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er=principal Safety net for the signer=principal change (PR #10): legacy estates lacking principalId would 400 on signing. Backfills from the role==principal estate_users junction; DRY RUN by default, --apply to write. PROD CHECK 2026-06-14: 11/11 estates already have principalId — residual is EMPTY, no backfill needed; script kept as a guard for future/other environments. Refs: ADR-047, claude-home signer=principal decision 2026-06-14 Changelog: ops script only Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uild Wave 2 added eslint-plugin-jsx-a11y as ERRORS, which flips the whole pre-existing codebase (labels, media captions, autofocus, static-element handlers) to failing lint in one shot — and the merge-workflow lint job only runs on PRs to main, so it surfaced only at consolidation. Downgrade ALL jsx-a11y rules to 'warn' (signal kept; 0 errors, 127 warnings). A11y debt to be burned down in a dedicated sprint, promoting rules back to error file-by-file. No behavior change. Refs: CLAUDE.md Rule 27/a11y, Wave-2 hardening Changelog: tooling — jsx-a11y incremental adoption Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completion Wave — Integration of 13 audit-driven fix branches + CR-10 legal corpus
Integrates the fix wave produced from disjoint worktree buckets into one shippable branch. All branches merged cleanly into `integration/completion` off `main` (no conflicts — buckets were globbed disjoint).
Merged buckets (13)
Auth / identity
Web
API (Go)
Functions
Design (Royal Neo-Deco)
Docs
Legal corpus (CR-10)
Build / test status (all green on integration branch)
Design fidelity
Palette tokens correct (royal blue #1E3A5F/#2563EB + metallic gold #C8A951, `--primary: var(--royal)`). No emerald/slate primary-palette violations — sole `emerald` is a semantic status-LED dot; `slate` confined to legal/marketing body text.
Residuals
🤖 Generated with Claude Code