Fixed 3 bugs in Aphoria's claim verification engine that were causing false positives in Maxwell validation testing: **Bug 1: Path matching + predicate filtering** - Added predicate filtering to prevent cross-predicate matches - Added path prefix matching to respect crate boundaries - Prevents core/imports/serde from matching hypervisor/vsock/imports/serde **Bug 2: Value-specific absent checks** - Absent mode now checks for specific forbidden value, not any observation - Example: "Clone absent" + "Debug present" = PASS (not CONFLICT) - Only conflicts when the exact forbidden value is found **Bug 3: Wildcard pattern support** - Wildcard patterns like message/*/derives now match multiple paths - Enhanced wildcard_matches() to support prefix/*/suffix patterns - Correctly strips full scheme+language from observation paths **Test coverage:** - All 39 existing tests passing - 3 new tests added for bug fixes - 2 tests updated to use correct predicates - Zero clippy warnings **Maxwell validation:** - maxwell-core-no-serde-001: CONFLICT → PASS (respects path boundaries) - maxwell-singleton-no-clone-001: CONFLICT → PASS (value-specific absent) - 5 claims now correctly show as MISSING (expose predicate mismatches) The fixes successfully eliminate false positives while exposing pre-existing issues where claims used incorrect predicates. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
275 lines
10 KiB
TypeScript
275 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Button } from "@/components/ui/button";
|
|
import { getClient } from "@/lib/api/client";
|
|
import type {
|
|
AuthoredClaimDto,
|
|
ListClaimsResponse,
|
|
VerifyReportResponse,
|
|
CoverageReportResponse,
|
|
} from "@/lib/api/types";
|
|
import { ClaimsLoadingSkeleton } from "./claims-loading-skeleton";
|
|
import { ClaimsEmptyState } from "./claims-empty-state";
|
|
|
|
type PanelState<T> =
|
|
| { status: "idle" }
|
|
| { status: "loading" }
|
|
| { status: "success"; data: T }
|
|
| { status: "error"; error: string };
|
|
|
|
function getDefaultProjectPath(): string {
|
|
return process.env.NEXT_PUBLIC_DEFAULT_PROJECT_PATH || "/home/jml/Workspace/stemedb";
|
|
}
|
|
|
|
export function ClaimsPanel() {
|
|
const [projectPath, setProjectPath] = useState(getDefaultProjectPath());
|
|
const [claimsState, setClaimsState] = useState<PanelState<ListClaimsResponse>>({
|
|
status: "idle",
|
|
});
|
|
const [verifyState, setVerifyState] = useState<PanelState<VerifyReportResponse>>({
|
|
status: "idle",
|
|
});
|
|
const [coverageState, setCoverageState] = useState<PanelState<CoverageReportResponse>>({
|
|
status: "idle",
|
|
});
|
|
const [selectedClaim, setSelectedClaim] = useState<AuthoredClaimDto | null>(null);
|
|
|
|
const client = getClient();
|
|
|
|
const loadClaims = async () => {
|
|
setClaimsState({ status: "loading" });
|
|
try {
|
|
const data = await client.listClaims({ project_path: projectPath });
|
|
setClaimsState({ status: "success", data });
|
|
} catch (error) {
|
|
setClaimsState({
|
|
status: "error",
|
|
error: error instanceof Error ? error.message : "Failed to load claims",
|
|
});
|
|
}
|
|
};
|
|
|
|
const runVerification = async () => {
|
|
setVerifyState({ status: "loading" });
|
|
try {
|
|
const data = await client.verifyClaims({ project_path: projectPath });
|
|
setVerifyState({ status: "success", data });
|
|
} catch (error) {
|
|
setVerifyState({
|
|
status: "error",
|
|
error: error instanceof Error ? error.message : "Verification failed",
|
|
});
|
|
}
|
|
};
|
|
|
|
const loadCoverage = async () => {
|
|
setCoverageState({ status: "loading" });
|
|
try {
|
|
const data = await client.getCoverage({ project_path: projectPath });
|
|
setCoverageState({ status: "success", data });
|
|
} catch (error) {
|
|
setCoverageState({
|
|
status: "error",
|
|
error: error instanceof Error ? error.message : "Coverage load failed",
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Project Path Input */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Project Configuration</CardTitle>
|
|
<CardDescription>
|
|
Select the project to analyze claims for
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<label htmlFor="project-path" className="text-sm font-medium">
|
|
Project Path
|
|
</label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
id="project-path"
|
|
value={projectPath}
|
|
onChange={(e) => setProjectPath(e.target.value)}
|
|
placeholder="/path/to/project"
|
|
/>
|
|
<Button onClick={loadClaims}>Load Claims</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Tabs for Claims / Verify / Coverage */}
|
|
<Tabs defaultValue="claims" className="w-full">
|
|
<TabsList className="grid w-full grid-cols-3">
|
|
<TabsTrigger value="claims">Claims List</TabsTrigger>
|
|
<TabsTrigger value="verify">Verification</TabsTrigger>
|
|
<TabsTrigger value="coverage">Coverage</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Claims List Tab */}
|
|
<TabsContent value="claims">
|
|
{claimsState.status === "loading" && <ClaimsLoadingSkeleton />}
|
|
{claimsState.status === "error" && (
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<p className="text-destructive">{claimsState.error}</p>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
{claimsState.status === "success" &&
|
|
(claimsState.data.claims.length === 0 ? (
|
|
<ClaimsEmptyState />
|
|
) : (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>
|
|
Authored Claims ({claimsState.data.claims.length})
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
{claimsState.data.claims.map((claim) => (
|
|
<div
|
|
key={claim.id}
|
|
className="border rounded-lg p-4 hover:bg-muted/50 cursor-pointer"
|
|
onClick={() => setSelectedClaim(claim)}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-1">
|
|
<p className="font-mono text-sm">{claim.id}</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
{claim.concept_path}
|
|
</p>
|
|
</div>
|
|
<div className="text-sm text-muted-foreground">
|
|
{claim.category}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
{claimsState.status === "idle" && (
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<p className="text-muted-foreground text-center">
|
|
Enter a project path and click "Load Claims" to begin
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</TabsContent>
|
|
|
|
{/* Verification Tab */}
|
|
<TabsContent value="verify">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Claim Verification</CardTitle>
|
|
<CardDescription>
|
|
Verify claims against extracted observations
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<Button onClick={runVerification} disabled={verifyState.status === "loading"}>
|
|
{verifyState.status === "loading" ? "Running..." : "Run Verification"}
|
|
</Button>
|
|
|
|
{verifyState.status === "success" && (
|
|
<div className="space-y-4">
|
|
<div className="grid grid-cols-5 gap-4">
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold">{verifyState.data.summary.total_claims}</div>
|
|
<div className="text-sm text-muted-foreground">Total</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-green-600">
|
|
{verifyState.data.summary.pass}
|
|
</div>
|
|
<div className="text-sm text-muted-foreground">Pass</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-destructive">
|
|
{verifyState.data.summary.conflict}
|
|
</div>
|
|
<div className="text-sm text-muted-foreground">Conflict</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold">{verifyState.data.summary.missing}</div>
|
|
<div className="text-sm text-muted-foreground">Missing</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold">{verifyState.data.summary.unclaimed}</div>
|
|
<div className="text-sm text-muted-foreground">Unclaimed</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{verifyState.status === "error" && (
|
|
<p className="text-destructive">{verifyState.error}</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* Coverage Tab */}
|
|
<TabsContent value="coverage">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Coverage Metrics</CardTitle>
|
|
<CardDescription>
|
|
Per-module claim coverage analysis
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<Button onClick={loadCoverage} disabled={coverageState.status === "loading"}>
|
|
{coverageState.status === "loading" ? "Computing..." : "Compute Coverage"}
|
|
</Button>
|
|
|
|
{coverageState.status === "success" && (
|
|
<div className="space-y-4">
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold">
|
|
{coverageState.data.summary.total_observations}
|
|
</div>
|
|
<div className="text-sm text-muted-foreground">Observations</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold">
|
|
{coverageState.data.summary.total_claims}
|
|
</div>
|
|
<div className="text-sm text-muted-foreground">Claims</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold">
|
|
{coverageState.data.summary.claimed_percentage.toFixed(1)}%
|
|
</div>
|
|
<div className="text-sm text-muted-foreground">Coverage</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{coverageState.status === "error" && (
|
|
<p className="text-destructive">{coverageState.error}</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
);
|
|
}
|