diff --git a/.ai-memory/MEMORY.md b/.ai-memory/MEMORY.md index cfe0846..338cf03 100644 --- a/.ai-memory/MEMORY.md +++ b/.ai-memory/MEMORY.md @@ -697,3 +697,179 @@ Club Penguin-style AI debate arena live at https://penguinwalk.co per-user immunity / streak / scan-count / receipt stats, and lists the four ways to navigate (⌘K / Shift-? / Role Tour / Layer Explorer). The "you made it here" panel. +102. Harness Diagnostic — self-watch the brain via OTel-shaped spans + - Cannibalized loop pattern from `context-labs/halo`. Three new + utils: + - `telemetry.js` — unified span buffer (cap 500). OpenTelemetry + schema (trace_id / span_id / parent_span_id / name / kind / + start_time / end_time / status / attributes / events). Ring + buffer in memory with debounced localStorage mirror, pub/sub + subscribers for live UI updates, withSpan() helper for + try/catch instrumentation + - `harnessLift.js` — generalized Layer 27 lift miner. Operates + on any labelled records via { features, isPositive } callbacks. + mineSpansByOutcome() is the OTel-aware wrapper that derives + feature tokens from span name + status + chosen attribute keys + - `harnessFailureModes.js` — named detectors codify The Brain's + equivalent of HALO's failure-mode taxonomy: error-burst / + slow-scan / hung-mcp / dead-patterns / fp-heavy / refusal-loop. + Each returns { id, label, severity, count, examples, hint }. + runDiagnostic() yields a harness-report-v1 JSON envelope and + renderReportText() flattens it for paste-into-coding-agent + - `harnessProposer.js` — translates a report into a + rule-diff-v1 envelope: additions[] (suggested Layer 55 + custom rules from manipulation-vocab lift candidates) + + followUps[] (which layer to run next: L31 evolve, L61 + diagnostic, L66 coverage, L19 inspect, L21 pause) + - Instrumented call sites: + - cognitiveFirewall.scoreContent() emits `firewall.scan` spans + - mcpBridge.handleToolCall() emits `mcp.tool` spans (ok/error) + - brainSteward.runTick() emits `steward.tick` spans with + anomaly / narration / scenario events + - New MCP tools (Layer 19 expansion): + - get_harness_report({ format: 'json'|'text' }) — pulls the + live diagnostic over the bridge so Claude Code / Codex can + read it autonomously + - propose_rule_diff({ topK }) — derives a candidate rule diff + the agent can apply via setActiveRules() + - HarnessDiagnosticPanel: live-reads spans via subscribe(), + shows the report tier (healthy / warn / critical), per-finding + hints, top operations p50, error-correlated features (lift), + copy-as-text and copy-as-JSON for handoff to Cursor / Claude Code +103. Auto-Apply Rule Steward — self-driving Layer 102 loop + - autoSteward.js polls runDiagnostic() + proposeRuleDiff() each + cycle, filters additions to "low-risk" (lift ≥ minLift, length ≥ + minPatternChars, ≤ maxPerCycle, ≤ quotaPerHour), applies via + Layer 55 addCustomRule(), logs every application + - Kill switches: dryRun (default true), enabled (default false), + quota throttle. Defaults to manual + dry-run so nothing happens + until the operator explicitly arms it + - Each cycle emits an `auto-steward.cycle` span (cycle / tier / + candidates / applied / dryRun) so Layer 102 sees its own work + - revertApplied(ruleId) → removes the rule + marks the log entry + reverted. Suggested follow-ups (L31 evolve / L66 coverage / + L21 pause) are surfaced as text, never auto-executed +104. Harness Comparator — before/after a rule change + - harnessComparator.js: compareReports(baseline, current) → + harness-diff-v1 envelope. Findings split into added / + removed / shifted (severity or count change). Aggregates ranked + by absolute errorRate delta. Tier shift labelled + improved / regressed / unchanged + - Snapshot store keyed by id, capped at 20, persisted in + localStorage. saveSnapshot({ label, report }) / + listSnapshots() / deleteSnapshot() + - HarnessComparatorPanel: snapshot button, A/B picker, diff + pane with red/green/orange per category, copy-diff for + handoff +105. Span Annotation — operator feedback into the loop + - spanAnnotation.js: annotate({ spanId, label, note }) → keyed + store cap 200. Standard labels: false-positive, false-negative, + real-bug, benign, investigate + - decorateSpansWithAnnotations(spans) folds labels into + attributes._annotation so the existing lift miner picks them + up as features. New detector detectAnnotatedFalsePositives + surfaces operator-marked FPs as a first-class finding + - SpanAnnotationPanel: label picker chips, span filter input, + live span list with "Tag" / "Remove" buttons, totals roll-up +106. Trace Replay — scrub the telemetry buffer + - traceReplay.js: buildFrames({ spans, frameMs }) buckets the + buffer into chronological frames. rollForward(frames, idx) + computes cumulative per-name totals up to a cursor index + - TraceReplayPanel: frame-size + speed picker, range scrubber, + play / pause / step / reset, current-frame inspector, per-name + cumulative ladder. "Show me what the harness was doing at + 14:32" without re-running the brain +107. OTLP Exporter — push spans to a real collector + - otlpExporter.js: maps internal span schema → OTel ResourceSpans + payload (traceId/spanId hex padding, ms→ns timestamps, attribute + typeAttrValue, kind enum, status code map). exportSpansOnce() + POSTs the batch to the configured endpoint with optional + Authorization header. Dry-run mode renders the payload locally + without sending. Always pipes through Layer 108 sanitizer + before egress + - OtlpExporterPanel: endpoint + auth header + service.name + + batchSize fields, enabled checkbox, Push / Dry run / Clear + buttons, recent-results log with status / error +108. Telemetry Sanitizer — PII redaction before egress + - telemetrySanitizer.js: 6 STANDARD_PATTERNS (email / phone / + IPv4 / SSN-like / credit-card / bearer-token). Custom patterns + add/remove with regex compile validation. sanitizeSpan() walks + attributes + events.attributes + status.message and replaces + every match with [REDACTED:label]. piiAuditSummary() counts + matches across the buffer for the panel's status banner + - SanitizerPanel: live PII audit banner, standard pattern list, + custom pattern editor, used by Layer 107 on every export +109. Harness Alerts — toast on tier shifts + - harnessAlerts.js: subscribes to telemetry pub/sub. On span + activity, debounces (default 4s), runs runDiagnostic, compares + tier + finding-id set to last snapshot. New findings → toast + severity-tinted; tier shift → red on regression, info on + recovery. Off by default; start()/stop() arm/disarm + - HarnessAlertsPanel: armed/idle indicator with last-tier + + last-findings, arm / disarm buttons, three config toggles + (toast on new finding / on tier shift / debounce ms) +110. Trace-Driven Tour — personalized from usage + - traceDrivenTour.js: deriveTour() reads the live aggregate-by- + name buckets, derives error rate + top ops + per-name totals, + walks a 7-step recipe library (each step's `when` predicate + checks the usage context) and emits a tour-v1 envelope with + summary + steps[] linking back to LAYER_CATALOG entries + - TraceDrivenTourPanel: usage summary banner + per-step card + with group-tinted left border. Replaces Layer 94's static + role tour with one derived from what you actually do +111. Span Distribution — tail-latency histograms + - spanDistribution.js: distributionFor(spans, { bounds, name }) + returns { count, min, max, mean, p50, p90, p95, p99, buckets, + labels }. Default log-scale buckets 1/5/10/50/100/500/1000/5000. + distributionsByName sorts top-K by call count + - SpanDistributionPanel: per-name 9-bucket inline histogram + with p50/p95/p99/max alongside. Catches the slow-tail outliers + that aggregate p50 + p95 alone hide +112. Trace Search — query language over the buffer + - traceSearch.js: parseQuery tokenizes `name=foo status=error + duration>200`, supports =/!=/>/<,>=/<= operators and bare-word + free-text matching against name + JSON-stringified attributes. + searchSpans applies a clause list and returns matching spans + - TraceSearchPanel: query input, parsed-clause echo, hit list + with status-tinted left border + JSON attribute footer +113. Diagnostic Snapshots — auto-snap on tier shift + - diagnosticSnapshots.js: subscribes to telemetry, debounces + (default 5s), runs runDiagnostic, compares tier to last + snapshot, calls Layer 104 saveSnapshot with `auto · A→B` label + on every change. Maintains a local timeline of tier shifts + with snapshotId + findingIds + - DiagnosticSnapshotsPanel: WATCHING / IDLE state banner, start / + stop, debounce slider, tier-shift timeline, prune-auto button + that wipes auto-tagged snapshots without touching manual ones +114. MCP Tool Usage — hot / slow / flaky / dead + - mcpToolUsage.js: computeUsage cross-references BRAIN_TOOLS + against mcp.tool spans. Returns hot (top 6 by count), slow + (top 5 by p95), flaky (top 5 by errorRate), and dead + (registered but never called) + - McpToolUsagePanel: totals banner + four sectioned tool lists + (hot/slow/flaky/dead) with per-tool count / p50 / p95 / error + rate. Pairs with Layer 102 dead-pattern detector — this is + its MCP-side cousin +101. Content Verification System — sign humanity, verify chain of custody + - ECDSA P-256 keypair via Web Crypto, stored locally, exportable + via Layer 57. Manifest format `brainsnn-prov/1` carries the pub + key + handle + a hash-linked chain of signed steps (origin → edit + → edit). Each step canonicalizes its fields, SHA-256s them, and + signs that with the creator's private key + - signContent() creates an origin step with optional device / + location / note context. appendEdit() adds a chained edit step + whose prevHash references the previous step's hash — a + lite-C2PA chain of custody for caption / photo updates + - verifyManifest({ manifest, payload }) walks every step, re-derives + its hash, verifies the signature against the manifest's pub key, + checks prevHash linkage, and confirms the latest payloadHash + matches the supplied payload. Returns + { ok, reason, fingerprint, handle, steps } + - humanityScore(text) — heuristic 0-100 over five axes (cadence + variance, candid markers, contractions, irregularities, AI + boilerplate). Tier ladder: AI-perfect / leans polished / mixed + signals / reads human / unmistakably human. Per-axis evidence + so creators see WHY a draft reads polished + - ContentProvenancePanel: Sign / Verify / Humanity / Log tabs. + Identity card with handle + fingerprint. Manifest copy-to-clipboard + for sharing. Recent-manifest log capped at 25 in localStorage diff --git a/brainsnn-r3f-app/src/App.jsx b/brainsnn-r3f-app/src/App.jsx index afb3b3f..7a274a8 100644 --- a/brainsnn-r3f-app/src/App.jsx +++ b/brainsnn-r3f-app/src/App.jsx @@ -81,6 +81,23 @@ import HotkeyMap from './components/HotkeyMap'; import ThemePanel from './components/ThemePanel'; import CommunityPackPanel from './components/CommunityPackPanel'; import MilestonePanel from './components/MilestonePanel'; +import ContentProvenancePanel from './components/ContentProvenancePanel'; +import HarnessDiagnosticPanel from './components/HarnessDiagnosticPanel'; +import AutoStewardPanel from './components/AutoStewardPanel'; +import HarnessComparatorPanel from './components/HarnessComparatorPanel'; +import SpanAnnotationPanel from './components/SpanAnnotationPanel'; +import TraceReplayPanel from './components/TraceReplayPanel'; +import OtlpExporterPanel from './components/OtlpExporterPanel'; +import SanitizerPanel from './components/SanitizerPanel'; +import HarnessAlertsPanel from './components/HarnessAlertsPanel'; +import TraceDrivenTourPanel from './components/TraceDrivenTourPanel'; +import SpanDistributionPanel from './components/SpanDistributionPanel'; +import TraceSearchPanel from './components/TraceSearchPanel'; +import DiagnosticSnapshotsPanel from './components/DiagnosticSnapshotsPanel'; +import McpToolUsagePanel from './components/McpToolUsagePanel'; +import HomeDeck from './components/HomeDeck'; +import FocusedScanDeck from './components/FocusedScanDeck'; +import FocusedDiagnoseDeck from './components/FocusedDiagnoseDeck'; import { registerServiceWorker } from './utils/pwa'; import { registerTheme } from './utils/theme'; import DreamModePanel from './components/DreamModePanel'; @@ -133,6 +150,13 @@ export default function App() { return createInitialState(); }); const [mode, setMode] = useState('simulation'); + const [viewMode, setViewMode] = useState(() => { + try { return localStorage.getItem('brainsnn_view_mode_v1') || 'home'; } + catch { return 'home'; } + }); + useEffect(() => { + try { localStorage.setItem('brainsnn_view_mode_v1', viewMode); } catch { /* noop */ } + }, [viewMode]); const [eegStatus, setEegStatus] = useState({ connected: false, label: 'No device connected' }); const [timelineIndex, setTimelineIndex] = useState(0); const [isRecording, setIsRecording] = useState(false); @@ -455,6 +479,36 @@ export default function App() { + + + {viewMode === 'home' && ( + setViewMode('scan')} + onDiagnose={() => setViewMode('diagnose')} + onAll={() => setViewMode('all')} + /> + )} + + {viewMode === 'scan' && ( + { + setFirewallResult(result); + setState((s) => mapTRIBEToRegions(s, result)); + recordImmunity(IMMUNITY_EVENTS.FIREWALL_SCAN, { + pressure: result.manipulationPressure, + confidence: result.confidence, + }); + toastWarning(`Firewall: ${result.recommendedAction.slice(0, 60)}...`); + }, + }} + /> + )} + + {viewMode === 'diagnose' && } + + {viewMode === 'all' && (<> @@ -779,6 +833,62 @@ export default function App() { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -994,6 +1104,7 @@ export default function App() {

Ready for GitHub Pages and Vercel

This project includes deployment configs plus browser-side export helpers for shareable demos.

+ )} ); } + +function ViewModeTabs({ value, onChange }) { + const tabs = [ + { id: 'home', label: 'Home', hint: 'Brain + quick scan' }, + { id: 'scan', label: 'Scan', hint: 'Firewall + Coverage + Tone + Archive' }, + { id: 'diagnose', label: 'Diagnose', hint: 'Harness diagnostic stack (L102–114)' }, + { id: 'all', label: 'All panels', hint: 'Every layer, full power' }, + ]; + return ( + + ); +} diff --git a/brainsnn-r3f-app/src/components/AutoStewardPanel.jsx b/brainsnn-r3f-app/src/components/AutoStewardPanel.jsx new file mode 100644 index 0000000..295740e --- /dev/null +++ b/brainsnn-r3f-app/src/components/AutoStewardPanel.jsx @@ -0,0 +1,173 @@ +import React, { useEffect, useState } from 'react'; +import { + subscribe, getStatus, updateConfig, start, stop, runOnce, + revertApplied, clearAppliedLog, +} from '../utils/autoSteward'; + +/** + * Layer 103 — Auto-Apply Rule Steward panel. + * + * Closes the HALO loop: poll the harness report, derive a candidate + * rule diff, and apply low-risk additions automatically. Defaults to + * dryRun + paused so nothing happens until the operator explicitly + * arms it. + */ +export default function AutoStewardPanel() { + const [status, setStatus] = useState(() => getStatus()); + + useEffect(() => subscribe(setStatus), []); + + const cfg = status.config; + const tone = ( + status.lastReportTier === 'critical' ? '#dd6974' + : status.lastReportTier === 'warn' ? '#fdab43' + : '#5ee69a' + ); + + function field(key, parser = (v) => v) { + return (e) => updateConfig({ [key]: parser(e.target.value) }); + } + + return ( +
+
Layer 103 · auto-apply rule steward
+

Self-driving Layer 102 loop

+

+ Each cycle, pull the harness report, derive a rule diff, and + apply low-risk additions to Layer 55. Default config is + dry-run + manual — flip enabled to arm it. Quota + + lift threshold prevent runaway rule growth. Every applied + rule is reversible from the log below. +

+ +
+
+ + {status.running ? 'RUNNING' : 'IDLE'} · last tier {status.lastReportTier} + + + cycle {status.cycleCount} · {status.lastDiffAdditions} candidates · {status.appliedLog.length} applied + +
+
+ +
+ {status.running ? ( + + ) : ( + + )} + + + +
+ +
+ + + + + +
+ + {status.lastDiffFollowUps.length > 0 && ( +
+
Suggested follow-ups (manual)
+ {status.lastDiffFollowUps.map((f, i) => ( +
+ L{f.layer} · {f.action} +
{f.reason}
+
+ ))} +
+ )} + +
+
+
Applied log
+ {status.appliedLog.length > 0 && ( + + )} +
+ {status.appliedLog.length === 0 ? ( +

Nothing applied yet.

+ ) : status.appliedLog.map((e, i) => ( +
+
+ + {e.category} + {' '}/{e.pattern}/ + {e.dryRun && · dry-run} + {e.error && · {e.error}} + + {e.ruleId && !e.reverted && !e.dryRun && ( + + )} + {e.reverted && reverted} +
+
+ {new Date(e.ts).toLocaleString()} · cycle {e.cycle} · {e.source} +
+
+ ))} +
+
+ ); +} + +function NumberInput({ label, value, onChange, ...rest }) { + return ( + + ); +} diff --git a/brainsnn-r3f-app/src/components/ContentProvenancePanel.jsx b/brainsnn-r3f-app/src/components/ContentProvenancePanel.jsx new file mode 100644 index 0000000..7b4b1d5 --- /dev/null +++ b/brainsnn-r3f-app/src/components/ContentProvenancePanel.jsx @@ -0,0 +1,500 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { + isCryptoAvailable, + ensureIdentity, + loadIdentity, + loadHandle, + setHandle, + fingerprintFor, + signContent, + appendEdit, + verifyManifest, + recentManifestLog, + logManifest, + clearManifestLog, + manifestToShareString, + tryParseManifest, + humanityScore, +} from '../utils/contentProvenance'; + +/** + * Layer 101 — Content Verification System panel. + * + * Three tabs: + * - Sign: capture context, sign payload, output a manifest + * - Verify: paste a manifest + payload, get pass/fail + chain view + * - Humanity: run the heuristic humanity score on a draft + * + * The whole thing runs in-browser — keys never leave the device. + */ +export default function ContentProvenancePanel() { + const supported = isCryptoAvailable(); + const [tab, setTab] = useState('sign'); + const [identity, setIdentity] = useState(() => loadIdentity()); + const [handle, setHandleState] = useState(() => loadHandle()); + const [fp, setFp] = useState(''); + + useEffect(() => { + let alive = true; + if (identity?.pub) { + fingerprintFor(identity.pub).then((f) => { if (alive) setFp(f); }); + } + return () => { alive = false; }; + }, [identity]); + + async function handleGenerate() { + if (!supported) return; + const r = await ensureIdentity({ handle }); + if (r.ok) { + setIdentity(loadIdentity()); + setFp(r.identity.fingerprint); + } + } + + function handleHandleChange(e) { + const v = e.target.value; + setHandleState(v); + setHandle(v); + } + + return ( +
+
Layer 101 · content verification
+

Sign your humanity

+

+ AI-perfect imagery is now cheap. The signal that cuts through is + a verifiable chain of custody — who captured it, on what device, + and which edits followed. Sign your content with a local keypair. + Anyone can verify your post hasn't been swapped for a fake. +

+ + + +
+ + + + +
+ + {tab === 'sign' && } + {tab === 'verify' && } + {tab === 'humanity' && } + {tab === 'log' && } +
+ ); +} + +function Tab({ id, tab, setTab, label }) { + const on = tab === id; + return ( + + ); +} + +function IdentityCard({ supported, identity, fingerprint, handle, onHandle, onGenerate }) { + if (!supported) { + return ( +
+ Web Crypto unavailable. +

+ Signing needs SubtleCrypto, which requires HTTPS or localhost. + The Humanity tab still works. +

+
+ ); + } + + const hasIdentity = !!identity?.priv && !!identity?.pub; + const tone = hasIdentity ? '#5ee69a' : '#fdab43'; + + return ( +
+
+ {hasIdentity ? 'Creator key ready' : 'No creator key yet'} + + {hasIdentity ? 'ECDSA P-256 · stored locally' : 'click to generate'} + +
+
+ + +
+ {fingerprint && ( +

+ fp · {fingerprint.slice(0, 16)}… +

+ )} +
+ ); +} + +function SignTab({ supported, identity }) { + const [payload, setPayload] = useState(''); + const [device, setDevice] = useState(''); + const [location, setLocation] = useState(''); + const [note, setNote] = useState(''); + const [manifest, setManifest] = useState(null); + const [editNote, setEditNote] = useState(''); + const [err, setErr] = useState(''); + + const ready = supported && identity?.priv; + + async function doSign() { + setErr(''); + if (!ready) { setErr('Generate a key first'); return; } + if (!payload.trim()) { setErr('Paste content to sign'); return; } + const r = await signContent({ + payload, + payloadKind: 'text', + device, + location, + note, + }); + if (!r.ok) { setErr(r.reason || 'sign failed'); return; } + setManifest(r.manifest); + logManifest({ manifest: r.manifest, payloadExcerpt: payload }); + } + + async function doAppendEdit() { + setErr(''); + if (!manifest) return; + const r = await appendEdit({ manifest, payload, note: editNote }); + if (!r.ok) { setErr(r.reason || 'edit failed'); return; } + setManifest(r.manifest); + setEditNote(''); + logManifest({ manifest: r.manifest, payloadExcerpt: payload }); + } + + function copyManifest() { + if (!manifest) return; + try { navigator.clipboard.writeText(manifestToShareString(manifest)); } catch { /* noop */ } + } + + return ( +
+