feat(trust): adopt canonical primitives across 5 trust surfaces (Wave 2)#342
Open
ctol3r wants to merge 1 commit into
Open
feat(trust): adopt canonical primitives across 5 trust surfaces (Wave 2)#342ctol3r wants to merge 1 commit into
ctol3r wants to merge 1 commit into
Conversation
Stacked on wave/trust-primitives-lane-b (PR #341). Surgical adoption only — replaces fragmented inline trust rendering with the canonical primitives from apps/web/components/trust/. No layout redesign, no new data fetching, no auth or backend changes. Surfaces adopted (5/6 of the brief): /passport/[id] PassportEntityClient.tsx + TrustHeader at top of the surface; institutional reading order (OBJECT → OWNERSHIP → CHECKED_AT → CHANNEL → REPLAY → RUN_ID) now precedes the existing PassportWallet body. PassportWallet (identity row) + CheckedAtStamp replaces inline `new Date(...).toLocaleDateString()` so the timestamp is rendered consistently across surfaces. LanePanel (proof inspection pane) + CheckedAtStamp on the "Checked at" detail row + the receipt block + RunIdentity for the visible receipt id chip ReadinessSurface (holder readiness) + TrustHeader variant=PREVIEW; matches the audit verdict that this surface is an anonymous/pre-claim exploration view. ConsoleWrapper (employer review) + TrustHeader variant=SNAPSHOT above the EmployerDecisionConsole. Deferred (not in this PR): ReviewClient.tsx — 2093-line surface with multiple inline timestamp sites; surgical adoption requires layout context this PR does not carry. Tracked as follow-up. Validation: pnpm turbo run build --filter @vitalcv/web → 13/13 tasks green No banned strings introduced; no API contract changes.
ctol3r
pushed a commit
that referenced
this pull request
May 12, 2026
Adds the cryptographically-signed receipt surface that an external
verifier needs to validate a clinician passport snapshot end-to-end:
1. fetch the receipt at /api/receipt/<npi>
2. fetch the JWKS at /.well-known/jwks.json (from Wave 9)
3. validate the JWT signature against the JWKS
The new route signs an ES256 JWT using the same getOrInitKeypair()
that produces the public key published at /.well-known/jwks.json
(Wave 9), so kid + key never diverge between issuance and
discoverability.
Receipt shape (ES256 JWT):
header { alg: 'ES256', kid: <active-kid>, typ: 'vc+jwt' }
payload { iss: <did:web issuer>, sub: 'npi:<npi>',
jti: 'receipt:<runId>' (deterministic per snapshot),
iat: <lastCheckedAt seconds>, nbf, exp = iat + 90d,
vc: W3C VC 2.0 credential block (VitalCVTrustReceipt),
vcv: VitalCV claim block incl. replay identity }
Determinism contract (test-pinned):
- jti = 'receipt:<replay.runId>' so the same evidence snapshot
yields the same receipt jti; the JWT signature is non-
deterministic per ES256 spec but the payload is byte-identical.
- iat is pinned to lastCheckedAt seconds so two receipts for the
same snapshot share iat. A verifier joining receipts by
(lineageKey, runId) gets unambiguous snapshot identity.
Verifier readability:
- vcv.jwksUri and vcv.didDocumentUri point at the well-known
surfaces #349 published, so a receipt is self-describing.
- X-Receipt-Kid + X-Receipt-Issuer response headers let a verifier
short-circuit JWKS resolution if it already has the key cached.
- ?format=download adds Content-Disposition: attachment so a
verifier engineer can save the JWT for offline validation.
Tests (10 vitest cases, all passing):
- 400 on non-10-digit NPI, 502 on malformed upstream, propagated
backend status on upstream non-ok
- 200 + application/jwt + ES256 + kid header + 'vc+jwt' typ
- W3C VC 2.0 + VitalCV claim block shape (credentialSubject,
replay.{lineageKey,runId,schemeVersion}, jwksUri, didDocumentUri)
- Determinism: same fetch → same jti, same iat
- End-to-end verifier flow: receipt JWT validates against the
JWKS the well-known route exposes (createLocalJWKSet + jwtVerify)
- ?format=download adds Content-Disposition: attachment
- Graceful no-replay fallback (jti from npi when replay absent)
Validation: 10/10 vitest passing; pnpm turbo run build --filter
@vitalcv/web → 13/13 tasks; truth-strings scan CLEAN.
Stacked on the rest of this branch (#349's well-known surfaces +
wellKnownIdentity helper). Brief 1 of this turn (replay-lineage
rendering) is structurally already shipped across #341 + #342 +
#343 + #345 — no rendering changes needed here.
This was referenced May 12, 2026
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.
Summary
Wave 2 of the trust-convergence migration. Stacked on #341 (Lane B primitives). Adopts the canonical primitives in 5 of the 6 brief-listed surfaces, replacing fragmented inline trust rendering. No layout redesign, no new data fetching, no auth/backend changes.
Surfaces adopted
/passport/[id]app/passport/[id]/PassportEntityClient.tsx<TrustHeader variant="SNAPSHOT">at the top of the surfacePassportWallet(identity row)components/passport/PassportWallet.tsx<CheckedAtStamp>replaces inlinetoLocaleDateStringLanePanel(proof)components/proof/LanePanel.tsx<CheckedAtStamp>for the Details + Receipt blocks;<RunIdentity>for the visible receipt id chipReadinessSurfaceapp/holder/readiness/ReadinessSurface.tsx<TrustHeader variant="PREVIEW">because this is the anonymous/pre-claim exploration viewConsoleWrapper(employer review)app/review/[entityId]/ConsoleWrapper.tsx<TrustHeader variant="SNAPSHOT">above EmployerDecisionConsoleDeferred (explicit follow-up)
ReviewClientcomponents/review/ReviewClient.tsxDoctrine notes
OBJECT → OWNERSHIP → CHECKED_AT → CHANNEL → REPLAY → RUN_IDis enforced by<TrustHeader>(the primitive itself, validated by tests in feat(trust): canonical institutional trust primitives (Lane B) #341).OwnershipStatedefaults toUNCLAIMEDbecause none of the 5 surfaces currently thread per-user ownership data — that plumbing is Lane D scope, not Wave 2.runIdfalls back toentityId/loopIdwhere no real run identifier is yet threaded. Surfaces gain a visible identifier chip today; tightening to actual run ids is Wave 10 (replay continuity & survivability).Truth rules
Validation
pnpm turbo run build --filter @vitalcv/web→ 13/13 tasks, green