feat(signing): signer = estate principal + email-verified gate (claude-home decision)#10
Merged
Merged
Conversation
…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>
|
Visit the preview URL for this PR (updated for commit 2fe5f9c): https://finalwishes-prod--pr10-feat-signer-principa-rynxm2ux.web.app (expires Sun, 21 Jun 2026 19:05:24 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 6c224b6590e2084ad0c46f0efd6a68ed08b63fa3 |
…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>
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.
Implements claude-home's owner-blessed decision (2026-06-14): the OpenSign signer for a legal directive/POA is the estate PRINCIPAL, not the caller.
Change
estates/{id}.principalId→ Firebase Auth (GetUser):signerEmail/signerName= the principal's verified email/displayName. Caller's token email + any bodysignerEmailare now ignored for signer identity.principalId→ 400, no principal email → 400, auth client nil → 503, Firebase lookup fail → 502.initiatedByin thesigning_envelopesaudit record (an executor/admin may initiate on the principal's behalf; the principal signs).createdBykept for back-compat.provider.gosigning-secret wiring untouched.Design
signerResolverinterface (prodfirebaseSignerResolver) + narrowuserLookupseam over*firebaseAuth.Clientso the resolution is unit-testable offline.NewWebhookHandler(fs, authClient);authClienthoisted to outer scope inmain.go, all 3 call sites updated; middleware unchanged.Tests / verify
go build ./...✓,go vet ./...✓,go test ./...✓ (0 failures). New tests: verified-principal → principal email is signer (caller token + body ignored); unverified → 403; missing principalId → 400; defensive-nil branch.Residual (flagged for review)
principalIdpopulated, or signing now 400s. Recommend confirming backfill before this ships to prod.Refs: ADR-047, claude-home signer=principal decision 2026-06-14
Stacked on PR #8 (integration/completion); independent of PR #9.