Skip to content

feat(signing): signer = estate principal + email-verified gate (claude-home decision)#10

Merged
SirsiMaster merged 2 commits into
integration/completionfrom
feat/signer-principal
Jun 14, 2026
Merged

feat(signing): signer = estate principal + email-verified gate (claude-home decision)#10
SirsiMaster merged 2 commits into
integration/completionfrom
feat/signer-principal

Conversation

@SirsiMaster

Copy link
Copy Markdown
Owner

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

  • Signer identity resolved server-side from estates/{id}.principalId → Firebase Auth (GetUser): signerEmail/signerName = the principal's verified email/displayName. Caller's token email + any body signerEmail are now ignored for signer identity.
  • Rejects if the principal's email is not verified (403). Other guards: missing principalId → 400, no principal email → 400, auth client nil → 503, Firebase lookup fail → 502.
  • Caller recorded as initiatedBy in the signing_envelopes audit record (an executor/admin may initiate on the principal's behalf; the principal signs). createdBy kept for back-compat.
  • provider.go signing-secret wiring untouched.

Design

  • New signerResolver interface (prod firebaseSignerResolver) + narrow userLookup seam over *firebaseAuth.Client so the resolution is unit-testable offline. NewWebhookHandler(fs, authClient); authClient hoisted to outer scope in main.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)

  • Legacy estates must have principalId populated, 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.

…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>
@github-actions

github-actions Bot commented Jun 14, 2026

Copy link
Copy Markdown

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>
@SirsiMaster SirsiMaster merged commit 1184718 into integration/completion Jun 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant