Merge pull request #738 from AssetOverflow/codex/lg-2-logos-route-shell
feat(workbench): CORE-Logos Studio LG-2 — /logos route shell + Overview/Identity/Safety
This commit is contained in:
commit
b7ec02b869
23 changed files with 1381 additions and 21 deletions
|
|
@ -59,7 +59,7 @@ successful proof.
|
|||
## 4. Current Route Map
|
||||
|
||||
The route registry in `workbench-ui/src/app/routes.ts` is the source of truth.
|
||||
Current route count: 14.
|
||||
Current route count: 15.
|
||||
|
||||
| Section | Route | Path | Shortcut | Purpose |
|
||||
|---|---|---|---|---|
|
||||
|
|
@ -76,6 +76,7 @@ Current route count: 14.
|
|||
| Discipline | Evals | `/evals` | `⌘5` | Run/read allowlisted eval lanes and wrong=0 ledgers. |
|
||||
| Discipline | Calibration | `/calibration` | Palette | Inspect practice-class reliability and license verdicts. |
|
||||
| Substrate | Packs | `/packs` | `⌘7` | Browse language/runtime pack metadata. |
|
||||
| Substrate | CORE-Logos | `/logos` | Palette | Inspect CORE-Logos pack identity and safety. |
|
||||
| Settings | Settings | `/settings` | `⌘0` | Manage local UI preferences; engine config remains CLI-only. |
|
||||
|
||||
Pinned route shortcuts cover Chat through Settings. All routes are searchable in
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ snapshot = {
|
|||
"GroundingSource": literal_values(ROOT / "core" / "epistemic_state.py", "GroundingSource"),
|
||||
"NormativeClearance": enum_values(ROOT / "core" / "epistemic_state.py", "NormativeClearance"),
|
||||
"ReviewState": literal_values(ROOT / "teaching" / "proposals.py", "ReviewState"),
|
||||
"SafetyVerdict": enum_values(ROOT / "workbench" / "schemas.py", "SafetyVerdict"),
|
||||
}
|
||||
|
||||
print(json.dumps(snapshot, indent=2, sort_keys=True))
|
||||
|
|
|
|||
|
|
@ -35,5 +35,11 @@
|
|||
"accepted",
|
||||
"rejected",
|
||||
"withdrawn"
|
||||
],
|
||||
"SafetyVerdict": [
|
||||
"clear",
|
||||
"warning",
|
||||
"failed",
|
||||
"unknown"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import type {
|
|||
RunDetail,
|
||||
PackSummary,
|
||||
PackDetail,
|
||||
LogosPackSummary,
|
||||
LogosPackOverview,
|
||||
LogosSafetyReport,
|
||||
VaultSummary,
|
||||
VaultEntry,
|
||||
CalibrationClass,
|
||||
|
|
@ -298,6 +301,21 @@ export async function fetchPack(packId: string): Promise<PackDetail> {
|
|||
return apiFetch<PackDetail>(`/packs/${encodeURIComponent(packId)}`);
|
||||
}
|
||||
|
||||
export async function fetchLogosPacks(): Promise<LogosPackSummary[]> {
|
||||
const envelope = await apiFetch<ItemsEnvelope<LogosPackSummary>>("/logos/packs");
|
||||
return envelope.items;
|
||||
}
|
||||
|
||||
export async function fetchLogosPackOverview(packId: string): Promise<LogosPackOverview> {
|
||||
return apiFetch<LogosPackOverview>(`/logos/packs/${encodeURIComponent(packId)}`);
|
||||
}
|
||||
|
||||
export async function fetchLogosPackSafety(packId: string): Promise<LogosSafetyReport> {
|
||||
return apiFetch<LogosSafetyReport>(
|
||||
`/logos/packs/${encodeURIComponent(packId)}/safety`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchVaultSummary(): Promise<VaultSummary> {
|
||||
return apiFetch<VaultSummary>("/vault/summary");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ import {
|
|||
fetchRun,
|
||||
fetchPacks,
|
||||
fetchPack,
|
||||
fetchLogosPacks,
|
||||
fetchLogosPackOverview,
|
||||
fetchLogosPackSafety,
|
||||
fetchVaultSummary,
|
||||
fetchVaultEntries,
|
||||
fetchCalibrationClasses,
|
||||
|
|
@ -53,6 +56,9 @@ import type {
|
|||
RunDetail,
|
||||
PackSummary,
|
||||
PackDetail,
|
||||
LogosPackSummary,
|
||||
LogosPackOverview,
|
||||
LogosSafetyReport,
|
||||
VaultSummary,
|
||||
VaultEntry,
|
||||
CalibrationClass,
|
||||
|
|
@ -399,6 +405,35 @@ export function usePack(packId?: string | null) {
|
|||
});
|
||||
}
|
||||
|
||||
export function useLogosPacks() {
|
||||
return useQuery<LogosPackSummary[], WorkbenchApiError>({
|
||||
queryKey: ["api", "logos", "packs"],
|
||||
queryFn: fetchLogosPacks,
|
||||
staleTime: 30_000,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLogosPackOverview(packId?: string | null) {
|
||||
return useQuery<LogosPackOverview, WorkbenchApiError>({
|
||||
queryKey: ["api", "logos", "pack", packId ?? null],
|
||||
queryFn: () => fetchLogosPackOverview(packId as string),
|
||||
enabled: typeof packId === "string" && packId.length > 0,
|
||||
staleTime: 30_000,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLogosPackSafety(packId?: string | null) {
|
||||
return useQuery<LogosSafetyReport, WorkbenchApiError>({
|
||||
queryKey: ["api", "logos", "pack", packId ?? null, "safety"],
|
||||
queryFn: () => fetchLogosPackSafety(packId as string),
|
||||
enabled: typeof packId === "string" && packId.length > 0,
|
||||
staleTime: 30_000,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useVaultSummary() {
|
||||
return useQuery<VaultSummary, WorkbenchApiError>({
|
||||
queryKey: ["api", "vault", "summary"],
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ const CalibrationRoute = lazy(() =>
|
|||
const PacksRoute = lazy(() =>
|
||||
import("./packs/PacksRoute").then((module) => ({ default: module.PacksRoute })),
|
||||
);
|
||||
const LogosRoute = lazy(() =>
|
||||
import("./logos/LogosRoute").then((module) => ({ default: module.LogosRoute })),
|
||||
);
|
||||
const SettingsRoute = lazy(() =>
|
||||
import("./settings/SettingsRoute").then((module) => ({
|
||||
default: module.SettingsRoute,
|
||||
|
|
@ -89,6 +92,7 @@ export const ROUTE_ELEMENTS: RouteElementMap = {
|
|||
evals: lazyRoute(<EvalsRoute />),
|
||||
calibration: lazyRoute(<CalibrationRoute />),
|
||||
packs: lazyRoute(<PacksRoute />),
|
||||
logos: lazyRoute(<LogosRoute />),
|
||||
settings: lazyRoute(<SettingsRoute />),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -159,6 +159,30 @@ export function deriveStages(subject: EvidenceSubject): RailStage[] | null {
|
|||
stage("action", "dim", "not applicable to pack metadata"),
|
||||
];
|
||||
}
|
||||
case "logos_pack": {
|
||||
const d = subject.data;
|
||||
return [
|
||||
stage("intent", "dim", "not applicable to read-only Logos pack inspection"),
|
||||
stage("subject", "lit", "selected CORE-Logos pack"),
|
||||
stage(
|
||||
"provenance",
|
||||
d ? evidenceAny(d.manifest_digest, d.manifest_path) : "hollow",
|
||||
"manifest_digest / manifest_path",
|
||||
),
|
||||
stage(
|
||||
"admissibility",
|
||||
d ? evidenceAny(d.safety_status, d.checksum_status) : "hollow",
|
||||
"safety_status / checksum_status",
|
||||
),
|
||||
stage(
|
||||
"replay",
|
||||
d && d.holonomy_case_count && d.holonomy_case_count > 0 ? "lit" : "hollow",
|
||||
"holonomy_case_count (0 means missing_evidence)",
|
||||
),
|
||||
stage("authority", "dim", "read-only Logos Studio has no mutation authority"),
|
||||
stage("action", "dim", "proposal mode none"),
|
||||
];
|
||||
}
|
||||
case "vault_entry": {
|
||||
const d = subject.data;
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import {
|
|||
EpistemicStateBadge,
|
||||
GroundingSourceBadge,
|
||||
NormativeClearanceBadge,
|
||||
SafetyVerdictBadge,
|
||||
type EpistemicState,
|
||||
type GroundingSource,
|
||||
type NormativeClearance,
|
||||
type SafetyVerdict,
|
||||
} from "../design/components/badges";
|
||||
import type { LeewayEvidence } from "../types/api";
|
||||
|
||||
|
|
@ -238,6 +240,75 @@ function PackInspector({ subject }: { subject: Extract<EvidenceSubject, { kind:
|
|||
);
|
||||
}
|
||||
|
||||
function LogosPackInspector({
|
||||
subject,
|
||||
}: {
|
||||
subject: Extract<EvidenceSubject, { kind: "logos_pack" }>;
|
||||
}) {
|
||||
const { data } = subject;
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
<h3 className="text-xs font-semibold text-[var(--color-text-secondary)]">
|
||||
CORE-Logos Pack
|
||||
</h3>
|
||||
<p className="m-0 font-mono text-xs text-[var(--color-text-primary)]">
|
||||
{subject.packId}
|
||||
</p>
|
||||
{data ? (
|
||||
<MetadataTable
|
||||
rows={[
|
||||
...(data.safety_status
|
||||
? [
|
||||
{
|
||||
key: "safety",
|
||||
value: (
|
||||
<SafetyVerdictBadge value={data.safety_status as SafetyVerdict} />
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.checksum_status
|
||||
? [
|
||||
{
|
||||
key: "checksum",
|
||||
value: (
|
||||
<SafetyVerdictBadge value={data.checksum_status as SafetyVerdict} />
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.manifest_digest
|
||||
? [
|
||||
{
|
||||
key: "manifest",
|
||||
value: data.manifest_digest,
|
||||
mono: true,
|
||||
copyable: true,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(data.role ? [{ key: "role", value: data.role }] : []),
|
||||
...(data.language ? [{ key: "language", value: data.language }] : []),
|
||||
...(typeof data.holonomy_case_count === "number"
|
||||
? [
|
||||
{
|
||||
key: "holonomy",
|
||||
value:
|
||||
data.holonomy_case_count === 0
|
||||
? "0 / missing_evidence"
|
||||
: String(data.holonomy_case_count),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<DetailNotLoaded />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VaultEntryInspector({ subject }: { subject: Extract<EvidenceSubject, { kind: "vault_entry" }> }) {
|
||||
const { data } = subject;
|
||||
return (
|
||||
|
|
@ -372,6 +443,8 @@ function InspectorProjection({ subject }: { subject: EvidenceSubject }) {
|
|||
return <RunInspector subject={subject} />;
|
||||
case "pack":
|
||||
return <PackInspector subject={subject} />;
|
||||
case "logos_pack":
|
||||
return <LogosPackInspector subject={subject} />;
|
||||
case "vault_entry":
|
||||
return <VaultEntryInspector subject={subject} />;
|
||||
case "audit_event":
|
||||
|
|
|
|||
|
|
@ -85,11 +85,11 @@ describe("Shell", () => {
|
|||
expect(document.querySelector('[data-density="compact"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("LeftNav has exactly 14 items in section-grouped order", () => {
|
||||
it("LeftNav has exactly 15 items in section-grouped order", () => {
|
||||
renderShell();
|
||||
const nav = document.querySelector('[data-region="leftnav"]')!;
|
||||
const links = nav.querySelectorAll("a");
|
||||
expect(links).toHaveLength(14);
|
||||
expect(links).toHaveLength(15);
|
||||
const labels = Array.from(links).map((l) => l.textContent);
|
||||
// Grouped by section (Converse → Cognition → Determinism → Evidence →
|
||||
// Discipline → Substrate → Settings), derived from the route registry.
|
||||
|
|
@ -107,6 +107,7 @@ describe("Shell", () => {
|
|||
"Evals",
|
||||
"Calibration",
|
||||
"Packs",
|
||||
"CORE-Logos",
|
||||
"Settings",
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ function routerParamsFor(url: string): Record<string, string | undefined> {
|
|||
return segment === undefined ? {} : { sessionId: segment };
|
||||
case "packs":
|
||||
return segment === undefined ? {} : { packId: segment };
|
||||
case "logos":
|
||||
return segment === undefined ? {} : { logosPackId: segment };
|
||||
case "evals":
|
||||
return segment === undefined ? {} : { laneId: segment };
|
||||
case "replay":
|
||||
|
|
@ -68,6 +70,11 @@ const KINDS: Array<{ name: string; subject: AddressableSubject; path: string }>
|
|||
subject: { kind: "pack", packId: "en/core pack" },
|
||||
path: "/packs/en%2Fcore%20pack",
|
||||
},
|
||||
{
|
||||
name: "logos_pack",
|
||||
subject: { kind: "logos_pack", packId: "he_logos_micro_v1" },
|
||||
path: "/logos/he_logos_micro_v1",
|
||||
},
|
||||
];
|
||||
|
||||
const INSPECT_ONLY_KINDS: Array<{ name: string; subject: AddressableSubject; path: string }> = [
|
||||
|
|
@ -192,6 +199,7 @@ describe("urlToSubject malformed input", () => {
|
|||
["empty proposal id", { proposalId: "" }],
|
||||
["empty session id", { sessionId: "" }],
|
||||
["empty pack id", { packId: "" }],
|
||||
["empty logos pack id", { logosPackId: "" }],
|
||||
["empty lane id", { laneId: "" }],
|
||||
["empty artifact id", { artifactId: "" }],
|
||||
["no params at all", {}],
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import type { EvidenceSubject } from "./evidenceContext";
|
|||
// artifact -> /replay/<artifactId>
|
||||
// run -> /runs/<sessionId>
|
||||
// pack -> /packs/<packId>
|
||||
// logos_pack -> /logos/<packId> (?inspect uses logos:<packId>)
|
||||
// vault_entry -> /vault?inspect=vault:<entryIndex>
|
||||
// audit_event -> /audit?inspect=audit:<eventId>
|
||||
// calibration_class -> /calibration?inspect=calibration:<className>
|
||||
|
|
@ -45,6 +46,8 @@ export function sameIdentity(a: EvidenceSubject, b: EvidenceSubject): boolean {
|
|||
return b.kind === "run" && b.sessionId === a.sessionId;
|
||||
case "pack":
|
||||
return b.kind === "pack" && b.packId === a.packId;
|
||||
case "logos_pack":
|
||||
return b.kind === "logos_pack" && b.packId === a.packId;
|
||||
case "vault_entry":
|
||||
return b.kind === "vault_entry" && b.entryIndex === a.entryIndex;
|
||||
case "audit_event":
|
||||
|
|
@ -86,6 +89,8 @@ function subjectAddress(subject: AddressableSubject): SubjectAddress {
|
|||
return { path: `/runs/${encodeURIComponent(subject.sessionId)}`, params: emptyParams() };
|
||||
case "pack":
|
||||
return { path: `/packs/${encodeURIComponent(subject.packId)}`, params: emptyParams() };
|
||||
case "logos_pack":
|
||||
return { path: `/logos/${encodeURIComponent(subject.packId)}`, params: emptyParams() };
|
||||
case "vault_entry":
|
||||
{
|
||||
const params = emptyParams();
|
||||
|
|
@ -121,6 +126,8 @@ export function subjectToInspectValue(subject: AddressableSubject): string {
|
|||
return `run:${subject.sessionId}`;
|
||||
case "pack":
|
||||
return `pack:${subject.packId}`;
|
||||
case "logos_pack":
|
||||
return `logos:${subject.packId}`;
|
||||
case "vault_entry":
|
||||
return `vault:${subject.entryIndex}`;
|
||||
case "audit_event":
|
||||
|
|
@ -172,6 +179,8 @@ export function inspectValueToSubject(
|
|||
return { kind: "run", sessionId: id };
|
||||
case "pack":
|
||||
return { kind: "pack", packId: id };
|
||||
case "logos":
|
||||
return { kind: "logos_pack", packId: id };
|
||||
case "vault": {
|
||||
const entryIndex = parseTurnId(id);
|
||||
return entryIndex === null ? null : { kind: "vault_entry", entryIndex };
|
||||
|
|
@ -211,6 +220,11 @@ function routeParamsToSubject(
|
|||
? null
|
||||
: { kind: "pack", packId: params.packId };
|
||||
}
|
||||
if (params.logosPackId !== undefined) {
|
||||
return params.logosPackId === ""
|
||||
? null
|
||||
: { kind: "logos_pack", packId: params.logosPackId };
|
||||
}
|
||||
if (params.laneId !== undefined) {
|
||||
return params.laneId === ""
|
||||
? null
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import type {
|
|||
ArtifactDetail,
|
||||
EvalRunResult,
|
||||
CalibrationClass,
|
||||
LogosPackOverview,
|
||||
SafetyVerdict,
|
||||
} from "../types/api";
|
||||
|
||||
export type ProposalSubjectDomain = "cognition" | "math";
|
||||
|
|
@ -33,6 +35,27 @@ export interface PackSubjectData {
|
|||
determinism_class?: string | null;
|
||||
}
|
||||
|
||||
export interface LogosPackSubjectData
|
||||
extends Partial<
|
||||
Pick<
|
||||
LogosPackOverview,
|
||||
| "pack_id"
|
||||
| "language"
|
||||
| "role"
|
||||
| "script"
|
||||
| "version"
|
||||
| "determinism_class"
|
||||
| "gate_engaged"
|
||||
| "oov_policy"
|
||||
| "safety_status"
|
||||
| "manifest_digest"
|
||||
| "manifest_path"
|
||||
| "holonomy_case_count"
|
||||
>
|
||||
> {
|
||||
checksum_status?: SafetyVerdict | null;
|
||||
}
|
||||
|
||||
export interface VaultEntrySubjectData {
|
||||
entry_index?: number;
|
||||
epistemic_state?: string;
|
||||
|
|
@ -60,6 +83,7 @@ export type EvidenceSubject =
|
|||
| { kind: "eval_result"; lane: string; data?: EvalRunResult }
|
||||
| { kind: "run"; sessionId: string; data?: RunSubjectData }
|
||||
| { kind: "pack"; packId: string; data?: PackSubjectData }
|
||||
| { kind: "logos_pack"; packId: string; data?: LogosPackSubjectData }
|
||||
| { kind: "vault_entry"; entryIndex: number; data?: VaultEntrySubjectData }
|
||||
| { kind: "audit_event"; eventId: string; data?: AuditEventSubjectData }
|
||||
| { kind: "calibration_class"; className: string; data?: CalibrationClass }
|
||||
|
|
|
|||
252
workbench-ui/src/app/logos/LogosRoute.test.tsx
Normal file
252
workbench-ui/src/app/logos/LogosRoute.test.tsx
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { MemoryRouter, Route, Routes, useLocation } from "react-router-dom";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createTestQueryClient } from "../../test/createTestQueryClient";
|
||||
import type {
|
||||
LogosPackOverview,
|
||||
LogosPackSummary,
|
||||
LogosSafetyReport,
|
||||
SafetyVerdict,
|
||||
} from "../../types/api";
|
||||
import { EvidenceProvider } from "../evidenceContext";
|
||||
import { RightInspector } from "../RightInspector";
|
||||
import { LogosRoute } from "./LogosRoute";
|
||||
|
||||
const summaries: LogosPackSummary[] = [
|
||||
{
|
||||
pack_id: "he_logos_micro_v1",
|
||||
language: "he",
|
||||
role: "depth_root",
|
||||
script: "Hebrew",
|
||||
version: "1.0.0",
|
||||
determinism_class: "D0",
|
||||
gate_engaged: true,
|
||||
oov_policy: "fail_closed",
|
||||
lexicon_count: 3,
|
||||
gloss_count: 2,
|
||||
morphology_count: 3,
|
||||
frame_count: 0,
|
||||
composition_count: 0,
|
||||
alignment_edge_count: 4,
|
||||
holonomy_case_count: 0,
|
||||
safety_status: "unknown",
|
||||
manifest_digest: "sha256:aaaaaaaaaaaaaaaa",
|
||||
manifest_path: "language_packs/data/he_logos_micro_v1/manifest.json",
|
||||
},
|
||||
{
|
||||
pack_id: "grc_logos_cognition_v1",
|
||||
language: "grc",
|
||||
role: "depth_relation",
|
||||
script: "Greek",
|
||||
version: "1.0.0",
|
||||
determinism_class: "D0",
|
||||
gate_engaged: true,
|
||||
oov_policy: "fail_closed",
|
||||
lexicon_count: 5,
|
||||
gloss_count: 5,
|
||||
morphology_count: 5,
|
||||
frame_count: 0,
|
||||
composition_count: 0,
|
||||
alignment_edge_count: 8,
|
||||
holonomy_case_count: 0,
|
||||
safety_status: "warning",
|
||||
manifest_digest: "sha256:bbbbbbbbbbbbbbbb",
|
||||
manifest_path: "language_packs/data/grc_logos_cognition_v1/manifest.json",
|
||||
},
|
||||
];
|
||||
|
||||
function overviewFor(packId: string): LogosPackOverview {
|
||||
const summary = summaries.find((item) => item.pack_id === packId) ?? summaries[0];
|
||||
return {
|
||||
...summary,
|
||||
schema_version: "logos_pack_overview_v1",
|
||||
normalization_policy: "NFC",
|
||||
source_manifest: "logos-source-manifest.json",
|
||||
known_gaps: packId.includes("grc") ? ["undeclared English collapse anchors"] : [],
|
||||
};
|
||||
}
|
||||
|
||||
function safetyFor(
|
||||
packId: string,
|
||||
verdict: SafetyVerdict = packId.includes("grc") ? "warning" : "unknown",
|
||||
): LogosSafetyReport {
|
||||
return {
|
||||
schema_version: "logos_safety_report_v1",
|
||||
pack_id: packId,
|
||||
checksum_status: verdict === "warning" ? "warning" : "unknown",
|
||||
checksum_errors: verdict === "warning" ? ["alignment checksum pending review"] : [],
|
||||
domain_contract: { valid: verdict !== "failed", domain_id: "hebrew_greek_textual_reasoning" },
|
||||
domain_contract_status: verdict === "failed" ? "failed" : "unknown",
|
||||
oov_policy_ok: true,
|
||||
gate_policy_ok: true,
|
||||
path_safety_ok: true,
|
||||
dangling_morphology_links: verdict === "warning"
|
||||
? [{ entry_id: "logos.entry.1", morphology_id: "missing-morph" }]
|
||||
: [],
|
||||
invalid_alignment_targets: verdict === "warning"
|
||||
? [
|
||||
{
|
||||
edge_id: "edge-warning-1",
|
||||
source_id: "grc-logos",
|
||||
target_id: "en-collapse-logos",
|
||||
relation: "logos-collapse",
|
||||
target_pack_id: "en_core_cognition_v1",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
missing_holonomy_refs: "unknown",
|
||||
epistemic_status_counts: { speculative: 5, contested: verdict === "warning" ? 1 : 0 },
|
||||
speculative_entries: ["logos.entry.1", "logos.entry.2"],
|
||||
contested_entries: verdict === "warning" ? ["logos.entry.3"] : [],
|
||||
falsified_entries: [],
|
||||
known_gaps: verdict === "warning" ? ["no holonomy refs"] : [],
|
||||
verdict,
|
||||
};
|
||||
}
|
||||
|
||||
function okEnvelope(data: unknown) {
|
||||
return { ok: true, generated_at: "2026-06-14T00:00:00Z", data };
|
||||
}
|
||||
|
||||
function stubLogosFetch() {
|
||||
const paths: string[] = [];
|
||||
const fetchMock = vi.fn((input: unknown) => {
|
||||
const path = new URL(String(input)).pathname;
|
||||
paths.push(path);
|
||||
if (path === "/logos/packs") {
|
||||
return Promise.resolve({
|
||||
json: async () => okEnvelope({ items: summaries }),
|
||||
});
|
||||
}
|
||||
const safetyMatch = path.match(/^\/logos\/packs\/([^/]+)\/safety$/);
|
||||
if (safetyMatch) {
|
||||
const packId = decodeURIComponent(safetyMatch[1]);
|
||||
return Promise.resolve({
|
||||
json: async () => okEnvelope(safetyFor(packId)),
|
||||
});
|
||||
}
|
||||
const overviewMatch = path.match(/^\/logos\/packs\/([^/]+)$/);
|
||||
if (overviewMatch) {
|
||||
const packId = decodeURIComponent(overviewMatch[1]);
|
||||
return Promise.resolve({
|
||||
json: async () => okEnvelope(overviewFor(packId)),
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
json: async () => ({
|
||||
ok: false,
|
||||
generated_at: "2026-06-14T00:00:00Z",
|
||||
error: { code: "not_found", message: `unexpected path ${path}` },
|
||||
}),
|
||||
});
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
return { paths, fetchMock };
|
||||
}
|
||||
|
||||
function LocationProbe() {
|
||||
const location = useLocation();
|
||||
return <span data-testid="location">{`${location.pathname}${location.search}`}</span>;
|
||||
}
|
||||
|
||||
function renderRoute(initialEntry = "/logos") {
|
||||
return render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
<EvidenceProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/logos/:logosPackId?"
|
||||
element={
|
||||
<div className="grid grid-cols-[1fr_20rem]">
|
||||
<div>
|
||||
<LogosRoute />
|
||||
<LocationProbe />
|
||||
</div>
|
||||
<RightInspector />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</EvidenceProvider>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe("LogosRoute", () => {
|
||||
it("renders the grouped Pack Universe and persistent read-only status", async () => {
|
||||
stubLogosFetch();
|
||||
renderRoute();
|
||||
|
||||
expect(await screen.findByText("Pack Universe")).toBeInTheDocument();
|
||||
expect(screen.getAllByText("Depth root").length).toBeGreaterThan(0);
|
||||
expect(screen.getByText("Logos cognition")).toBeInTheDocument();
|
||||
expect(screen.getByText("he_logos_micro_v1")).toBeInTheDocument();
|
||||
expect(screen.getByText("grc_logos_cognition_v1")).toBeInTheDocument();
|
||||
expect(screen.getByText("proposal mode: none — read-only")).toBeInTheDocument();
|
||||
expect(screen.getByText("no pack selected")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Draft proposal/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("selects a pack, renders Overview, projects logos_pack evidence, and avoids deferred endpoints", async () => {
|
||||
const { paths } = stubLogosFetch();
|
||||
const user = userEvent.setup();
|
||||
renderRoute();
|
||||
|
||||
await user.click(await screen.findByText("he_logos_micro_v1"));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("location")).toHaveTextContent("/logos/he_logos_micro_v1"),
|
||||
);
|
||||
expect(await screen.findByText("depth-root compression")).toBeInTheDocument();
|
||||
expect(screen.getByText("operational articulation")).toBeInTheDocument();
|
||||
expect(screen.getByText("depth-relation precision")).toBeInTheDocument();
|
||||
expect(screen.getByText("holonomy_cases · missing_evidence")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("tab", { name: /holonomy/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/proof card/i)).not.toBeInTheDocument();
|
||||
expect(screen.getByText("CORE-Logos Pack")).toBeInTheDocument();
|
||||
expect(screen.getByText("0 / missing_evidence")).toBeInTheDocument();
|
||||
expect(paths).not.toContain("/logos/packs/he_logos_micro_v1/contents");
|
||||
expect(paths).not.toContain("/logos/packs/he_logos_micro_v1/alignment");
|
||||
});
|
||||
|
||||
it("renders Identity passport fields and the raw live overview projection", async () => {
|
||||
stubLogosFetch();
|
||||
const user = userEvent.setup();
|
||||
renderRoute("/logos/he_logos_micro_v1");
|
||||
|
||||
await user.click(await screen.findByRole("tab", { name: "Identity" }));
|
||||
|
||||
expect(await screen.findByText("manifest_digest")).toBeInTheDocument();
|
||||
expect(screen.getByText("source_manifest")).toBeInTheDocument();
|
||||
expect(screen.getByText("Raw overview projection")).toBeInTheDocument();
|
||||
expect(screen.getByText(/\/manifest_digest/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/\/schema_version/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders Safety warnings and unknown holonomy without mapping either to clear", async () => {
|
||||
stubLogosFetch();
|
||||
const user = userEvent.setup();
|
||||
renderRoute("/logos/grc_logos_cognition_v1");
|
||||
|
||||
await user.click(await screen.findByRole("tab", { name: "Safety" }));
|
||||
|
||||
expect(await screen.findByText("Invalid alignment targets")).toBeInTheDocument();
|
||||
expect(screen.getByText("en-collapse-logos")).toBeInTheDocument();
|
||||
expect(screen.getByText("Dangling morphology links")).toBeInTheDocument();
|
||||
expect(screen.getByText("missing-morph")).toBeInTheDocument();
|
||||
expect(screen.getByText("missing_holonomy_refs")).toBeInTheDocument();
|
||||
expect(screen.getAllByText("Warning").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText("Unknown").length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText("Clear")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
733
workbench-ui/src/app/logos/LogosRoute.tsx
Normal file
733
workbench-ui/src/app/logos/LogosRoute.tsx
Normal file
|
|
@ -0,0 +1,733 @@
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { WorkbenchApiError } from "../../api/client";
|
||||
import {
|
||||
useLogosPackOverview,
|
||||
useLogosPacks,
|
||||
useLogosPackSafety,
|
||||
} from "../../api/queries";
|
||||
import { MetadataTable } from "../../design/components/MetadataTable/MetadataTable";
|
||||
import { Panel } from "../../design/components/Panel/Panel";
|
||||
import { SplitPane } from "../../design/components/SplitPane/SplitPane";
|
||||
import { StableJsonViewer } from "../../design/components/StableJsonViewer";
|
||||
import { TabBar, type Tab } from "../../design/components/TabBar/TabBar";
|
||||
import { EmptyState } from "../../design/components/states/EmptyState";
|
||||
import { ErrorState } from "../../design/components/states/ErrorState";
|
||||
import { LoadingState } from "../../design/components/states/LoadingState";
|
||||
import {
|
||||
SafetyVerdict as BadgeSafetyVerdict,
|
||||
SafetyVerdictBadge,
|
||||
} from "../../design/components/badges";
|
||||
import type {
|
||||
LogosAlignmentTargetIssue,
|
||||
LogosMorphologyLinkIssue,
|
||||
LogosPackOverview,
|
||||
LogosPackSummary,
|
||||
LogosSafetyReport,
|
||||
SafetyVerdict,
|
||||
} from "../../types/api";
|
||||
import { pushRecentItem } from "../commandRegistry";
|
||||
import { subjectToUrl } from "../evidenceAddress";
|
||||
import { useEvidenceSubject } from "../evidenceContext";
|
||||
|
||||
const LOGOS_TABS: readonly Tab[] = [
|
||||
{ id: "overview", label: "Overview" },
|
||||
{ id: "identity", label: "Identity" },
|
||||
{ id: "safety", label: "Safety" },
|
||||
];
|
||||
|
||||
const ROLE_ORDER = ["depth_root", "depth_relation", "logos-cognition", "other"] as const;
|
||||
|
||||
const ROLE_LABELS: Record<string, string> = {
|
||||
depth_root: "Depth root",
|
||||
depth_relation: "Depth relation",
|
||||
"logos-cognition": "Logos cognition",
|
||||
other: "Other Logos packs",
|
||||
};
|
||||
|
||||
const TRI_LANGUAGE = [
|
||||
{
|
||||
id: "en",
|
||||
label: "English",
|
||||
role: "operational articulation",
|
||||
},
|
||||
{
|
||||
id: "he",
|
||||
label: "Hebrew",
|
||||
role: "depth-root compression",
|
||||
},
|
||||
{
|
||||
id: "grc",
|
||||
label: "Koine Greek",
|
||||
role: "depth-relation precision",
|
||||
},
|
||||
] as const;
|
||||
|
||||
function errorMessage(error: unknown) {
|
||||
return error instanceof WorkbenchApiError
|
||||
? error.message
|
||||
: "CORE-Logos request failed.";
|
||||
}
|
||||
|
||||
function safetyVerdict(value: SafetyVerdict): BadgeSafetyVerdict {
|
||||
return value as BadgeSafetyVerdict;
|
||||
}
|
||||
|
||||
function roleKey(pack: LogosPackSummary): (typeof ROLE_ORDER)[number] {
|
||||
if (pack.role === "logos-cognition" || pack.pack_id.includes("cognition")) {
|
||||
return "logos-cognition";
|
||||
}
|
||||
if (pack.role === "depth_root" || pack.role === "depth_relation") {
|
||||
return pack.role;
|
||||
}
|
||||
return "other";
|
||||
}
|
||||
|
||||
function roleLabel(role: string | null): string {
|
||||
if (!role) return "not declared";
|
||||
return ROLE_LABELS[role] ?? role.replaceAll("_", " ");
|
||||
}
|
||||
|
||||
function languageLabel(language: string | null): string {
|
||||
if (language === "he") return "Hebrew";
|
||||
if (language === "grc") return "Koine Greek";
|
||||
if (language === "en") return "English";
|
||||
return language ?? "not declared";
|
||||
}
|
||||
|
||||
function CountBadge({ label, value }: { label: string; value: number }) {
|
||||
return (
|
||||
<span className="inline-flex h-6 items-center rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface-inset)] px-2 text-xs text-[var(--color-text-secondary)]">
|
||||
<span className="font-mono text-[var(--color-text-primary)]">{value}</span>
|
||||
<span className="ml-1">{label}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusPill({
|
||||
tone,
|
||||
children,
|
||||
}: {
|
||||
tone: "neutral" | "warning" | "danger";
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const className =
|
||||
tone === "warning"
|
||||
? "border-[var(--color-state-warning-border)] bg-[var(--color-state-warning-bg)] text-[var(--color-state-warning-text)]"
|
||||
: tone === "danger"
|
||||
? "border-[var(--color-state-danger-border)] bg-[var(--color-state-danger-bg)] text-[var(--color-state-danger-text)]"
|
||||
: "border-[var(--color-border-subtle)] bg-[var(--color-surface-inset)] text-[var(--color-text-secondary)]";
|
||||
return (
|
||||
<span className={`inline-flex h-6 items-center rounded-md border px-2 text-xs ${className}`}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function PackRow({
|
||||
pack,
|
||||
selected,
|
||||
onSelect,
|
||||
}: {
|
||||
pack: LogosPackSummary;
|
||||
selected: boolean;
|
||||
onSelect: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-current={selected ? "true" : undefined}
|
||||
onClick={onSelect}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Enter" && event.key !== " ") return;
|
||||
event.preventDefault();
|
||||
onSelect();
|
||||
}}
|
||||
className={`grid w-full grid-cols-[minmax(0,1fr)_auto] items-start gap-3 border-b border-[var(--color-border-subtle)] px-3 py-2 text-left transition-colors hover:bg-[var(--color-surface-inset)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-inset focus-visible:outline-[var(--color-focus-ring)] ${
|
||||
selected
|
||||
? "border-l-2 border-l-[var(--color-selected-border)] bg-[var(--color-selected-bg)] pl-[10px]"
|
||||
: "border-l-2 border-l-transparent pl-[10px]"
|
||||
}`}
|
||||
>
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate font-mono text-sm text-[var(--color-text-primary)]">
|
||||
{pack.pack_id}
|
||||
</span>
|
||||
<span className="mt-1 flex flex-wrap items-center gap-2 text-xs text-[var(--color-text-secondary)]">
|
||||
<span>{roleLabel(pack.role)}</span>
|
||||
<span aria-hidden className="text-[var(--color-text-muted)]">
|
||||
·
|
||||
</span>
|
||||
<span>{languageLabel(pack.language)}</span>
|
||||
{pack.script ? <span>{pack.script}</span> : null}
|
||||
</span>
|
||||
<span className="mt-2 flex flex-wrap gap-1.5">
|
||||
<CountBadge label="lex" value={pack.lexicon_count} />
|
||||
<CountBadge label="gloss" value={pack.gloss_count} />
|
||||
<CountBadge label="morph" value={pack.morphology_count} />
|
||||
<CountBadge label="align" value={pack.alignment_edge_count} />
|
||||
<CountBadge label="holonomy" value={pack.holonomy_case_count} />
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="justify-self-end"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<SafetyVerdictBadge value={safetyVerdict(pack.safety_status)} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PackUniverse({
|
||||
packs,
|
||||
selectedPackId,
|
||||
onSelect,
|
||||
}: {
|
||||
packs: readonly LogosPackSummary[];
|
||||
selectedPackId: string | null;
|
||||
onSelect: (pack: LogosPackSummary) => void;
|
||||
}) {
|
||||
const groups = useMemo(() => {
|
||||
const byRole = new Map<(typeof ROLE_ORDER)[number], LogosPackSummary[]>(
|
||||
ROLE_ORDER.map((role) => [role, []]),
|
||||
);
|
||||
for (const pack of packs) byRole.get(roleKey(pack))!.push(pack);
|
||||
return ROLE_ORDER.map((role) => ({
|
||||
role,
|
||||
packs: byRole.get(role)!.toSorted((a, b) => a.pack_id.localeCompare(b.pack_id)),
|
||||
})).filter((group) => group.packs.length > 0);
|
||||
}, [packs]);
|
||||
|
||||
return (
|
||||
<Panel title="Pack Universe">
|
||||
<div className="grid min-h-0 gap-4">
|
||||
{groups.map((group) => (
|
||||
<section key={group.role} aria-labelledby={`logos-group-${group.role}`}>
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<h3
|
||||
id={`logos-group-${group.role}`}
|
||||
className="m-0 text-xs font-semibold uppercase text-[var(--color-text-secondary)]"
|
||||
>
|
||||
{ROLE_LABELS[group.role]}
|
||||
</h3>
|
||||
<CountBadge label="packs" value={group.packs.length} />
|
||||
</div>
|
||||
<div className="overflow-hidden rounded-md border border-[var(--color-border-subtle)]">
|
||||
{group.packs.map((pack) => (
|
||||
<PackRow
|
||||
key={pack.pack_id}
|
||||
pack={pack}
|
||||
selected={pack.pack_id === selectedPackId}
|
||||
onSelect={() => onSelect(pack)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewTab({ overview }: { overview: LogosPackOverview }) {
|
||||
const counts = [
|
||||
["lexicon", overview.lexicon_count],
|
||||
["glosses", overview.gloss_count],
|
||||
["morphology", overview.morphology_count],
|
||||
["frames", overview.frame_count],
|
||||
["compositions", overview.composition_count],
|
||||
["alignment_edges", overview.alignment_edge_count],
|
||||
] as const;
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<section className="grid gap-3 sm:grid-cols-3">
|
||||
{TRI_LANGUAGE.map((item) => {
|
||||
const active =
|
||||
overview.language === item.id ||
|
||||
(item.id === "he" && overview.role === "depth_root") ||
|
||||
(item.id === "grc" && overview.role === "depth_relation");
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`rounded-md border p-3 ${
|
||||
active
|
||||
? "border-[var(--color-focus-ring)] bg-[var(--color-surface-inset)]"
|
||||
: "border-[var(--color-border-subtle)]"
|
||||
}`}
|
||||
>
|
||||
<h3 className="m-0 text-sm font-semibold text-[var(--color-text-primary)]">
|
||||
{item.label}
|
||||
</h3>
|
||||
<p className="m-0 mt-1 text-xs text-[var(--color-text-secondary)]">
|
||||
{item.role}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
|
||||
<MetadataTable
|
||||
rows={[
|
||||
{ key: "role", value: roleLabel(overview.role) },
|
||||
{ key: "language", value: languageLabel(overview.language) },
|
||||
{ key: "script", value: overview.script ?? "not declared" },
|
||||
{ key: "version", value: overview.version ?? "not declared", mono: true },
|
||||
{
|
||||
key: "determinism",
|
||||
value: overview.determinism_class ?? "not declared",
|
||||
},
|
||||
{
|
||||
key: "gate",
|
||||
value: overview.gate_engaged ? "engaged" : "not engaged",
|
||||
},
|
||||
{ key: "OOV", value: overview.oov_policy ?? "not declared" },
|
||||
{
|
||||
key: "safety",
|
||||
value: <SafetyVerdictBadge value={safetyVerdict(overview.safety_status)} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<section className="grid gap-2 sm:grid-cols-3 lg:grid-cols-4">
|
||||
{counts.map(([label, value]) => (
|
||||
<div
|
||||
key={label}
|
||||
className="rounded-md border border-[var(--color-border-subtle)] p-3"
|
||||
>
|
||||
<div className="font-mono text-lg text-[var(--color-text-primary)]">
|
||||
{value}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--color-text-secondary)]">{label}</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="rounded-md border border-[var(--color-state-warning-border)] bg-[var(--color-state-warning-bg)] p-3">
|
||||
<div className="font-mono text-lg text-[var(--color-state-warning-text)]">
|
||||
{overview.holonomy_case_count}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--color-state-warning-text)]">
|
||||
holonomy_cases · missing_evidence
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function IdentityTab({ overview }: { overview: LogosPackOverview }) {
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<MetadataTable
|
||||
rows={[
|
||||
{ key: "pack_id", value: overview.pack_id, mono: true, copyable: true },
|
||||
{ key: "manifest_path", value: overview.manifest_path, mono: true, copyable: true },
|
||||
{
|
||||
key: "manifest_digest",
|
||||
value: overview.manifest_digest,
|
||||
mono: true,
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
key: "source_manifest",
|
||||
value: overview.source_manifest ?? "not declared",
|
||||
mono: overview.source_manifest !== null,
|
||||
},
|
||||
{
|
||||
key: "normalization",
|
||||
value: overview.normalization_policy ?? "not declared",
|
||||
},
|
||||
{ key: "known_gaps", value: overview.known_gaps.length.toString(), mono: true },
|
||||
]}
|
||||
/>
|
||||
<section>
|
||||
<h3 className="m-0 mb-2 text-xs font-semibold text-[var(--color-text-secondary)]">
|
||||
Raw overview projection
|
||||
</h3>
|
||||
<StableJsonViewer source={JSON.stringify(overview, null, 2)} />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function IssueList({
|
||||
title,
|
||||
empty,
|
||||
items,
|
||||
}: {
|
||||
title: string;
|
||||
empty: string;
|
||||
items: readonly ReactNode[];
|
||||
}) {
|
||||
return (
|
||||
<section className="rounded-md border border-[var(--color-border-subtle)] p-3">
|
||||
<h3 className="m-0 mb-2 text-xs font-semibold text-[var(--color-text-secondary)]">
|
||||
{title}
|
||||
</h3>
|
||||
{items.length > 0 ? (
|
||||
<div className="grid gap-2">{items}</div>
|
||||
) : (
|
||||
<p className="m-0 text-sm text-[var(--color-text-secondary)]">{empty}</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function MorphologyIssue({ issue }: { issue: LogosMorphologyLinkIssue }) {
|
||||
return (
|
||||
<div className="rounded border border-[var(--color-state-warning-border)] bg-[var(--color-state-warning-bg)] p-2 text-xs text-[var(--color-state-warning-text)]">
|
||||
<span className="font-mono">{issue.entry_id}</span>
|
||||
<span> references missing morphology </span>
|
||||
<span className="font-mono">{issue.morphology_id}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AlignmentIssue({ issue }: { issue: LogosAlignmentTargetIssue }) {
|
||||
return (
|
||||
<div className="rounded border border-[var(--color-state-warning-border)] bg-[var(--color-state-warning-bg)] p-2 text-xs text-[var(--color-state-warning-text)]">
|
||||
<span className="font-mono">{issue.edge_id}</span>
|
||||
<span> invalid target </span>
|
||||
<span className="font-mono">{issue.target_id}</span>
|
||||
<span> via {issue.relation}</span>
|
||||
{issue.target_pack_id ? (
|
||||
<span>
|
||||
{" "}
|
||||
in <span className="font-mono">{issue.target_pack_id}</span>
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SafetyTab({ report }: { report: LogosSafetyReport }) {
|
||||
const epistemicRows = Object.entries(report.epistemic_status_counts).sort(([a], [b]) =>
|
||||
a.localeCompare(b),
|
||||
);
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<MetadataTable
|
||||
rows={[
|
||||
{
|
||||
key: "verdict",
|
||||
value: <SafetyVerdictBadge value={safetyVerdict(report.verdict)} />,
|
||||
},
|
||||
{
|
||||
key: "checksum",
|
||||
value: <SafetyVerdictBadge value={safetyVerdict(report.checksum_status)} />,
|
||||
},
|
||||
{
|
||||
key: "domain_contract",
|
||||
value: (
|
||||
<SafetyVerdictBadge value={safetyVerdict(report.domain_contract_status)} />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "missing_holonomy_refs",
|
||||
value: (
|
||||
<SafetyVerdictBadge value={safetyVerdict(report.missing_holonomy_refs)} />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "OOV policy",
|
||||
value: report.oov_policy_ok ? (
|
||||
<StatusPill tone="neutral">ok</StatusPill>
|
||||
) : (
|
||||
<StatusPill tone="danger">failed</StatusPill>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "gate policy",
|
||||
value: report.gate_policy_ok ? (
|
||||
<StatusPill tone="neutral">ok</StatusPill>
|
||||
) : (
|
||||
<StatusPill tone="danger">failed</StatusPill>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "path safety",
|
||||
value: report.path_safety_ok ? (
|
||||
<StatusPill tone="neutral">ok</StatusPill>
|
||||
) : (
|
||||
<StatusPill tone="danger">failed</StatusPill>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<section className="grid gap-3 md:grid-cols-2">
|
||||
<IssueList
|
||||
title="Checksum errors"
|
||||
empty="none recorded"
|
||||
items={report.checksum_errors.map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
className="rounded border border-[var(--color-state-danger-border)] bg-[var(--color-state-danger-bg)] p-2 text-xs text-[var(--color-state-danger-text)]"
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
/>
|
||||
<IssueList
|
||||
title="Known gaps"
|
||||
empty="none recorded"
|
||||
items={report.known_gaps.map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
className="rounded border border-[var(--color-state-warning-border)] bg-[var(--color-state-warning-bg)] p-2 text-xs text-[var(--color-state-warning-text)]"
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
/>
|
||||
<IssueList
|
||||
title="Dangling morphology links"
|
||||
empty="none recorded"
|
||||
items={report.dangling_morphology_links.map((issue) => (
|
||||
<MorphologyIssue key={`${issue.entry_id}:${issue.morphology_id}`} issue={issue} />
|
||||
))}
|
||||
/>
|
||||
<IssueList
|
||||
title="Invalid alignment targets"
|
||||
empty="none recorded"
|
||||
items={report.invalid_alignment_targets.map((issue) => (
|
||||
<AlignmentIssue key={issue.edge_id} issue={issue} />
|
||||
))}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-3 md:grid-cols-2">
|
||||
<div className="rounded-md border border-[var(--color-border-subtle)] p-3">
|
||||
<h3 className="m-0 mb-2 text-xs font-semibold text-[var(--color-text-secondary)]">
|
||||
Epistemic counts
|
||||
</h3>
|
||||
{epistemicRows.length === 0 ? (
|
||||
<p className="m-0 text-sm text-[var(--color-text-secondary)]">none recorded</p>
|
||||
) : (
|
||||
<MetadataTable
|
||||
rows={epistemicRows.map(([key, value]) => ({
|
||||
key,
|
||||
value: String(value),
|
||||
mono: true,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-md border border-[var(--color-border-subtle)] p-3">
|
||||
<h3 className="m-0 mb-2 text-xs font-semibold text-[var(--color-text-secondary)]">
|
||||
Epistemic entry sets
|
||||
</h3>
|
||||
<MetadataTable
|
||||
rows={[
|
||||
{
|
||||
key: "speculative",
|
||||
value: String(report.speculative_entries.length),
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
key: "contested",
|
||||
value: String(report.contested_entries.length),
|
||||
mono: true,
|
||||
},
|
||||
{
|
||||
key: "falsified",
|
||||
value: String(report.falsified_entries.length),
|
||||
mono: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusStrip({
|
||||
selectedPackId,
|
||||
overview,
|
||||
safety,
|
||||
}: {
|
||||
selectedPackId: string | null;
|
||||
overview?: LogosPackOverview;
|
||||
safety?: LogosSafetyReport;
|
||||
}) {
|
||||
const selected = overview?.pack_id ?? selectedPackId ?? "no pack selected";
|
||||
const checksum = safety?.checksum_status ?? "unknown";
|
||||
const gateOov = overview
|
||||
? `gate ${overview.gate_engaged ? "engaged" : "not engaged"} / OOV ${
|
||||
overview.oov_policy ?? "unknown"
|
||||
}`
|
||||
: "gate/OOV unknown";
|
||||
return (
|
||||
<footer className="mt-3 flex flex-wrap items-center gap-2 border-t border-[var(--color-border-subtle)] pt-2 text-xs text-[var(--color-text-secondary)]">
|
||||
<span className="font-mono text-[var(--color-text-primary)]">{selected}</span>
|
||||
<span aria-hidden>·</span>
|
||||
<span>checksum {checksum}</span>
|
||||
<span aria-hidden>·</span>
|
||||
<span>{gateOov}</span>
|
||||
<span aria-hidden>·</span>
|
||||
<span>proposal mode: none — read-only</span>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
function Workspace({
|
||||
selectedPackId,
|
||||
overview,
|
||||
overviewLoading,
|
||||
overviewError,
|
||||
safety,
|
||||
safetyLoading,
|
||||
safetyError,
|
||||
}: {
|
||||
selectedPackId: string | null;
|
||||
overview?: LogosPackOverview;
|
||||
overviewLoading: boolean;
|
||||
overviewError: unknown;
|
||||
safety?: LogosSafetyReport;
|
||||
safetyLoading: boolean;
|
||||
safetyError: unknown;
|
||||
}) {
|
||||
const [activeTab, setActiveTab] = useState("overview");
|
||||
|
||||
if (selectedPackId === null) {
|
||||
return (
|
||||
<EmptyState
|
||||
statement="Select a CORE-Logos pack to inspect overview, identity, and safety evidence."
|
||||
nextAction={{ kind: "cli", command: "core pack validate <path>" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (overviewLoading) {
|
||||
return <LoadingState label="Loading CORE-Logos pack..." />;
|
||||
}
|
||||
|
||||
if (overviewError) {
|
||||
return (
|
||||
<ErrorState
|
||||
whatFailed={errorMessage(overviewError)}
|
||||
mutationStatus="No Logos mutation occurred."
|
||||
reproducer={`curl /logos/packs/${selectedPackId}`}
|
||||
retrySafety="Retry: safe"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!overview) return null;
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title={overview.pack_id}
|
||||
toolbar={<SafetyVerdictBadge value={safetyVerdict(overview.safety_status)} />}
|
||||
>
|
||||
<TabBar tabs={LOGOS_TABS} activeTab={activeTab} onTabChange={setActiveTab}>
|
||||
{activeTab === "overview" ? <OverviewTab overview={overview} /> : null}
|
||||
{activeTab === "identity" ? <IdentityTab overview={overview} /> : null}
|
||||
{activeTab === "safety" ? (
|
||||
safetyLoading ? (
|
||||
<LoadingState label="Loading CORE-Logos safety..." />
|
||||
) : safetyError ? (
|
||||
<ErrorState
|
||||
whatFailed={errorMessage(safetyError)}
|
||||
mutationStatus="No Logos mutation occurred."
|
||||
reproducer={`curl /logos/packs/${selectedPackId}/safety`}
|
||||
retrySafety="Retry: safe"
|
||||
/>
|
||||
) : safety ? (
|
||||
<SafetyTab report={safety} />
|
||||
) : null
|
||||
) : null}
|
||||
</TabBar>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
export function LogosRoute() {
|
||||
const { logosPackId } = useParams();
|
||||
const selectedPackId = logosPackId && logosPackId.length > 0 ? logosPackId : null;
|
||||
const navigate = useNavigate();
|
||||
const { setSubject } = useEvidenceSubject();
|
||||
|
||||
const packsQuery = useLogosPacks();
|
||||
const overviewQuery = useLogosPackOverview(selectedPackId);
|
||||
const safetyQuery = useLogosPackSafety(selectedPackId);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedPackId === null) return;
|
||||
setSubject({
|
||||
kind: "logos_pack",
|
||||
packId: selectedPackId,
|
||||
data: overviewQuery.data
|
||||
? {
|
||||
...overviewQuery.data,
|
||||
checksum_status: safetyQuery.data?.checksum_status ?? null,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}, [selectedPackId, setSubject, overviewQuery.data, safetyQuery.data]);
|
||||
|
||||
function selectPack(pack: LogosPackSummary) {
|
||||
const subject = { kind: "logos_pack" as const, packId: pack.pack_id };
|
||||
const path = subjectToUrl(subject);
|
||||
navigate(path, { replace: true });
|
||||
pushRecentItem({ label: pack.pack_id, path });
|
||||
}
|
||||
|
||||
if (packsQuery.isLoading) {
|
||||
return <LoadingState label="Loading CORE-Logos packs..." />;
|
||||
}
|
||||
|
||||
if (packsQuery.isError) {
|
||||
return (
|
||||
<ErrorState
|
||||
whatFailed={errorMessage(packsQuery.error)}
|
||||
mutationStatus="No Logos mutation occurred."
|
||||
reproducer="curl /logos/packs"
|
||||
retrySafety="Retry: safe"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const packs = packsQuery.data ?? [];
|
||||
if (packs.length === 0) {
|
||||
return (
|
||||
<EmptyState
|
||||
statement="No CORE-Logos packs discovered."
|
||||
nextAction={{ kind: "cli", command: "core pack validate <path>" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
<div className="min-h-0 flex-1">
|
||||
<SplitPane direction="horizontal" id="logos" defaultSplit={34} minSize={300}>
|
||||
<PackUniverse
|
||||
packs={packs}
|
||||
selectedPackId={selectedPackId}
|
||||
onSelect={selectPack}
|
||||
/>
|
||||
<section className="h-full min-h-0 overflow-y-auto pl-3">
|
||||
<Workspace
|
||||
selectedPackId={selectedPackId}
|
||||
overview={overviewQuery.data}
|
||||
overviewLoading={overviewQuery.isLoading}
|
||||
overviewError={overviewQuery.isError ? overviewQuery.error : null}
|
||||
safety={safetyQuery.data}
|
||||
safetyLoading={safetyQuery.isLoading}
|
||||
safetyError={safetyQuery.isError ? safetyQuery.error : null}
|
||||
/>
|
||||
</section>
|
||||
</SplitPane>
|
||||
</div>
|
||||
<StatusStrip
|
||||
selectedPackId={selectedPackId}
|
||||
overview={overviewQuery.data}
|
||||
safety={safetyQuery.data}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import { ReplayRoute } from "./replay/ReplayRoute";
|
|||
import { DemoTheaterRoute } from "./demos/DemoTheaterRoute";
|
||||
import { RunsRoute } from "./runs/RunsRoute";
|
||||
import { PacksRoute } from "./packs/PacksRoute";
|
||||
import { LogosRoute } from "./logos/LogosRoute";
|
||||
import { VaultRoute } from "./vault/VaultRoute";
|
||||
import { CalibrationRoute } from "./calibration/CalibrationRoute";
|
||||
import { SettingsRoute } from "./settings/SettingsRoute";
|
||||
|
|
@ -194,6 +195,15 @@ const MOUNT_ROUTES: MountRouteSpec[] = [
|
|||
emptyStatement: "No packs discovered.",
|
||||
emptyCommand: "core pack validate <path>",
|
||||
},
|
||||
{
|
||||
name: "CORE-Logos",
|
||||
element: <LogosRoute />,
|
||||
path: "/logos/:logosPackId?",
|
||||
initialEntry: "/logos",
|
||||
loadingLabel: "Loading CORE-Logos packs...",
|
||||
emptyStatement: "No CORE-Logos packs discovered.",
|
||||
emptyCommand: "core pack validate <path>",
|
||||
},
|
||||
{
|
||||
name: "Vault",
|
||||
element: <VaultRoute />,
|
||||
|
|
|
|||
|
|
@ -235,6 +235,19 @@ export const WORKBENCH_ROUTES: readonly WorkbenchRoute[] = [
|
|||
keyboardDigit: "7",
|
||||
routeConformanceRequired: true,
|
||||
},
|
||||
{
|
||||
id: "logos",
|
||||
path: "/logos",
|
||||
routePattern: "logos/:logosPackId?",
|
||||
label: "CORE-Logos",
|
||||
description: "Inspect CORE-Logos pack identity and safety.",
|
||||
section: "Substrate",
|
||||
leftNavVisible: true,
|
||||
commandPaletteVisible: true,
|
||||
landingRouteAllowed: true,
|
||||
keyboardDigit: null,
|
||||
routeConformanceRequired: true,
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
path: "/settings",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
groundingSourceMeta,
|
||||
normativeClearanceMeta,
|
||||
reviewStateMeta,
|
||||
safetyVerdictMeta,
|
||||
} from "./mappings";
|
||||
|
||||
function expectExactCoverage(name: string, engineValues: string[], uiValues: string[]) {
|
||||
|
|
@ -43,4 +44,12 @@ describe("build-time enum coverage", () => {
|
|||
Object.keys(groundingSourceMeta),
|
||||
);
|
||||
});
|
||||
|
||||
it("tracks every ratified SafetyVerdict value exactly once", () => {
|
||||
expectExactCoverage(
|
||||
"SafetyVerdict",
|
||||
snapshot.SafetyVerdict,
|
||||
Object.keys(safetyVerdictMeta),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
groundingSourceMeta,
|
||||
normativeClearanceMeta,
|
||||
reviewStateMeta,
|
||||
safetyVerdictMeta,
|
||||
} from "./mappings";
|
||||
import { InfoBadge } from "./Badge";
|
||||
import {
|
||||
|
|
@ -10,6 +11,7 @@ import {
|
|||
GroundingSource,
|
||||
NormativeClearance,
|
||||
ReviewState,
|
||||
SafetyVerdict,
|
||||
} from "./types";
|
||||
|
||||
export {
|
||||
|
|
@ -17,6 +19,7 @@ export {
|
|||
GroundingSource,
|
||||
NormativeClearance,
|
||||
ReviewState,
|
||||
SafetyVerdict,
|
||||
};
|
||||
|
||||
export function EpistemicStateBadge({ value }: { value: EpistemicState }) {
|
||||
|
|
@ -35,6 +38,10 @@ export function GroundingSourceBadge({ value }: { value: GroundingSource }) {
|
|||
return <InfoBadge {...groundingSourceMeta[value]} />;
|
||||
}
|
||||
|
||||
export function SafetyVerdictBadge({ value }: { value: SafetyVerdict }) {
|
||||
return <InfoBadge {...safetyVerdictMeta[value]} />;
|
||||
}
|
||||
|
||||
export function TraceHashBadge({
|
||||
value,
|
||||
truncate = 12,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
GroundingSource,
|
||||
NormativeClearance,
|
||||
ReviewState,
|
||||
SafetyVerdict,
|
||||
type BadgeMeta,
|
||||
} from "./types";
|
||||
|
||||
|
|
@ -46,3 +47,10 @@ export const groundingSourceMeta = {
|
|||
[GroundingSource.OOV]: { label: "OOV", colorToken: "--color-grounding-oov", meaning: "Out-of-vocabulary grounding was encountered.", adr: "ADR-0160 / ADR-0162", evidence: "grounding_source is oov." },
|
||||
[GroundingSource.NONE]: { label: "None", colorToken: "--color-grounding-none", meaning: "No grounding source was present.", adr: "ADR-0160 / ADR-0162", evidence: "grounding_source is none." },
|
||||
} satisfies BadgeMeta<GroundingSource>;
|
||||
|
||||
export const safetyVerdictMeta = {
|
||||
[SafetyVerdict.CLEAR]: { label: "Clear", colorToken: "--color-state-verified", meaning: "The reported Logos safety check has no recorded warning or failure.", adr: "ADR-0162 / CORE-Logos LG-2", evidence: "SafetyVerdict is clear." },
|
||||
[SafetyVerdict.WARNING]: { label: "Warning", colorToken: "--color-state-warning-text", meaning: "The reported Logos safety check found reviewable gaps or link issues; this is not clear.", adr: "ADR-0162 / CORE-Logos LG-2", evidence: "SafetyVerdict is warning." },
|
||||
[SafetyVerdict.FAILED]: { label: "Failed", colorToken: "--color-state-danger-text", meaning: "The reported Logos safety check failed a blocking contract.", adr: "ADR-0162 / CORE-Logos LG-2", evidence: "SafetyVerdict is failed." },
|
||||
[SafetyVerdict.UNKNOWN]: { label: "Unknown", colorToken: "--color-state-undetermined", meaning: "The reported Logos safety check lacks proof evidence; this is not clear.", adr: "ADR-0162 / CORE-Logos LG-2", evidence: "SafetyVerdict is unknown." },
|
||||
} satisfies BadgeMeta<SafetyVerdict>;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,13 @@ export enum GroundingSource {
|
|||
NONE = "none",
|
||||
}
|
||||
|
||||
export enum SafetyVerdict {
|
||||
CLEAR = "clear",
|
||||
WARNING = "warning",
|
||||
FAILED = "failed",
|
||||
UNKNOWN = "unknown",
|
||||
}
|
||||
|
||||
export type BadgeMeta<T extends string> = Record<
|
||||
T,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -52,11 +52,11 @@ describe("CommandPalette keyboard contract", () => {
|
|||
expect(screen.getByRole("button", { name: "Open Trace" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Open Replay" })).toBeInTheDocument();
|
||||
|
||||
// One Navigate command per palette-visible route (14), derived from the
|
||||
// route registry — Demos, Calibration, Contemplation, and Tour are included
|
||||
// (the prior hand-maintained list of 10 dropped them).
|
||||
// One Navigate command per palette-visible route (15), derived from the
|
||||
// route registry — Demos, Calibration, Contemplation, Tour, and CORE-Logos
|
||||
// are included (the prior hand-maintained list of 10 dropped them).
|
||||
const items = dialog.querySelectorAll('[role="option"]');
|
||||
expect(items.length).toBe(14);
|
||||
expect(items.length).toBe(15);
|
||||
const lastIndex = items.length - 1;
|
||||
|
||||
// Initially first item (index 0) is focused — check aria-selected
|
||||
|
|
|
|||
|
|
@ -21,20 +21,7 @@ import { describe, expect, it } from "vitest";
|
|||
|
||||
// Shrink-only debt: backend schemas shipped ahead of their TS mirror. A class
|
||||
// gaining a TS interface while still listed here fails the gate.
|
||||
// CORE-Logos read models (LG-1): backend readers shipped without the /logos
|
||||
// frontend route; LG-2 adds the TS interfaces and removes each entry here.
|
||||
const NOT_YET_MIRRORED = new Set<string>([
|
||||
"LogosPackSummary",
|
||||
"LogosPackOverview",
|
||||
"LogosPackContents",
|
||||
"LogosLexiconRow",
|
||||
"LogosGlossRow",
|
||||
"LogosMorphologyRow",
|
||||
"LogosAlignmentRow",
|
||||
"LogosMorphologyLinkIssue",
|
||||
"LogosAlignmentTargetIssue",
|
||||
"LogosSafetyReport",
|
||||
]);
|
||||
const NOT_YET_MIRRORED = new Set<string>([]);
|
||||
|
||||
const UI_ROOT = join(__dirname, "..", "..", "..");
|
||||
const snapshot: Record<string, string[]> = JSON.parse(
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ export interface AuditEvent {
|
|||
}
|
||||
|
||||
export type PackSource = "language_pack" | "runtime_pack";
|
||||
export type SafetyVerdict = "clear" | "warning" | "failed" | "unknown";
|
||||
|
||||
export interface PackSummary {
|
||||
pack_id: string;
|
||||
|
|
@ -320,6 +321,130 @@ export interface PackDetail extends PackSummary {
|
|||
manifest: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface LogosPackSummary {
|
||||
pack_id: string;
|
||||
language: string | null;
|
||||
role: string | null;
|
||||
script: string | null;
|
||||
version: string | null;
|
||||
determinism_class: string | null;
|
||||
gate_engaged: boolean;
|
||||
oov_policy: string | null;
|
||||
lexicon_count: number;
|
||||
gloss_count: number;
|
||||
morphology_count: number;
|
||||
frame_count: number;
|
||||
composition_count: number;
|
||||
alignment_edge_count: number;
|
||||
holonomy_case_count: number;
|
||||
safety_status: SafetyVerdict;
|
||||
manifest_digest: string;
|
||||
manifest_path: string;
|
||||
}
|
||||
|
||||
export interface LogosPackOverview extends LogosPackSummary {
|
||||
schema_version: "logos_pack_overview_v1";
|
||||
normalization_policy: string | null;
|
||||
source_manifest: string | null;
|
||||
known_gaps: string[];
|
||||
}
|
||||
|
||||
export interface LogosLexiconRow {
|
||||
entry_id: string;
|
||||
surface: string;
|
||||
lemma: string;
|
||||
language: string;
|
||||
part_of_speech: string | null;
|
||||
pos: string | null;
|
||||
morphology_id: string | null;
|
||||
morphology_tags: string[];
|
||||
semantic_domains: string[];
|
||||
provenance_ids: string[];
|
||||
epistemic_status: string;
|
||||
}
|
||||
|
||||
export interface LogosGlossRow {
|
||||
gloss_id: string;
|
||||
lemma: string;
|
||||
gloss: string;
|
||||
pos: string | null;
|
||||
entry_ids: string[];
|
||||
provenance_ids: string[];
|
||||
epistemic_status: string | null;
|
||||
raw: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface LogosMorphologyRow {
|
||||
morphology_id: string;
|
||||
surface: string;
|
||||
lemma: string;
|
||||
language: string;
|
||||
root: string | null;
|
||||
prefix_chain: string[];
|
||||
stem: string | null;
|
||||
inflection: Record<string, string>;
|
||||
suffix_chain: string[];
|
||||
}
|
||||
|
||||
export interface LogosAlignmentRow {
|
||||
edge_id: string;
|
||||
source_id: string;
|
||||
target_id: string;
|
||||
relation: string;
|
||||
weight: number;
|
||||
evidence_ids: string[];
|
||||
target_pack_id: string | null;
|
||||
target_resolved: boolean;
|
||||
invalid_target: boolean;
|
||||
}
|
||||
|
||||
export interface LogosPackContents {
|
||||
schema_version: "logos_pack_contents_v1";
|
||||
pack_id: string;
|
||||
manifest: Record<string, unknown>;
|
||||
lexicon: LogosLexiconRow[];
|
||||
glosses: LogosGlossRow[];
|
||||
morphology: LogosMorphologyRow[];
|
||||
frames: Record<string, unknown>[];
|
||||
compositions: Record<string, unknown>[];
|
||||
alignment_edges: LogosAlignmentRow[];
|
||||
holonomy_cases: Record<string, unknown>[];
|
||||
}
|
||||
|
||||
export interface LogosMorphologyLinkIssue {
|
||||
entry_id: string;
|
||||
morphology_id: string;
|
||||
}
|
||||
|
||||
export interface LogosAlignmentTargetIssue {
|
||||
edge_id: string;
|
||||
source_id: string;
|
||||
target_id: string;
|
||||
relation: string;
|
||||
target_pack_id: string | null;
|
||||
}
|
||||
|
||||
export interface LogosSafetyReport {
|
||||
schema_version: "logos_safety_report_v1";
|
||||
pack_id: string;
|
||||
checksum_status: SafetyVerdict;
|
||||
checksum_errors: string[];
|
||||
domain_contract: Record<string, unknown>;
|
||||
domain_contract_status: SafetyVerdict;
|
||||
oov_policy_ok: boolean;
|
||||
gate_policy_ok: boolean;
|
||||
path_safety_ok: boolean;
|
||||
dangling_morphology_links: LogosMorphologyLinkIssue[];
|
||||
invalid_alignment_targets: LogosAlignmentTargetIssue[];
|
||||
missing_holonomy_refs: SafetyVerdict;
|
||||
epistemic_status_counts: Record<string, number>;
|
||||
speculative_entries: string[];
|
||||
contested_entries: string[];
|
||||
falsified_entries: string[];
|
||||
known_gaps: string[];
|
||||
verdict: SafetyVerdict;
|
||||
}
|
||||
|
||||
export type ArtifactKind =
|
||||
| "trace"
|
||||
| "eval_result"
|
||||
|
|
|
|||
Loading…
Reference in a new issue