Skip to content

feat(trust): canonical institutional trust primitives (Lane B)#341

Open
ctol3r wants to merge 1 commit into
mainfrom
wave/trust-primitives-lane-b
Open

feat(trust): canonical institutional trust primitives (Lane B)#341
ctol3r wants to merge 1 commit into
mainfrom
wave/trust-primitives-lane-b

Conversation

@ctol3r
Copy link
Copy Markdown
Owner

@ctol3r ctol3r commented May 12, 2026

Summary

Lane B of the trust-convergence migration. Creates the shared foundation of canonical primitives under apps/web/components/trust/ that Lane C/D will adopt. No surface adoption in this PR — pure additive primitive layer.

Primitives created

Component Role
CheckedAtStamp Visible ISO timestamp; never aria-only / tooltip-only
RunIdentity Visible run_id (middle-truncated; full id on data-run-id)
TierBadge T1–T4 + DEGRADED / UNAVAILABLE / UNSIGNED / ISSUER_UNREACHABLE
TrustStateBand GREEN / YELLOW / RED / UNKNOWN — decision-readiness band
OwnershipState CLAIMED / PENDING / UNCLAIMED / DISPUTED — fills the audit-identified UI gap
DegradedStateBanner A/B/C/D/E taxonomy; D renders as SUCCESS, C as critical w/ role=alert
IssuerAttribution signer / DID / key id / receipt / continuity
ReplayLineage Evidence-continuity chain (not analytics); gaps explicit
TrustHeader Composite enforcing the mandatory render order

Mandatory render order (enforced by <TrustHeader>)

OBJECT → OWNERSHIP → CHECKED_AT → CHANNEL → REPLAY → RUN_ID

Test asserts the six sections appear in this order in the DOM. Variant (PREVIEW / SNAPSHOT / SIGNED) changes visual register but never the order.

Degraded-state taxonomy (canonical)

Code Meaning Tone
A Source unreachable warning
B Anonymous restriction info
C Infrastructure outage critical (role=alert)
D No adverse findings success (mandatory per brief)
E Issuer unavailable warning

D-is-success is pinned with a test that explicitly rejects data-degraded-tone="warning" and "critical" on code D, ensuring "absence of evidence" is not conflated with "presence of risk".

What this PR does NOT do

  • Adopt primitives in any existing surface (passport/verifier/replay/receipt/trust pages) — that is Lane C/D.
  • Redesign routes, auth, architecture.
  • Touch the frozen @vitalcv/trust-state package.

Truth rules

  • Banned-strings scan across the rendered output of every primitive: CLEAN.
  • "Verified" bare-label scan: CLEAN (no primitive renders bare Verified as a status label).

Validation

  • Targeted vitest: 51/51 passing (components/trust/__tests__/trust-primitives.test.tsx)
  • Build: pnpm turbo run build --filter @vitalcv/web13/13 tasks, 37s
  • Diff scope: apps/web/components/trust/ only (11 new files, no existing files modified)

Creates the shared foundation that Lane C/D will adopt across passport,
verifier, replay, receipt, and trust surfaces. No surface adoption in
this PR — that is Lane C's scope.

New module: apps/web/components/trust/

  CheckedAtStamp     — visible ISO timestamp; never aria-only / tooltip-only
  RunIdentity        — visible run_id; truncates middle, full id on data-run-id
  TierBadge          — T1/T2/T3/T4 + DEGRADED / UNAVAILABLE / UNSIGNED /
                       ISSUER_UNREACHABLE special states
  TrustStateBand     — GREEN / YELLOW / RED / UNKNOWN; decision-readiness
                       band (distinct from TierBadge claim-confidence)
  OwnershipState     — CLAIMED / PENDING / UNCLAIMED / DISPUTED chip;
                       fills the audit-identified UI gap where
                       /api/ownership endpoints exist with no surface
  DegradedStateBanner— A/B/C/D/E taxonomy; D ("no adverse findings")
                       renders as SUCCESS, C ("infrastructure outage")
                       as critical with role=alert
  IssuerAttribution  — signer / DID / key id / receipt / continuity
                       (ACTIVE / STALE / BROKEN); surfaces backend
                       identity data that previously never reached the UI
  ReplayLineage      — evidence continuity chain (not analytics):
                       lineage key, prior checks, gaps, replay count
  TrustHeader        — composite enforcing the mandatory render order:
                       OBJECT → OWNERSHIP → CHECKED_AT → CHANNEL → REPLAY
                       → RUN_ID; PREVIEW / SNAPSHOT / SIGNED variants

Tests (51 cases, vitest):
  - A/B/C/D/E taxonomy completeness; D-is-success / C-is-critical invariants
  - Replay continuity detection (zero checks, no gaps, with gaps)
  - TrustHeader section count + canonical order assertion
  - CheckedAtStamp + RunIdentity visibility (rendered text, not just data-*)
  - OwnershipState + TierBadge full-vocabulary coverage
  - Doctrine: banned-strings scan across all primitives
  - "Verified" bare-label scan

Validation: 51/51 vitest passing; pnpm turbo run build --filter @vitalcv/web
green (13/13 tasks).
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vcv-web Ready Ready Preview, Comment May 12, 2026 0:48am
vitalcv Ready Ready Preview, Comment May 12, 2026 0:48am

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d491e4e3fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +62 to +63
const sortedChecks = [...priorChecks].sort((a, b) => a.checkedAt.localeCompare(b.checkedAt));
const sortedGaps = [...gaps].sort((a, b) => a.from.localeCompare(b.from));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sort replay timestamps by epoch time

checkedAt/from are documented as ISO timestamps, but these arrays are currently sorted with localeCompare, which is not chronological when offsets differ (e.g. 2026-01-01T00:30:00+01:00 sorts after 2026-01-01T00:00:00Z even though it is earlier). In those inputs, the lineage chain and gap list render in the wrong order, which can misstate evidence continuity. Parse to numeric time (new Date(...).getTime()) for ordering instead of lexical string compare.

Useful? React with 👍 / 👎.

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.
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.

2 participants