[pitboss] phase 07: M6 — Evidence consumers: formatters, ranking, UI

This commit is contained in:
pitboss 2026-05-12 13:26:52 -04:00
parent 6f8a645077
commit bfdfcb9d1a
18 changed files with 3208 additions and 46 deletions

View file

@ -8,6 +8,7 @@ import { escapeHtml, highlightSyntax } from '../utils/syntaxHighlight';
import { parseNoteText } from '../utils/parseNote';
import { findingToMarkdown } from '../utils/findingMarkdown';
import { CopyMarkdownButton } from '../components/CopyMarkdownButton';
import { VerdictBadge } from '../components/VerdictBadge';
import { Dropdown, DropdownItem } from '../components/ui/Dropdown';
import { CodeViewerModal } from '../modals/CodeViewerModal';
import type {
@ -16,6 +17,7 @@ import type {
FlowStep,
SpanEvidence,
RelatedFindingView,
VerifyResult,
} from '../api/types';
// ── Helpers ─────────────────────────────────────────────────────────────────
@ -701,6 +703,97 @@ function HowToFix({ finding }: { finding: FindingView }) {
);
}
// ── Dynamic Verification Panel ──────────────────────────────────────────────
function DynamicVerdictSection({ verdict }: { verdict: VerifyResult }) {
const [copied, setCopied] = useState(false);
const reproPath = `~/.cache/nyx/dynamic/repro/${verdict.finding_id}/`;
const reproCmd = './reproduce.sh';
const copyCmd = () => {
navigator.clipboard.writeText(reproCmd).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
};
return (
<div className="dynamic-verdict-section">
<div className="dynamic-verdict-badge-row">
<VerdictBadge verdict={verdict} />
{verdict.toolchain_match && (
<span
className="dynamic-toolchain-match"
title={`Toolchain match: ${verdict.toolchain_match}`}
>
{verdict.toolchain_match === 'exact' ? 'exact toolchain' : 'approximate toolchain'}
</span>
)}
</div>
{verdict.status === 'Confirmed' && (
<div className="repro-panel" data-testid="repro-panel">
<div className="repro-path-row">
<span className="repro-label">Repro artifact:</span>
<code className="repro-path">{reproPath}</code>
</div>
<div className="repro-cmd-row">
<code className="repro-cmd">{reproCmd}</code>
<button
type="button"
className="btn btn-sm repro-copy-btn"
onClick={copyCmd}
>
{copied ? 'Copied!' : 'Copy'}
</button>
</div>
</div>
)}
{(verdict.reason || verdict.inconclusive_reason || verdict.detail) && (
<div className="dynamic-verdict-detail">
{verdict.reason && (
<div>
<strong>Reason:</strong> {verdict.reason}
</div>
)}
{verdict.inconclusive_reason && (
<div>
<strong>Inconclusive reason:</strong> {verdict.inconclusive_reason}
</div>
)}
{verdict.detail && (
<div className="dynamic-verdict-detail-text">{verdict.detail}</div>
)}
</div>
)}
{verdict.attempts.length > 0 && (
<div className="dynamic-attempts">
<strong>Payload attempts:</strong>
<ul className="dynamic-attempt-list">
{verdict.attempts.map((a, i) => (
<li key={i} className={`attempt-row ${a.triggered ? 'triggered' : ''}`}>
<code>{a.payload_label}</code>
<span className="attempt-outcome">
{a.triggered
? 'triggered'
: a.timed_out
? 'timeout'
: 'no hit'}
</span>
{a.exit_code != null && (
<span className="attempt-exit-code">exit {a.exit_code}</span>
)}
</li>
))}
</ul>
</div>
)}
</div>
);
}
// ── Status Control ──────────────────────────────────────────────────────────
function StatusControl({
@ -1017,6 +1110,13 @@ export function FindingDetailPage() {
</CollapsibleSection>
)}
{/* Dynamic Verification */}
{evidence?.dynamic_verdict && (
<CollapsibleSection title="Dynamic Verification">
<DynamicVerdictSection verdict={evidence.dynamic_verdict} />
</CollapsibleSection>
)}
{/* Code Preview */}
{hasCode && (
<CollapsibleSection title="Code Preview" defaultOpen={false}>

View file

@ -17,6 +17,7 @@ import { Dropdown, DropdownItem } from '../components/ui/Dropdown';
import { LoadingState } from '../components/ui/LoadingState';
import { ErrorState } from '../components/ui/ErrorState';
import { CopyMarkdownButton } from '../components/CopyMarkdownButton';
import { VerdictBadge } from '../components/VerdictBadge';
import { truncPath } from '../utils/truncPath';
import { findingsToMarkdown } from '../utils/findingMarkdown';
import { ApiError } from '../api/client';
@ -711,6 +712,7 @@ export function FindingsPage() {
currentDir={state.sort_dir}
onSort={handleSort}
/>
<th>Verified</th>
</tr>
</thead>
<tbody>
@ -760,6 +762,12 @@ export function FindingsPage() {
{formatTriageState(f.triage_state || f.status)}
</span>
</td>
<td>
<VerdictBadge
verdict={f.evidence?.dynamic_verdict}
compact
/>
</td>
</tr>
))}
</tbody>