@hugpy/video-intelligence-ui 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/package.json +1 -3
  2. package/src/config.ts +36 -0
  3. package/src/demo/assets/generate/cinema-cavern/movie.mp4 +0 -0
  4. package/src/demo/assets/generate/cinema-cavern/seg_00.mp4 +0 -0
  5. package/src/demo/assets/generate/clips/cat-lounge.mp4 +0 -0
  6. package/src/demo/assets/generate/clips/drone-city.mp4 +0 -0
  7. package/src/demo/assets/generate/clips/paper-sails.mp4 +0 -0
  8. package/src/demo/assets/generate/movie-galaxy/movie.mp4 +0 -0
  9. package/src/demo/assets/generate/movie-galaxy/seg_00.mp4 +0 -0
  10. package/src/demo/assets/generate/movie-galaxy/seg_01.mp4 +0 -0
  11. package/src/demo/assets/generate/movie-galaxy/seg_02.mp4 +0 -0
  12. package/src/demo/assets/generate/movie-galaxy/seg_03.mp4 +0 -0
  13. package/src/demo/assets/generate/movie-medow/movie.mp4 +0 -0
  14. package/src/demo/assets/generate/movie-medow/seg_00.mp4 +0 -0
  15. package/src/demo/assets/generate/movie-medow/seg_01.mp4 +0 -0
  16. package/src/demo/assets/generate/scene-desert/frame_00000.png +0 -0
  17. package/src/demo/assets/generate/scene-desert/frame_00001.png +0 -0
  18. package/src/demo/assets/generate/scene-desert/frame_00002.png +0 -0
  19. package/src/demo/assets/generate/scene-desert/frame_00003.png +0 -0
  20. package/src/demo/assets/generate/scene-desert/frame_00004.png +0 -0
  21. package/src/demo/assets/generate/scene-desert/frame_00005.png +0 -0
  22. package/src/demo/assets/generate/scene-desert/scene.mp4 +0 -0
  23. package/src/demo/assets/generate/scene-vintage/frame_00000.png +0 -0
  24. package/src/demo/assets/generate/scene-vintage/frame_00001.png +0 -0
  25. package/src/demo/assets/generate/scene-vintage/frame_00002.png +0 -0
  26. package/src/demo/assets/generate/scene-vintage/frame_00003.png +0 -0
  27. package/src/demo/assets/generate/scene-vintage/frame_00004.png +0 -0
  28. package/src/demo/assets/generate/scene-vintage/frame_00005.png +0 -0
  29. package/src/demo/assets/generate/scene-vintage/scene.mp4 +0 -0
  30. package/src/demo/assets/generate2/s0.png +0 -0
  31. package/src/demo/assets/generate2/s1.png +0 -0
  32. package/src/demo/assets/generate2/s10.png +0 -0
  33. package/src/demo/assets/generate2/s11.png +0 -0
  34. package/src/demo/assets/generate2/s12.png +0 -0
  35. package/src/demo/assets/generate2/s13.png +0 -0
  36. package/src/demo/assets/generate2/s14.png +0 -0
  37. package/src/demo/assets/generate2/s15.png +0 -0
  38. package/src/demo/assets/generate2/s16.png +0 -0
  39. package/src/demo/assets/generate2/s17.png +0 -0
  40. package/src/demo/assets/generate2/s18.png +0 -0
  41. package/src/demo/assets/generate2/s19.png +0 -0
  42. package/src/demo/assets/generate2/s2.png +0 -0
  43. package/src/demo/assets/generate2/s20.png +0 -0
  44. package/src/demo/assets/generate2/s21.mp4 +0 -0
  45. package/src/demo/assets/generate2/s22.mp4 +0 -0
  46. package/src/demo/assets/generate2/s23.mp4 +0 -0
  47. package/src/demo/assets/generate2/s24.mp4 +0 -0
  48. package/src/demo/assets/generate2/s25.mp4 +0 -0
  49. package/src/demo/assets/generate2/s26.mp4 +0 -0
  50. package/src/demo/assets/generate2/s27.mp4 +0 -0
  51. package/src/demo/assets/generate2/s28.mp4 +0 -0
  52. package/src/demo/assets/generate2/s29.mp4 +0 -0
  53. package/src/demo/assets/generate2/s3.png +0 -0
  54. package/src/demo/assets/generate2/s30.mp4 +0 -0
  55. package/src/demo/assets/generate2/s4.png +0 -0
  56. package/src/demo/assets/generate2/s5.png +0 -0
  57. package/src/demo/assets/generate2/s6.png +0 -0
  58. package/src/demo/assets/generate2/s7.png +0 -0
  59. package/src/demo/assets/generate2/s8.png +0 -0
  60. package/src/demo/assets/generate2/s9.png +0 -0
  61. package/src/demo/demoFetch.ts +126 -3
  62. package/src/demo/fixtures.ts +72 -4
  63. package/src/demo/promptAssist.ts +25 -0
  64. package/src/demo/sampleData.ts +960 -0
  65. package/src/demo/sampleSets.ts +172 -0
  66. package/src/stations/ActiveProcessesStation.tsx +18 -3
  67. package/src/stations/GenerateStation.tsx +901 -366
  68. package/src/stations/GoalComposer.tsx +9 -1
  69. package/src/stations/ScriptFirstStation.tsx +1081 -0
  70. package/src/stations/ToksPanel.tsx +188 -0
  71. package/src/stations/WorkbenchDrawer.tsx +4 -3
  72. package/src/stations/WorkbenchSidebar.tsx +6 -3
  73. package/src/stations/oracle/DagPanel.tsx +391 -0
  74. package/src/stations/oracle/OracleStation.tsx +304 -0
  75. package/src/stations/oracle/SpatialTab.tsx +185 -0
  76. package/src/stations/oracle/StewardPanel.tsx +165 -0
  77. package/src/stations/oracle/oracle.css +467 -0
  78. package/src/stations/oracle/oracleShared.tsx +160 -0
  79. package/src/stations/oracle/oracleTypes.ts +304 -0
  80. package/src/stations/oracle/useOracle.ts +560 -0
  81. package/src/stations/registry.ts +37 -0
  82. package/src/stations/scriptFirst.css +350 -0
  83. package/src/stations/studio/IdentitiesStation.tsx +219 -292
  84. package/src/stations/studio/IdentityCreatePanel.tsx +390 -0
  85. package/src/stations/studio/StudioGenerateTab.tsx +10 -17
  86. package/src/stations/studio/StudioMovieComposer.tsx +296 -59
  87. package/src/stations/studio/StudioPlane.tsx +35 -3
  88. package/src/stations/studio/StudioViewer.tsx +116 -14
  89. package/src/stations/studio/coordinationBadge.tsx +206 -0
  90. package/src/stations/studio/identityUpload.ts +66 -0
  91. package/src/stations/studio/promptCard.tsx +362 -59
  92. package/src/stations/studio/useIdentityCreateJob.ts +184 -0
  93. package/src/stations/studio/useIdentityProfiles.ts +66 -0
  94. package/src/stations/useScriptFirstRun.ts +596 -0
  95. package/src/style/app.css +157 -0
  96. package/src/video/LengthRow.tsx +189 -0
  97. package/src/video/coordinationReport.ts +266 -0
  98. package/src/video/jobClock.ts +104 -0
  99. package/src/video/spreadAssist.ts +26 -0
  100. package/src/video/useMediaJobs.ts +26 -0
  101. package/src/video/useOperatorHistory.ts +5 -0
  102. package/src/video/useToks.ts +193 -0
@@ -0,0 +1,304 @@
1
+ /**
2
+ * or-k18 / k114 — the Oracle station: the in-between of generation, made
3
+ * visible.
4
+ *
5
+ * 1. Steward — the selector's self-audit (findings by severity, matrix_stale
6
+ * banner, last run time, re-run → POST).
7
+ * 2. Run picker — the performance jobs on the media bus (the same
8
+ * GET /video/jobs feed Active Processes reads), and the picked run's
9
+ * manifest from GET /video/jobs/<id> → result.movie.
10
+ * 3. Run header — run id, ok/gap, digests, registry version, receipts.
11
+ * 4. Tabs: DAG (node states + per-node drawer) · Spatial overlays (honest
12
+ * placeholder) · Ledger (central reliability ledger summary + catalog).
13
+ *
14
+ * Every object on this screen is shown with where it came from and a raw JSON
15
+ * fold-out; every absence is named (route missing, selector disabled, no dag
16
+ * block, no spatial conditioning) rather than left blank.
17
+ */
18
+ import { useState } from "react";
19
+
20
+ import type { StationSpec } from "../types";
21
+ import { DagPanel } from "./DagPanel";
22
+ import { SpatialTab } from "./SpatialTab";
23
+ import { StewardPanel } from "./StewardPanel";
24
+ import { GapNote, RawJson, StateChip, Tag, short, when } from "./oracleShared";
25
+ import {
26
+ useCapabilities,
27
+ useLedgerSummary,
28
+ usePerformanceJobs,
29
+ useRunManifest,
30
+ useSteward,
31
+ type RunManifestApi,
32
+ } from "./useOracle";
33
+ import "./oracle.css";
34
+
35
+ type TabId = "dag" | "spatial" | "ledger";
36
+
37
+ function RunPicker({ runs }: { runs: RunManifestApi }) {
38
+ const jobs = usePerformanceJobs();
39
+ const [manual, setManual] = useState("");
40
+ return (
41
+ <section className="vi-oracle-panel" aria-label="Runs">
42
+ <h3>
43
+ Runs
44
+ <span className="vi-oracle-sub">performance jobs on the media bus (GET /video/jobs?all=1) — pick one to read its manifest</span>
45
+ </h3>
46
+ <div className="vi-oracle-row">
47
+ <button type="button" className="vi-btn vi-btn-sm" onClick={jobs.refresh}>
48
+ Refresh list
49
+ </button>
50
+ <input
51
+ className="vi-oracle-grow"
52
+ placeholder="…or paste a job id"
53
+ value={manual}
54
+ onChange={(e) => setManual(e.target.value)}
55
+ onKeyDown={(e) => {
56
+ if (e.key === "Enter" && manual.trim()) runs.select(manual.trim());
57
+ }}
58
+ />
59
+ <button type="button" className="vi-btn vi-btn-sm" disabled={!manual.trim()} onClick={() => runs.select(manual.trim())}>
60
+ Open
61
+ </button>
62
+ {runs.jobId ? (
63
+ <button type="button" className="vi-btn vi-btn-sm vi-btn-ghost" onClick={() => runs.select(null)}>
64
+ Clear
65
+ </button>
66
+ ) : null}
67
+ </div>
68
+ <GapNote gap={jobs.gap} />
69
+ <div className="vi-oracle-runs" style={{ marginTop: "0.5rem" }}>
70
+ {jobs.jobs.map((j) => (
71
+ <button
72
+ key={j.id}
73
+ type="button"
74
+ className={`vi-btn vi-btn-sm${runs.jobId === j.id ? " vi-btn-on" : ""}`}
75
+ onClick={() => runs.select(j.id)}
76
+ title={`${j.name} · ${j.status}${j.runId ? ` · run ${j.runId}` : ""}`}
77
+ >
78
+ {short(j.id, 10)} · {j.status}
79
+ {j.runId ? ` · ${short(j.runId, 10)}` : ""}
80
+ </button>
81
+ ))}
82
+ {!jobs.jobs.length && !jobs.gap ? (
83
+ <span className="vi-knob-hint">
84
+ No performance jobs in the bus feed (live or recent terminal). Enqueue one via the oracle runner, or paste an
85
+ older job id.
86
+ </span>
87
+ ) : null}
88
+ </div>
89
+ </section>
90
+ );
91
+ }
92
+
93
+ function RunHeader({ runs }: { runs: RunManifestApi }) {
94
+ const m = runs.manifest;
95
+ const job = runs.job;
96
+ if (!runs.jobId) return null;
97
+ return (
98
+ <section className="vi-oracle-panel" aria-label="Run">
99
+ <h3>
100
+ Run
101
+ <span className="vi-oracle-sub">GET /video/jobs/{runs.jobId} → result.movie</span>
102
+ <button type="button" className="vi-btn vi-btn-sm" style={{ marginLeft: "auto" }} onClick={runs.refresh} disabled={runs.loading}>
103
+ {runs.loading ? "Reading…" : "Re-read"}
104
+ </button>
105
+ </h3>
106
+ <GapNote gap={runs.gap} />
107
+ {job && !m ? (
108
+ <p className="vi-oracle-gap" role="status">
109
+ Job <code>{job.job_id}</code> is <StateChip state={job.status ?? "unknown"} />
110
+ {job.result ? (
111
+ <> and its result carries no manifest (<code>result.movie</code> is empty){job.result.error ? <> — {job.result.error.code}: {job.result.error.message}</> : null}.</>
112
+ ) : (
113
+ <> with no result yet — the manifest is written at the terminal transition.</>
114
+ )}
115
+ {job.progress ? <RawJson label="live progress" value={job.progress} /> : null}
116
+ </p>
117
+ ) : null}
118
+ {m ? (
119
+ <>
120
+ <div className="vi-oracle-row">
121
+ <Tag kind={m.ok ? "ok" : "alarm"}>{m.ok ? "ok" : "not ok"}</Tag>
122
+ {m.stopped_after ? <Tag kind="warn">stopped after {m.stopped_after}</Tag> : null}
123
+ {job?.status ? <StateChip state={job.status} /> : null}
124
+ {m.dag ? <Tag kind="accent">ran on the DAG</Tag> : <Tag>linear recipe</Tag>}
125
+ {job?.result?.error ? (
126
+ <Tag kind="alarm">
127
+ {job.result.error.code}: {job.result.error.message}
128
+ </Tag>
129
+ ) : null}
130
+ </div>
131
+ <dl className="vi-oracle-kv" style={{ marginTop: "0.5rem" }}>
132
+ <dt>run id</dt>
133
+ <dd>
134
+ <code>{m.run_id}</code>
135
+ </dd>
136
+ <dt>registry version</dt>
137
+ <dd>
138
+ <code>{m.registry_version ?? "not recorded"}</code>
139
+ </dd>
140
+ <dt>goal / snapshot / lock</dt>
141
+ <dd>
142
+ <code>{short(m.goal_digest)}</code> · <code>{short(m.snapshot_digest)}</code> · <code>{short(m.lock_digest)}</code>
143
+ </dd>
144
+ <dt>segments</dt>
145
+ <dd>{m.segment_digests?.length ?? 0}</dd>
146
+ <dt>video</dt>
147
+ <dd>{m.video_ref ? <code title={m.video_ref}>{short(m.video_ref, 56)}</code> : "none accepted"}</dd>
148
+ <dt>receipts</dt>
149
+ <dd>
150
+ {(m.receipts ?? []).length ? (
151
+ <ul className="vi-oracle-reasons" style={{ marginTop: 0 }}>
152
+ {(m.receipts ?? []).map((r, i) => (
153
+ <li key={i}>
154
+ <code>{r.capability}</code> → <code>{r.model_id}</code>
155
+ {r.worker ? <> on {r.worker}</> : null} · {when(r.started_at)}
156
+ {typeof r.duration_s === "number" ? <> · {r.duration_s.toFixed(1)}s</> : null}
157
+ {r.failure ? <Tag kind="alarm">{r.failure}</Tag> : null}
158
+ </li>
159
+ ))}
160
+ </ul>
161
+ ) : (
162
+ <span className="vi-knob-hint">none in the manifest</span>
163
+ )}
164
+ </dd>
165
+ <dt>state path</dt>
166
+ <dd>
167
+ <code>{m.state_path ?? "—"}</code>
168
+ </dd>
169
+ </dl>
170
+ {m.gap ? (
171
+ <p className="vi-oracle-gap">
172
+ gap at stage <code>{String(m.gap.stage)}</code>: {String(m.gap.diagnosis ?? "")}
173
+ {m.gap.requirement ? <> — required: {String(m.gap.requirement)}</> : null}
174
+ </p>
175
+ ) : null}
176
+ {m.limitations?.length ? (
177
+ <ul className="vi-oracle-limits">
178
+ {m.limitations.map((l, i) => (
179
+ <li key={i}>{l}</li>
180
+ ))}
181
+ </ul>
182
+ ) : null}
183
+ {m.warnings?.length ? <RawJson label={`warnings (${m.warnings.length})`} value={m.warnings} /> : null}
184
+ <RawJson label="manifest json" value={m} />
185
+ </>
186
+ ) : null}
187
+ </section>
188
+ );
189
+ }
190
+
191
+ function LedgerTab() {
192
+ const ledger = useLedgerSummary();
193
+ const caps = useCapabilities();
194
+ return (
195
+ <>
196
+ <section className="vi-oracle-panel" aria-label="Ledger">
197
+ <h3>
198
+ Reliability ledger
199
+ <span className="vi-oracle-sub">GET /api/oracle/ledger/summary — the ONE record workers share</span>
200
+ <button type="button" className="vi-btn vi-btn-sm" style={{ marginLeft: "auto" }} onClick={ledger.refresh}>
201
+ Refresh
202
+ </button>
203
+ </h3>
204
+ <GapNote gap={ledger.gap} />
205
+ {ledger.summary ? (
206
+ <>
207
+ <div className="vi-oracle-row">
208
+ {ledger.summary.remote ? <Tag kind="accent">remote: {String(ledger.summary.remote)}</Tag> : <Tag>local ledger</Tag>}
209
+ {Object.entries(ledger.summary)
210
+ .filter(([k, v]) => typeof v === "number" && k !== "ok")
211
+ .map(([k, v]) => (
212
+ <span key={k}>
213
+ <Tag>
214
+ {k}: {String(v)}
215
+ </Tag>
216
+ </span>
217
+ ))}
218
+ </div>
219
+ <RawJson label="ledger summary json" value={ledger.summary} open />
220
+ </>
221
+ ) : !ledger.gap ? (
222
+ <p className="vi-oracle-note">Reading the ledger…</p>
223
+ ) : null}
224
+ </section>
225
+ <section className="vi-oracle-panel" aria-label="Capabilities">
226
+ <h3>
227
+ Capability catalog
228
+ <span className="vi-oracle-sub">GET /api/oracle/capabilities — registry {caps.caps?.registry_version ? <code>{short(caps.caps.registry_version)}</code> : "unknown"}</span>
229
+ </h3>
230
+ <GapNote gap={caps.gap} />
231
+ {caps.caps?.capabilities?.length ? (
232
+ <table className="vi-oracle-table">
233
+ <thead>
234
+ <tr>
235
+ <th>capability</th>
236
+ <th>eligible models</th>
237
+ </tr>
238
+ </thead>
239
+ <tbody>
240
+ {caps.caps.capabilities.map((c) => (
241
+ <tr key={c.name}>
242
+ <td>
243
+ <code>{c.name}</code>
244
+ </td>
245
+ <td>{c.model_ids?.length ? c.model_ids.map((m) => <code key={m} style={{ marginRight: "0.5rem" }}>{m}</code>) : <span className="vi-knob-hint">none eligible</span>}</td>
246
+ </tr>
247
+ ))}
248
+ </tbody>
249
+ </table>
250
+ ) : caps.caps && !caps.gap ? (
251
+ <p className="vi-oracle-note">The catalog answered with zero capabilities.</p>
252
+ ) : null}
253
+ {caps.caps ? <RawJson label="capabilities json" value={caps.caps} /> : null}
254
+ </section>
255
+ </>
256
+ );
257
+ }
258
+
259
+ export function OracleStation({ spec }: { spec: StationSpec }) {
260
+ const steward = useSteward();
261
+ const runs = useRunManifest();
262
+ const [tab, setTab] = useState<TabId>("dag");
263
+
264
+ const tabs: Array<{ id: TabId; label: string }> = [
265
+ { id: "dag", label: "DAG" },
266
+ { id: "spatial", label: "Spatial overlays" },
267
+ { id: "ledger", label: "Ledger + catalog" },
268
+ ];
269
+
270
+ return (
271
+ <section className="station-card station-wide vi-oracle" aria-label={spec.title}>
272
+ <p className="station-phase">{spec.phase}</p>
273
+ <p className="station-blurb">{spec.blurb}</p>
274
+
275
+ <StewardPanel api={steward} embedded={runs.manifest?.dag?.steward ?? null} />
276
+ <RunPicker runs={runs} />
277
+ <RunHeader runs={runs} />
278
+
279
+ <div>
280
+ <div className="vi-oracle-tabs" role="tablist" aria-label="Oracle views">
281
+ {tabs.map((t) => (
282
+ <button
283
+ key={t.id}
284
+ type="button"
285
+ role="tab"
286
+ id={`vi-oracle-tab-${t.id}`}
287
+ aria-selected={tab === t.id}
288
+ aria-controls={`vi-oracle-panel-${t.id}`}
289
+ className="vi-oracle-tab"
290
+ onClick={() => setTab(t.id)}
291
+ >
292
+ {t.label}
293
+ </button>
294
+ ))}
295
+ </div>
296
+ <div role="tabpanel" id={`vi-oracle-panel-${tab}`} aria-labelledby={`vi-oracle-tab-${tab}`}>
297
+ {tab === "dag" ? <DagPanel runs={runs} /> : null}
298
+ {tab === "spatial" ? <SpatialTab manifest={runs.manifest} /> : null}
299
+ {tab === "ledger" ? <LedgerTab /> : null}
300
+ </div>
301
+ </div>
302
+ </section>
303
+ );
304
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * or-k18 — "spatial overlays", the honest placeholder.
3
+ *
4
+ * The spatial layer (oracle/spatial.py SpatialSceneManifest, oracle/spatial_eval.py
5
+ * SpatialEvalReport) exists, and the DAG grows a `spatial:<seg>` node when a
6
+ * segment carries a manifest — but the evaluator is NOT yet folded into the run
7
+ * manifest, and most runs carry `SegmentSpec.spatial_ref = None`. So this tab
8
+ * does not draw overlays it cannot source. It walks the manifest for anything
9
+ * shaped like a spatial manifest / eval report / spatial_manifest ref and prints
10
+ * what it finds with the JSON path it found it at; otherwise it says "no spatial
11
+ * conditioning" and quotes the run's own limitation line when there is one.
12
+ */
13
+ import { useMemo } from "react";
14
+
15
+ import type { RunManifest } from "./oracleTypes";
16
+ import { findSpatial } from "./useOracle";
17
+ import { RawJson, Tag } from "./oracleShared";
18
+
19
+ function isRecord(v: unknown): v is Record<string, unknown> {
20
+ return typeof v === "object" && v !== null && !Array.isArray(v);
21
+ }
22
+
23
+ export function SpatialTab({ manifest }: { manifest: RunManifest | null }) {
24
+ const finds = useMemo(() => findSpatial(manifest), [manifest]);
25
+ const spatialNodes = useMemo(
26
+ () => Object.entries(manifest?.dag?.nodes ?? {}).filter(([id]) => id.startsWith("spatial:")),
27
+ [manifest],
28
+ );
29
+ const limitation = (manifest?.limitations ?? []).find((l) => /spatial/i.test(l)) ?? null;
30
+
31
+ if (!manifest) {
32
+ return (
33
+ <section className="vi-oracle-panel" aria-label="Spatial overlays">
34
+ <h3>
35
+ Spatial overlays
36
+ <span className="vi-oracle-sub">placeholder — renders spatial manifests + evaluator metrics when a run carries them</span>
37
+ </h3>
38
+ <p className="vi-oracle-note">Pick a performance run to look for spatial conditioning.</p>
39
+ </section>
40
+ );
41
+ }
42
+
43
+ const manifests = finds.filter((f) => f.kind === "manifest");
44
+ const evals = finds.filter((f) => f.kind === "eval");
45
+ const refs = finds.filter((f) => f.kind === "ref");
46
+
47
+ return (
48
+ <section className="vi-oracle-panel" aria-label="Spatial overlays">
49
+ <h3>
50
+ Spatial overlays
51
+ <span className="vi-oracle-sub">placeholder — no geometry is drawn here yet; the data is shown as it is</span>
52
+ </h3>
53
+
54
+ <div className="vi-oracle-row">
55
+ <Tag kind={manifests.length ? "accent" : undefined}>{manifests.length} spatial manifest(s)</Tag>
56
+ <Tag kind={evals.length ? "accent" : undefined}>{evals.length} evaluator report(s)</Tag>
57
+ <Tag>{refs.length} spatial_manifest ref(s) on segments</Tag>
58
+ <Tag>{spatialNodes.length} spatial:* DAG node(s)</Tag>
59
+ </div>
60
+
61
+ {!manifests.length && !evals.length && !refs.length && !spatialNodes.length ? (
62
+ <>
63
+ <p className="vi-oracle-gap" role="status">
64
+ No spatial conditioning on this run. {limitation ? <>The run says so itself: “{limitation}”</> : <>No SegmentSpec carried a <code>spatial_ref</code>, no <code>spatial:&lt;seg&gt;</code> node was built, and no SpatialSceneManifest or SpatialEvalReport appears anywhere in the manifest.</>}
65
+ </p>
66
+ <p className="vi-oracle-note">
67
+ When a run is conditioned, this tab will list each segment's manifest (coordinate system, camera, entities,
68
+ timebase, provenance) and the evaluator's per-rubric metrics with their codes and thresholds. Overlay
69
+ rendering on frames is not built; nothing here will pretend it is.
70
+ </p>
71
+ </>
72
+ ) : null}
73
+
74
+ {spatialNodes.length ? (
75
+ <>
76
+ <h4>spatial DAG nodes</h4>
77
+ <table className="vi-oracle-table">
78
+ <thead>
79
+ <tr>
80
+ <th>node</th>
81
+ <th>state</th>
82
+ </tr>
83
+ </thead>
84
+ <tbody>
85
+ {spatialNodes.map(([id, st]) => (
86
+ <tr key={id}>
87
+ <td>
88
+ <code>{id}</code>
89
+ </td>
90
+ <td>{String(st)}</td>
91
+ </tr>
92
+ ))}
93
+ </tbody>
94
+ </table>
95
+ <p className="vi-oracle-note">
96
+ A spatial node validates the segment's manifest against the lock and emits the conditioning request; its
97
+ outputs (conditioning, manifest_digest, validation) live in the journal and are not exported in the manifest.
98
+ </p>
99
+ </>
100
+ ) : null}
101
+
102
+ {refs.length ? (
103
+ <>
104
+ <h4>spatial_manifest refs</h4>
105
+ <ul className="vi-oracle-reasons">
106
+ {refs.map((f) => (
107
+ <li key={f.path}>
108
+ <code>{f.path}</code> → <code>{String(f.value)}</code>
109
+ </li>
110
+ ))}
111
+ </ul>
112
+ </>
113
+ ) : null}
114
+
115
+ {manifests.map((f) => {
116
+ const m = isRecord(f.value) ? f.value : {};
117
+ return (
118
+ <div key={f.path}>
119
+ <h4>
120
+ spatial manifest <span className="vi-knob-hint">at {f.path || "(root)"}</span>
121
+ </h4>
122
+ <dl className="vi-oracle-kv">
123
+ <dt>segment</dt>
124
+ <dd>{String(m.segment_id ?? "—")}</dd>
125
+ <dt>schema</dt>
126
+ <dd>{String(m.schema_version ?? "—")}</dd>
127
+ <dt>revision</dt>
128
+ <dd>{String(m.artifact_revision ?? "—")}</dd>
129
+ <dt>entities</dt>
130
+ <dd>{Array.isArray(m.entities) ? m.entities.length : "—"}</dd>
131
+ <dt>fallbacks</dt>
132
+ <dd>{Array.isArray(m.fallbacks) ? m.fallbacks.length : "—"}</dd>
133
+ </dl>
134
+ <RawJson label="manifest json" value={m} open />
135
+ </div>
136
+ );
137
+ })}
138
+
139
+ {evals.map((f) => {
140
+ const r = isRecord(f.value) ? f.value : {};
141
+ const metrics = Array.isArray(r.metrics) ? (r.metrics as Array<Record<string, unknown>>) : [];
142
+ return (
143
+ <div key={f.path}>
144
+ <h4>
145
+ evaluator report <span className="vi-knob-hint">at {f.path || "(root)"}</span>
146
+ </h4>
147
+ <div className="vi-oracle-row">
148
+ <Tag kind={r.ok ? "ok" : "alarm"}>{r.ok ? "ok" : "drift"}</Tag>
149
+ <Tag>segment {String(r.segment_id ?? "—")}</Tag>
150
+ <Tag>{Array.isArray(r.measured) ? r.measured.length : 0} measured</Tag>
151
+ <Tag>{Array.isArray(r.skipped) ? r.skipped.length : 0} skipped</Tag>
152
+ </div>
153
+ {metrics.length ? (
154
+ <table className="vi-oracle-table" style={{ marginTop: "0.5rem" }}>
155
+ <thead>
156
+ <tr>
157
+ <th>metric</th>
158
+ <th>value</th>
159
+ <th>threshold</th>
160
+ <th>code</th>
161
+ </tr>
162
+ </thead>
163
+ <tbody>
164
+ {metrics.map((m, i) => (
165
+ <tr key={i}>
166
+ <td>{String(m.name ?? m.rubric ?? i)}</td>
167
+ <td>
168
+ <code>{String(m.value ?? m.measured ?? "—")}</code>
169
+ </td>
170
+ <td>
171
+ <code>{String(m.threshold ?? "—")}</code>
172
+ </td>
173
+ <td>{m.code ? <Tag kind="warn">{String(m.code)}</Tag> : <span className="vi-knob-hint">—</span>}</td>
174
+ </tr>
175
+ ))}
176
+ </tbody>
177
+ </table>
178
+ ) : null}
179
+ <RawJson label="evaluator json" value={r} />
180
+ </div>
181
+ );
182
+ })}
183
+ </section>
184
+ );
185
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * or-k18 — the steward report: the system checking itself (k113c).
3
+ *
4
+ * GET /api/oracle/steward is report-only. The "Re-run" button POSTs, which
5
+ * re-audits AND applies bounded rebalancing to the live selector — the button
6
+ * says so, because "re-run" that silently changes policy would be a lie.
7
+ *
8
+ * Findings are grouped by severity (alarm → warn → info). A `matrix_stale`
9
+ * finding is promoted to its own banner regardless of severity: a stale
10
+ * routing matrix means every selection below is reasoning from old evidence.
11
+ */
12
+ import { useMemo } from "react";
13
+
14
+ import type { StewardFinding, StewardReport } from "./oracleTypes";
15
+ import type { StewardApi } from "./useOracle";
16
+ import { GapNote, RawJson, Tag, ago, when } from "./oracleShared";
17
+
18
+ const ORDER: Array<"alarm" | "warn" | "info"> = ["alarm", "warn", "info"];
19
+
20
+ function bucket(findings: StewardFinding[]): Record<string, StewardFinding[]> {
21
+ const out: Record<string, StewardFinding[]> = { alarm: [], warn: [], info: [], other: [] };
22
+ for (const f of findings) {
23
+ const k = ORDER.includes(f.severity as "alarm") ? f.severity : "other";
24
+ out[k].push(f);
25
+ }
26
+ return out;
27
+ }
28
+
29
+ function sevOf(f: StewardFinding): "alarm" | "warn" | "info" {
30
+ return (ORDER.includes(f.severity as "alarm") ? f.severity : "info") as "alarm" | "warn" | "info";
31
+ }
32
+
33
+ /** The BODY of one finding row. The `<li key>` wrapper stays at the call site —
34
+ * this project's scoped tsconfig rejects a `key` prop on a locally-declared
35
+ * component (the JobRow idiom in ActiveProcessesStation). */
36
+ function FindingBody({ f }: { f: StewardFinding }) {
37
+ return (
38
+ <>
39
+ <div className="vi-oracle-finding-head">
40
+ <Tag kind={sevOf(f)}>{f.severity}</Tag>
41
+ <Tag>{f.kind}</Tag>
42
+ {f.capability ? <code>{f.capability}</code> : <span className="vi-knob-hint">fleet-wide</span>}
43
+ </div>
44
+ <div style={{ marginTop: "0.25rem" }}>{f.message}</div>
45
+ {f.action ? <p className="vi-oracle-finding-action">action: {f.action}</p> : null}
46
+ {f.evidence && Object.keys(f.evidence).length ? <RawJson label="evidence" value={f.evidence} /> : null}
47
+ </>
48
+ );
49
+ }
50
+
51
+ export function StewardPanel({ api, embedded }: { api: StewardApi; embedded?: StewardReport | null }) {
52
+ const report = api.report;
53
+ const findings = useMemo(() => report?.findings ?? [], [report]);
54
+ const groups = useMemo(() => bucket(findings), [findings]);
55
+ const stale = findings.filter((f) => f.kind === "matrix_stale");
56
+ const busy = api.busy != null;
57
+
58
+ return (
59
+ <section className="vi-oracle-panel" aria-label="Steward report">
60
+ <h3>
61
+ Steward
62
+ <span className="vi-oracle-sub">the selector audited from its own ledger — GET /api/oracle/steward</span>
63
+ </h3>
64
+
65
+ <div className="vi-oracle-row">
66
+ <button type="button" className="vi-btn vi-btn-sm" disabled={busy} onClick={api.refresh}>
67
+ {api.busy === "get" ? "Reading…" : "Refresh report"}
68
+ </button>
69
+ <button
70
+ type="button"
71
+ className="vi-btn vi-btn-sm vi-btn-accent"
72
+ disabled={busy}
73
+ onClick={api.rerun}
74
+ title="POST /api/oracle/steward — re-audits AND applies bounded rebalancing to the live selection policy"
75
+ >
76
+ {api.busy === "post" ? "Re-running…" : "Re-run + apply rebalancing"}
77
+ </button>
78
+ <span className="vi-knob-hint">
79
+ last run (server): <code>{when(report?.at)}</code>
80
+ {api.fetchedAt != null ? <> · fetched {ago(api.fetchedAt)} (browser clock)</> : null}
81
+ </span>
82
+ </div>
83
+
84
+ <GapNote gap={api.gap} />
85
+
86
+ {report ? (
87
+ <>
88
+ <div className="vi-oracle-row" style={{ marginTop: "0.6rem" }}>
89
+ <Tag kind={report.ok ? "ok" : "alarm"}>{report.ok ? "no alarms" : "alarms present"}</Tag>
90
+ {report.applied ? <Tag kind="accent">policy applied this call</Tag> : <Tag>report only</Tag>}
91
+ {report.policy_changed ? <Tag kind="warn">policy changed</Tag> : null}
92
+ {typeof report.ledger_rows === "number" ? <Tag>ledger rows: {report.ledger_rows}</Tag> : null}
93
+ <Tag>
94
+ {groups.alarm.length} alarm · {groups.warn.length} warn · {groups.info.length} info
95
+ </Tag>
96
+ </div>
97
+ {report.summary ? <p className="vi-oracle-note">{report.summary}</p> : null}
98
+
99
+ {stale.length ? (
100
+ <div className="vi-oracle-stale" role="alert">
101
+ <strong>Routing matrix is stale.</strong> Selections below reason from old evidence.
102
+ <ul className="vi-oracle-reasons">
103
+ {stale.map((f, i) => (
104
+ <li key={i}>
105
+ {f.message}
106
+ {f.action ? <> — {f.action}</> : null}
107
+ </li>
108
+ ))}
109
+ </ul>
110
+ </div>
111
+ ) : null}
112
+
113
+ {ORDER.map((sev) =>
114
+ groups[sev].length ? (
115
+ <div key={sev}>
116
+ <h4>
117
+ {sev} ({groups[sev].length})
118
+ </h4>
119
+ <ul className="vi-oracle-findings">
120
+ {groups[sev].map((f, i) => (
121
+ <li key={`${sev}-${f.kind}-${f.capability ?? ""}-${i}`} className={`vi-oracle-finding vi-oracle-finding-${sevOf(f)}`}>
122
+ <FindingBody f={f} />
123
+ </li>
124
+ ))}
125
+ </ul>
126
+ </div>
127
+ ) : null,
128
+ )}
129
+ {groups.other.length ? (
130
+ <div>
131
+ <h4>unclassified severity ({groups.other.length})</h4>
132
+ <ul className="vi-oracle-findings">
133
+ {groups.other.map((f, i) => (
134
+ <li key={`o-${i}`} className="vi-oracle-finding vi-oracle-finding-info">
135
+ <FindingBody f={f} />
136
+ </li>
137
+ ))}
138
+ </ul>
139
+ </div>
140
+ ) : null}
141
+ {!findings.length ? (
142
+ <p className="vi-oracle-note">The steward returned zero findings — a clean fleet normally still says so with numbers; treat an empty list as "nothing audited".</p>
143
+ ) : null}
144
+
145
+ {report.selection_policy ? <RawJson label="selection policy (live)" value={report.selection_policy} /> : null}
146
+ {report.policy_after ? <RawJson label="policy after this call" value={report.policy_after} /> : null}
147
+ <RawJson label="steward report json" value={report} />
148
+ </>
149
+ ) : !api.gap ? (
150
+ <p className="vi-oracle-note">Reading the steward…</p>
151
+ ) : null}
152
+
153
+ {embedded ? (
154
+ <>
155
+ <h4>steward snapshot embedded in the selected run</h4>
156
+ <p className="vi-oracle-note">
157
+ This is the report the run itself recorded at the time (manifest.dag.steward), not the live one above.
158
+ {embedded.summary ? <> Summary: {embedded.summary}</> : null}
159
+ </p>
160
+ <RawJson label="manifest.dag.steward" value={embedded} />
161
+ </>
162
+ ) : null}
163
+ </section>
164
+ );
165
+ }