feat(dashboard): zod trust boundary — schema-validated audit-event parsing (PR3/3)#37
Merged
Merged
Conversation
…omponent tests - Add @testing-library/react@^16, @testing-library/jest-dom@^6, jsdom@^25 as devDependencies - Create vitest.config.ts with jsdom environment and test-setup.ts for jest-dom matchers - Update tsconfig.json to include vitest/globals and node types (skipLibCheck for jest-dom compatibility) - All 48 existing tests pass under jsdom environment @testing-library/react@^16 chosen over @^15 as it supports React 18+ with better alignment. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on tests for B and F Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… test for D Replace inline safeParseJSON<any> with parseTypedPayload(finalDecisionPayloadSchema) at the FINAL_DECISION trust boundary. Tagged-result switch handles all three statuses explicitly — closes finding D (chat-approval shape no longer renders NaN%). Extract parse logic to parseRunDecision helper for testability. Add 5 regression tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aSchema + regression for E
Extract parseEventList helper (pure, testable) and wire useMemo([events]) in EventTimeline. Drops dual-case guard_type||guardType reads; reads canonical camelCase from schema-normalized parsed.value. Computes driftCount for future DriftBanner integration. Adds 5-test memoization suite. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 tasks
kaicianflone
added a commit
that referenced
this pull request
May 6, 2026
Slop-scan rerun on apps/dashboard after the 3-PR zod trust-boundary refactor flagged two strong findings that survived. Both are 1-line classes that the manual subagent audit also caught: 1. defensive.error-swallowing at WorkflowsDashboard.tsx:346 handleLoadTemplate caught and only console.error'd. User-clicked template loads now surface the failure via setApiError, mirroring executeWorkflow's pattern at the same file. 2. structure.pass-through-wrappers at AgentsPanel.tsx:57 parseMetadata was a literal forward to parseAgentMetadata introduced in PR #37 (T05) for testability. Drop the wrapper and call parseAgentMetadata at the 3 sites directly. Required a type guard at line 498 because parseAgentMetadata returns the typed schema output (where passthrough fields are 'unknown') instead of Record<string, any>. Verification: - pnpm --filter @consensus-tools/dashboard test 79/79 pass - pnpm --filter @consensus-tools/dashboard typecheck clean - pnpm --filter @consensus-tools/dashboard build passes - npx slop-scan@latest scan apps/dashboard --lint 3 findings (was 5) Both 'strong' findings closed; remaining 3 are 'medium' directory fan-out hotspots that are React app shape, not slop. Co-authored-by: Claude Opus 4.7 <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.
Summary
PR3 of 3 from
~/.gstack/projects/consensus-tools-toolkit/ceo-plans/dashboard-zod-trust-boundary.md. Closes round-3 findings B, D, E, F (and implicitly C, J).Wires the dashboard render paths through PR2's Tier-0
finalDecisionPayloadSchema+parseTypedPayloadhelper. Replaces ad-hoc shape checks (typeof === 'object' && !Array.isArray(),!e.payload_json || e.payload_json === '') scattered across components with a single validated boundary. Same parser called from every consumer eliminates the page-divergence bug (finding F).Architecture
Implementation (orchestrated via /gstack-orchestrate)
vitest.config.ts,test-setup.ts, depscomponents/dashboard/DriftBanner.tsx(new)role="alert" aria-live="polite"; count = 0 → null. No PII / payload data in banner text. Created + tested but not yet mounted on any page — see Out of Scope below.pages/BoardDetailPage.tsx+ newbuildRunDecisions.tshelpersafeParseJSONwithparseTypedPayload. On invalid: do NOT overwrite map[run_id]. Read canonicaldecision.riskScore/decision.guardType. Closes findings B (empty-input misclassification) and F (BoardDetail/RunDetail divergence). Track driftCount.pages/RunDetailPage.tsx+ newparseRunDecision.tshelper'invalid'non-empty input renders explicit malformed-event placeholder. Risk badge conditionally rendered (handles chat-approval-limited shape). Closes finding D (NaN% from missing risk_score).components/agents/AgentsPanel.tsx+ newparseAgentMetadata.tshelperp.metadatawithparticipantMetadataSchema-backed parse. Schema's.safeParse()always returns fresh object → mutations don't affect source. Closes finding E (live-reference mutation hazard) + finding C (Date/Map/Set/class instances rejected).components/workflow/EventTimeline.tsx+ newparseEventList.tshelpereventsarray change, not per render row. Drop dual-casepayload.guard_type || payload.guardTypereads (schema's.transform()normalizes). Tracks driftCount.Round-3 findings closed
0/false/null/whitespace; can overwrite valid decisions with{}parseTypedPayloadreturns'invalid'for non-empty malformed; map write only on'ok'parseMetadataaccepts Date/Map/Set/class instancesparticipantMetadataSchemarejects non-plain objectsriskScore !== undefinedguardparseMetadatashort-circuits to livep.metadatareference.find()first match — same run shows diverging FINAL_DECISION outcomesparseTypedPayload(..., finalDecisionPayloadSchema), same canonical outputanynarrowing in parseMetadataMandatory regression tests (IRON RULE per /plan-eng-review)
All 4 mandatory regressions are tested with failing-before-PR / passing-after-PR semantics:
BoardDetailPage.test.tsx—payload_json='null'literal does not overwrite valid map[run_id];payload_json=''does not write; type-mismatch does not overwriteRunDetailPage.test.tsx— chat-approval emit shape (no risk fields) renders without NaN%; malformed riskScore renders explicit placeholderAgentsPanel.test.tsx— mutating returned metadata does not mutate sourcep.metadataBoardDetailPage.test.tsx— canonical camelCase event matches directparseTypedPayloadoutput (same canonical view across pages)Test counts
safeJson.test.tsapi.test.tsDriftBanner.test.tsxBoardDetailPage.test.tsxRunDetailPage.test.tsxAgentsPanel.test.tsxEventTimeline.test.tsxVerification
pnpm --filter @consensus-tools/dashboard test— 79/79 passpnpm --filter @consensus-tools/dashboard typecheck— cleanpnpm --filter @consensus-tools/dashboard build— passes (chunk-size warning is pre-existing, not introduced)pnpm test— 47/47 turbo tasks pass across the whole monorepopnpm dep-check— clean (233 modules, 432 deps — gained the new helper files + dashboard deps)Out of scope for this PR
driftCountvia state but does not render the<DriftBanner>JSX. Per /plan-eng-review report: "DriftBanner component (PR3) would benefit from/plan-design-reviewbefore implementation." Mounting decisions (placement, exact copy, color, dismissibility) need a design pass first. Tracked as P2 follow-up: a small PR4 can run/plan-design-reviewon the banner UX and then mount it.JSON.parseof workflow.definition — different concern (workflow data, not audit event); P3 follow-up.Test plan
pnpm test) passes — 47/47 turbo tasks/plan-design-reviewthe DriftBanner and mount it🤖 Generated with Claude Code