Demo/cockpit sovereign hud#8
Conversation
Nest CRK-1 provenance into agent receipts, append .nova/ledger.wal.jsonl, return blocked receipts on generate failures, and POST observe ingest when NOVA_OBSERVE/spine env is set. Co-authored-by: Cursor <cursoragent@cursor.com>
Wire Sovereign X HUD and CRVS v1 evidence bindings for the cockpit demo, with CRVS contract tests and CI typecheck job. Backend stewardship/ISL emitters remain out of this commit. Co-authored-by: Cursor <cursoragent@cursor.com>
Deploy cockpit/dist on push to main and demo/cockpit-sovereign-hud. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
| : [ | ||
| { id: "1", label: "Understand Request", status: "done" as const, pct: 100 }, | ||
| { id: "2", label: "Bind Evidence", status: "done" as const, pct: 100 }, | ||
| { id: "3", label: "Await Authority", status: "running" as const, pct: 63 }, | ||
| { id: "4", label: "Execute & Verify", status: "pending" as const, pct: 0 }, | ||
| ]; | ||
|
|
||
| const authStatus = String(authority?.authorityStatus ?? "awaiting evidence"); | ||
| const authOk = authStatus === "Verified"; | ||
| const phase = | ||
| Array.isArray(exec?.activeExecutions) && (exec!.activeExecutions as unknown[]).length | ||
| ? "EXECUTION" | ||
| : unresolved.length | ||
| ? "INTENT" | ||
| : authOk | ||
| ? "ARCHITECTURE" | ||
| : "PLANNING"; | ||
|
|
||
| const confidence = authOk ? 94 : 62; |
There was a problem hiding this comment.
🟡 Command panel shows made-up progress and confidence numbers with no supporting evidence
The central command view invents a fixed confidence figure (confidence = authOk ? 94 : 62 at cockpit/src/hud/CenterCommand.tsx:59) and a placeholder task checklist with hardcoded completion percentages when no live intent data exists, so the operator sees fabricated status instead of the honest "awaiting evidence" state used everywhere else.
Impact: The operator can be misled into believing work is progressing and trust a confidence level that was never computed from real system state.
Why this contradicts the panel's own evidence law
The rest of the HUD (cockpit/src/hud/HudRealityStrip.tsx, cockpit/src/crvs/ContractFields.tsx) deliberately renders "awaiting evidence" / "—" when a packet is missing, honoring the stated CRVS law ("Never fabricate evidence — bind to live state or show empty/pending provenance" in cockpit/src/crvs/README.md). In CenterCommand.tsx:41-46 the fallback checklist (Understand Request done 100%, Await Authority running 63%, etc.) and CenterCommand.tsx:59 confidence are static literals not derived from any evidence packet. Even the queue/unresolved branch assigns invented pct: 50 / pct: 0 values (CenterCommand.tsx:32,38). This is the exact fabrication the PR's anti-fabrication tests (tests/crvs.test.ts) were added to prevent.
Prompt for agents
CenterCommand.tsx fabricates constitutional/status facts, contradicting the CRVS 'never fabricate evidence' law that the rest of the HUD honors. Two problem spots: (1) the fallback checklist at lines 41-46 hardcodes task labels and completion percentages (e.g. 'Understand Request' done 100%, 'Await Authority' running 63%) when there is no live intent/queue data; (2) the confidence value at line 59 is a static literal (94 or 62) not derived from any evidence packet; the queue/unresolved mapping also assigns invented pct values (50/0). When no live evidence is available, the panel should show an 'awaiting evidence' / empty state and omit or blank the confidence figure, mirroring HudRealityStrip and ContractFields. Derive percentages only from real step statuses if present, otherwise render the pending/awaiting provenance state.
Was this helpful? React with 👍 or 👎 to provide feedback.
| for (const r of list) { | ||
| if (r?.id) store.actions.addReceipt(r); | ||
| } | ||
| if (list.length) requestEvidenceRefresh("rest:receipts"); |
There was a problem hiding this comment.
🟡 Receipt list grows without bound and duplicates when the live event stream is unavailable
When the server-sent event stream is down, the fallback poller re-fetches the full receipt list every few seconds and re-inserts every receipt (store.actions.addReceipt(r) at cockpit/src/bridge/WebSocketBridge.ts:145) with no de-duplication, so the same receipts pile up indefinitely.
Impact: In fallback/offline mode the cockpit accumulates duplicate receipts without limit, inflating the evidence/receipt counts shown in the new panels and steadily consuming memory.
Mechanism and interaction with the new CRVS bindings
pollReceipts runs on the 5s interval whenever SSE is unavailable (WebSocketBridge.ts:158, soft=false). Each run pulls the complete /api/receipts list and loops for (const r of list) { if (r?.id) store.actions.addReceipt(r); } (WebSocketBridge.ts:144-146). addReceipt in cockpit/src/state/store.ts:151-170 unconditionally prepends [r, ...s.governance.receipts] with no id check, so every poll appends the entire ledger again. The new CRVS bindings compute evidence directly from this array — MemoryEvidenceBinding uses receipts.length and receipts.slice(0,8).map(r=>r.id), EvidenceChainBinding and RealityBinding count/verify receipts — so duplicates corrupt receiptCount, evidenceScore and produce repeated shard/packet ids. With the newly durable WAL (agent/governance/ledger.ts) the server-side list also grows across restarts, amplifying the effect.
Prompt for agents
The REST fallback poller in WebSocketBridge.ts (pollReceipts, lines 134-151) re-adds the entire receipt list on every poll, and store.ts addReceipt (lines 151-170) does not de-duplicate by id, causing unbounded duplication in fallback mode. This corrupts the new CRVS evidence bindings that count receipts and list receipt ids. Fix by making addReceipt idempotent (skip if a receipt with the same id already exists, or replace it), or by having pollReceipts only add receipts whose ids are not already present in the store. Consider the timeline array in addReceipt too, which likewise accumulates duplicates.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "/api/receipts/ingest": { | ||
| POST: async (req, res) => { | ||
| try { | ||
| const body = (await readBody(req)) as GovernanceReceipt; | ||
| if (!body?.id || !body?.hash) return error(res, 400, "Missing receipt id/hash"); | ||
| ingestObservedReceipt(body); | ||
| json(res, 200, { ok: true, id: body.id }); | ||
| } catch (err) { | ||
| error(res, 500, err instanceof Error ? err.message : String(err)); | ||
| } | ||
| }, | ||
| }, |
There was a problem hiding this comment.
🟥 Unauthenticated receipt ingest endpoint lets anyone inject forged receipts into the live stream
The new /api/receipts/ingest route (backend/server.ts:208-219) accepts a POST body cast directly to GovernanceReceipt with no authentication and only a trivial body?.id/body?.hash presence check, then passes it to ingestObservedReceipt (backend/nova-spine.ts:44-46) which writes it into the CRK-2 observability ledger and broadcasts it to every connected cockpit via the SSE receipt event. Any network client that can reach the API (which also sets Access-Control-Allow-Origin: *) can fabricate arbitrary constitutional receipts — including forged authority, invariantsChecked, blocked, and crk1 provenance — that appear as genuine governed evidence in the cockpit.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "/api/receipts/ingest": { | ||
| POST: async (req, res) => { | ||
| try { | ||
| const body = (await readBody(req)) as GovernanceReceipt; | ||
| if (!body?.id || !body?.hash) return error(res, 400, "Missing receipt id/hash"); | ||
| ingestObservedReceipt(body); | ||
| json(res, 200, { ok: true, id: body.id }); | ||
| } catch (err) { | ||
| error(res, 500, err instanceof Error ? err.message : String(err)); | ||
| } | ||
| }, | ||
| }, |
There was a problem hiding this comment.
🟥 Forged receipts from the open ingest endpoint can execute script in the cockpit (stored XSS)
Receipts injected through the unauthenticated /api/receipts/ingest endpoint are broadcast over SSE, ingested by the cockpit (cockpit/src/bridge/WebSocketBridge.ts:24-41 ingestReceiptPayload) and stored via addReceipt. When an operator opens the Receipt Viewer, the selected receipt is rendered with dangerouslySetInnerHTML={{ __html: highlightJSON(selected) }} (cockpit/src/panels/ReceiptViewer.tsx:80-83), and highlightJSON (ReceiptViewer.tsx:6-24) only wraps JSON tokens in spans without HTML-escaping string values. An attacker-controlled string field (e.g. blockReason or an action.payload value) containing markup such as <img src=x onerror=...> is therefore injected into the DOM and executed.
Was this helpful? React with 👍 or 👎 to provide feedback.
…auth, XSS CenterCommand shows awaiting evidence instead of fake progress. Receipt store is idempotent. Ingest requires token in production. ReceiptViewer escapes JSON before highlight.
Wire /api/receipts/ingest to ingest-auth (token in production, localhost in dev) and allow X-Nova-Ingest-Token in CORS preflight.
name: Pull Request
about: Contribute changes to the project
title: ''
labels: ''
assignees: ''
Description
Please include a summary of the changes and the related issue. Please also include relevant motivation and context.
Fixes # (issue)
Type of change
Please delete options that are not relevant.
Constitutional Impact
If this change affects constitutional governance:
If constitutional impact, describe:
Testing
Describe the tests you ran to verify your changes:
Checklist