[pitboss/grind] deferred session-0002 (20260521T201327Z-3848)

This commit is contained in:
pitboss 2026-05-21 15:48:29 -05:00
parent 159a779f31
commit d99361cff6
18 changed files with 499 additions and 144 deletions

View file

@ -707,16 +707,21 @@ function HowToFix({ finding }: { finding: FindingView }) {
export function DynamicVerdictSection({ verdict }: { verdict: VerifyResult }) {
const [copied, setCopied] = useState(false);
const attempts = verdict.attempts ?? [];
// The repro bundle is keyed by spec_hash (not finding_id) inside the Nyx
// cache. Rather than showing a path that may not match, surface the CLI
// command that locates and opens the bundle regardless of the hash.
const reproCmd = `nyx repro --finding ${verdict.finding_id}`;
const copyCmd = () => {
navigator.clipboard.writeText(reproCmd).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
if (!navigator.clipboard) return;
navigator.clipboard.writeText(reproCmd).then(
() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
},
() => {},
);
};
return (
@ -767,11 +772,11 @@ export function DynamicVerdictSection({ verdict }: { verdict: VerifyResult }) {
</div>
)}
{verdict.attempts.length > 0 && (
{attempts.length > 0 && (
<div className="dynamic-attempts">
<strong>Payload attempts:</strong>
<ul className="dynamic-attempt-list">
{verdict.attempts.map((a, i) => (
{attempts.map((a, i) => (
<li key={i} className={`attempt-row ${a.triggered ? 'triggered' : ''}`}>
<code>{a.payload_label}</code>
<span className="attempt-outcome">
@ -953,6 +958,7 @@ export function FindingDetailPage() {
const f = finding;
const evidence = f.evidence;
const dynamicVerdict = evidence?.dynamic_verdict ?? f.dynamic_verdict;
const isState = isStateFinding(f);
const hasWhySection =
f.message ||
@ -1110,9 +1116,9 @@ export function FindingDetailPage() {
)}
{/* Dynamic Verification */}
{evidence?.dynamic_verdict && (
{dynamicVerdict && (
<CollapsibleSection title="Dynamic Verification">
<DynamicVerdictSection verdict={evidence.dynamic_verdict} />
<DynamicVerdictSection verdict={dynamicVerdict} />
</CollapsibleSection>
)}

View file

@ -29,6 +29,11 @@ function formatTriageState(state: string): string {
return (state || 'open').replace(/_/g, ' ');
}
function formatVerificationStatus(status: string): string {
if (status === 'NotConfirmed') return 'Not confirmed';
return status || 'Unverified';
}
// ── Filter Bar ──────────────────────────────────────────────────────────────
interface FilterSelectProps {
@ -37,6 +42,7 @@ interface FilterSelectProps {
values: string[] | undefined;
current: string;
onChange: (value: string) => void;
formatValue?: (value: string) => string;
}
function FilterSelect({
@ -45,6 +51,7 @@ function FilterSelect({
values,
current,
onChange,
formatValue,
}: FilterSelectProps) {
if (!values || values.length === 0) return null;
return (
@ -52,7 +59,7 @@ function FilterSelect({
<option value="">All {label}</option>
{values.map((v) => (
<option key={v} value={v}>
{v}
{formatValue ? formatValue(v) : v}
</option>
))}
</select>
@ -322,6 +329,7 @@ export function FindingsPage() {
language: state.language || undefined,
rule_id: state.rule_id || undefined,
status: state.status || undefined,
verification: state.verification || undefined,
search: state.search || undefined,
}),
[state],
@ -621,6 +629,14 @@ export function FindingsPage() {
current={state.status}
onChange={(v) => handleFilterChange('status', v)}
/>
<FilterSelect
id="filter-verification"
label="Verification"
values={filters?.verification_statuses}
current={state.verification}
onChange={(v) => handleFilterChange('verification', v)}
formatValue={formatVerificationStatus}
/>
{hasActiveFilters && (
<button className="btn btn-sm btn-clear" onClick={resetFilters}>
Clear All
@ -764,7 +780,7 @@ export function FindingsPage() {
</td>
<td>
<VerdictBadge
verdict={f.evidence?.dynamic_verdict}
verdict={f.dynamic_verdict ?? f.evidence?.dynamic_verdict}
compact
/>
</td>