feat(trust): replay-integrity panel — script logic on the operator surface#352
feat(trust): replay-integrity panel — script logic on the operator surface#352ctol3r wants to merge 1 commit into
Conversation
…rface Stacked on #350 (recent-NPI replay-memory). Promotes the CLI tooling shipped in #351 (scripts/replay/*) into a visible operator surface on the passport page. A reviewer now sees replay continuity health without terminal access. Three rows render in the institutional register (monochrome, mono identifiers, left-aligned, no shadows, no decorative iconography): 1. coherence: does the runId the backend declared on the passport match the runId locally recomputed from the same evidence? States: computing / coherent (emerald) / no-declaration / run-drift (amber) / lineage-drift (rose, role=alert). 2. gaps: chronology gap summary over the recent-NPIs history, using the same DEFAULT_MAX_GAP_HOURS=720 the find-replay-gaps.ts script uses. Reports "continuous (N snapshots)" or "N gap(s); largest XXh" with explicit from/to timestamps. 3. identity: the resolved lineageKey + runId rendered as visible mono text, always — never aria-only, never tooltip-only. New files: apps/web/lib/replay/clientReplayIdentity.ts Browser-compatible mirror of the v1 replay-identity algorithm. Uses crypto.subtle.digest (async) instead of node:crypto. Same inputs, same normalization, same scheme prefix as the backend canonical (#343). Pinned by the parity test below. apps/web/lib/replay/integrityEvaluation.ts Pure server-side helpers: - evaluateReplayCoherence(input) → CoherenceFinding - summarizeReplayGaps(history, thresholdHours) → GapSummary Same gap-detection semantics as scripts/replay/find-replay-gaps.ts. apps/web/components/trust/ReplayIntegrityPanel.tsx Client component composing the three rows. Reuses Lane B <CheckedAtStamp> + <RunIdentity> — no new visual vocabulary. apps/web/__tests__/replay-identity-parity.test.ts (13 cases) Pins the web mirror against the v1 algorithm shape contract: prefixes, lengths, normalization invariance, sensitivity to every input field, deterministic 25-iteration loop, degraded distinguishability, error on empty entity id. Direct cross-implementation parity (web ↔ backend module) is deferred to a post-merge integration test once the parallel stacks converge — the backend module ships in #343, which this branch doesn't have in its history. apps/web/__tests__/replay-integrity-panel.test.tsx (12 cases) evaluateReplayCoherence transitions through all four states; summarizeReplayGaps respects threshold + sorting + largest-gap selection; panel renders the expected data-* anchors for each initial state; doctrine banned-strings scan CLEAN. Adoption: apps/web/app/passport/[id]/PassportEntityClient.tsx <ReplayIntegrityPanel> renders below the RecentNpis section, consumes passport.replay (Wave 10 / #343) when present, falls back gracefully to "no-declaration" state otherwise. Validation: 25/25 vitest passing; pnpm turbo run build --filter @vitalcv/web → 13/13 tasks; truth-strings CLEAN. Out of scope (explicit follow-ups): - /ops/replay diagnostics page — would consume the same helpers but expose them at an operator-only route - Direct web↔backend parity test — needs both stacks merged - /api/replay/integrity/[id] external endpoint — wraps the helpers for third-party verifier polling
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 954ffc33cb
ℹ️ 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".
| replay integrity | ||
| </span> | ||
| <span className="font-mono text-[10px] uppercase tracking-wider opacity-60"> | ||
| scheme · {declared?.schemeVersion ?? coherence?.state === 'coherent' ? 'v1' : 'pending'} |
There was a problem hiding this comment.
Preserve declared replay scheme in header
The scheme badge expression currently parses as (declared?.schemeVersion ?? coherence?.state === 'coherent') ? 'v1' : 'pending', so any non-null declared version is treated as truthy and rendered as v1. This means a future backend value like "v2" would be mislabeled as v1, which breaks the intended survivability/version marker for operators. Parenthesize the fallback so the actual declared string is displayed when present.
Useful? React with 👍 / 👎.
| // via passport.replay when present; otherwise the panel | ||
| // computes from entityId + lastCheckedAt alone, which | ||
| // still gives a coherent lineage check. | ||
| artifactChecksums: undefined, |
There was a problem hiding this comment.
Pass artifact checksums into coherence evaluation
This panel recomputes runId to compare with the backend declaration, but artifactChecksums is always forced to undefined here. In any environment where the backend runId includes artifact-derived inputs, the client recomputation will systematically omit part of the evidence and produce false run-drift states for otherwise matching snapshots. Wire the replay/artifact inputs through instead of hardcoding undefined.
Useful? React with 👍 / 👎.
Summary
Stacked on #350 (recent-NPI memory). Promotes the CLI tooling shipped in #351 into a visible operator surface on
/passport/[id]. A reviewer now sees replay continuity health without terminal access — three rows in the institutional register.Panel rows
role="status") / lineage-drift (rose,role="alert")lineage · lin_v1_…+<RunIdentity>— visible mono text, never aria-onlycoherenceanswers: "does the runId the backend declared match the runId I can recompute from the same evidence?" That detects either upstream tampering / cache contamination OR algorithm drift between the web mirror and the backend canonical.gapsuses the sameDEFAULT_MAX_GAP_HOURS=720threshold asscripts/replay/find-replay-gaps.ts. The history input is the recent-NPIs list from #350, filtered to the current subject — so the gap rendering is per-subject continuity over what this client has actually inspected.Files
apps/web/lib/replay/clientReplayIdentity.tscrypto.subtle.digest)apps/web/lib/replay/integrityEvaluation.tsevaluateReplayCoherence+summarizeReplayGapspure helpersapps/web/components/trust/ReplayIntegrityPanel.tsx<CheckedAtStamp>+<RunIdentity>; no new visual vocabularyapps/web/components/trust/index.tsapps/web/app/passport/[id]/PassportEntityClient.tsxapps/web/__tests__/replay-identity-parity.test.tsapps/web/__tests__/replay-integrity-panel.test.tsxAesthetic compliance
Per the institutional brief: monochrome with coloured borders ONLY on coherence states; no charts; no animations; no decorative iconography; mono identifiers; left-aligned. The panel reads as an institutional log row, not a card.
Truth rules
VerifiedlabelValidation
pnpm turbo run build --filter @vitalcv/web→ 13/13 tasksCross-implementation parity
The web mirror at
clientReplayIdentity.tsproduces v1-prefixed ids usingcrypto.subtle.digest. The backend canonical (#343, parallel stack) usesnode:cryptoover the SAME algorithm. Until both stacks merge, the parity test pins the shape + invariants of the web mirror; a follow-up integration test will lock direct web↔backend cross-import once the stacks converge.Out of scope (explicit follow-ups)
/ops/replay//ops/lineage//ops/survivabilitydiagnostic pages (operator-only routes)/api/replay/integrity/[id]external endpoint