merge: testing → main (reconcile 2-week divergence)

This commit is contained in:
2026-04-28 18:36:00 -04:00
parent 499836c9e4
commit 862e4dbb31
1235 changed files with 160255 additions and 7996 deletions

View File

@@ -0,0 +1,122 @@
/* Realism Config — layered on DeckyFleet.css.
Adds: weight tables (label + numeric input + percent share),
canary-class accent, canary-probability slider row. */
.realism-config-root .mono { font-family: var(--font-mono); }
.realism-config-root .info-banner {
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border);
border-left: 3px solid var(--violet);
padding: 10px 14px;
font-size: 0.78rem;
line-height: 1.5;
}
.realism-config-root .info-banner em { color: var(--matrix); font-style: normal; }
/* Section heading above each weight table. */
.realism-config-root .section-head {
display: flex;
justify-content: space-between;
align-items: baseline;
font-size: 0.7rem;
letter-spacing: 1.5px;
color: var(--dim);
text-transform: uppercase;
margin: 0 0 8px;
}
.realism-config-root .section-head .total {
font-family: var(--font-mono);
color: var(--matrix);
letter-spacing: 1px;
}
.realism-config-root .section-help {
font-size: 0.7rem;
opacity: 0.45;
letter-spacing: 0.5px;
margin: -4px 0 8px;
}
/* Weight table: class label · raw enum · weight input · percent share. */
.realism-config-root .weight-table {
width: 100%;
border-collapse: collapse;
background: var(--panel);
border: 1px solid var(--border);
margin-bottom: 24px;
font-size: 0.82rem;
}
.realism-config-root .weight-table tr {
border-bottom: 1px solid var(--border);
}
.realism-config-root .weight-table tr:last-child { border-bottom: none; }
.realism-config-root .weight-table td {
padding: 8px 14px;
vertical-align: middle;
}
.realism-config-root .weight-table td.cls { width: 50%; }
.realism-config-root .weight-table td.cls .cls-label { font-weight: 600; }
.realism-config-root .weight-table td.cls .cls-enum {
margin-left: 10px;
font-family: var(--font-mono);
font-size: 0.7rem;
color: var(--dim);
letter-spacing: 0.5px;
}
.realism-config-root .weight-table td.cls.canary .cls-label {
color: var(--amber, #f59e0b);
}
.realism-config-root .weight-table td.weight {
width: 130px;
}
.realism-config-root .weight-table input.weight-input {
width: 90px;
background: var(--bg-elev, rgba(0, 0, 0, 0.3));
border: 1px solid var(--border);
color: var(--matrix);
padding: 5px 9px;
font-family: var(--font-mono);
font-size: 0.85rem;
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
text-align: right;
}
.realism-config-root .weight-table input.weight-input:focus {
border-color: var(--violet);
box-shadow: 0 0 0 1px var(--violet);
}
.realism-config-root .weight-table td.share {
font-family: var(--font-mono);
color: var(--dim);
width: 80px;
text-align: right;
}
/* Canary probability slider row. */
.realism-config-root .prob-row {
display: flex;
align-items: center;
gap: 16px;
background: var(--panel);
border: 1px solid var(--border);
padding: 12px 16px;
margin-bottom: 24px;
}
.realism-config-root .prob-row input[type="range"] {
flex: 1;
accent-color: var(--violet);
}
.realism-config-root .prob-row .prob-value {
font-family: var(--font-mono);
font-size: 1rem;
color: var(--matrix);
letter-spacing: 1px;
min-width: 70px;
text-align: right;
}
.realism-config-root .footer-actions {
display: flex;
gap: 12px;
margin-top: 8px;
}

View File

@@ -0,0 +1,264 @@
import React, { useEffect, useMemo, useState } from 'react';
import api from '../../utils/api';
import { useToast } from '../Toasts/useToast';
import { Save, RotateCcw, AlertTriangle } from '../../icons';
import { contentClassLabel, isCanaryClass } from '../../realism/labels';
// Reuse the DeckyFleet shell (page-header / btn / fleet-* / dim / mono) and
// the persona-page tweaks (info-banner, .input) so the realism config panel
// reads the same as the rest of the realism nav group.
import '../DeckyFleet.css';
import '../PersonaGeneration.css';
import './RealismConfig.css';
// ─── Types ───────────────────────────────────────────────────────────────────
interface WeightEntry {
content_class: string;
weight: number;
}
interface ConfigPayload {
user_class_weights: WeightEntry[];
system_class_weights: WeightEntry[];
canary_class_weights: WeightEntry[];
canary_probability: number;
}
const DEFAULTS: ConfigPayload = {
user_class_weights: [
{ content_class: 'note', weight: 30 },
{ content_class: 'todo', weight: 20 },
{ content_class: 'draft', weight: 15 },
{ content_class: 'script', weight: 10 },
],
system_class_weights: [
{ content_class: 'log_cron', weight: 12 },
{ content_class: 'log_daemon', weight: 8 },
{ content_class: 'cache_tmp', weight: 5 },
],
canary_class_weights: [
{ content_class: 'canary_aws_creds', weight: 1 },
{ content_class: 'canary_env_file', weight: 1 },
{ content_class: 'canary_git_config', weight: 1 },
{ content_class: 'canary_ssh_key', weight: 1 },
{ content_class: 'canary_honeydoc', weight: 1 },
{ content_class: 'canary_honeydoc_docx', weight: 1 },
{ content_class: 'canary_honeydoc_pdf', weight: 1 },
{ content_class: 'canary_mysql_dump', weight: 1 },
],
canary_probability: 0.03,
};
// ─── Subcomponent ────────────────────────────────────────────────────────────
const WeightTable: React.FC<{
title: string;
help: string;
weights: WeightEntry[];
onChange: (next: WeightEntry[]) => void;
}> = ({ title, help, weights, onChange }) => {
const total = weights.reduce((s, w) => s + Math.max(0, w.weight), 0);
return (
<>
<div className="section-head">
<span>{title}</span>
<span className="total">TOTAL {total}</span>
</div>
<div className="section-help">{help}</div>
<table className="weight-table">
<tbody>
{weights.map((w, i) => {
const canary = isCanaryClass(w.content_class);
const share =
total === 0
? '—'
: `${((Math.max(0, w.weight) / total) * 100).toFixed(1)}%`;
return (
<tr key={w.content_class}>
<td className={`cls${canary ? ' canary' : ''}`}>
<span className="cls-label">{contentClassLabel(w.content_class)}</span>
<span className="cls-enum">{w.content_class}</span>
</td>
<td className="weight">
<input
type="number"
min={0}
step={1}
className="weight-input"
value={w.weight}
onChange={(e) => {
const v = parseInt(e.target.value, 10);
const next = weights.slice();
next[i] = {
...next[i],
weight: Number.isFinite(v) ? Math.max(0, v) : 0,
};
onChange(next);
}}
/>
</td>
<td className="share">{share}</td>
</tr>
);
})}
</tbody>
</table>
</>
);
};
// ─── Page ────────────────────────────────────────────────────────────────────
const RealismConfig: React.FC = () => {
const { push } = useToast();
const [config, setConfig] = useState<ConfigPayload>(DEFAULTS);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchConfig = async () => {
setLoading(true);
setError(null);
try {
const res = await api.get<ConfigPayload>('/realism/config');
setConfig(res.data);
} catch (err: any) {
setError(err?.response?.status === 401 ? 'Authentication required.' : 'Load failed.');
} finally {
setLoading(false);
}
};
useEffect(() => { fetchConfig(); }, []);
const handleSave = async () => {
setSaving(true);
setError(null);
try {
const res = await api.put<ConfigPayload>('/realism/config', config);
setConfig(res.data);
push({ text: 'REALISM CONFIG SAVED', tone: 'matrix', icon: 'terminal' });
} catch (err: any) {
const detail = err?.response?.data?.detail;
const status = err?.response?.status;
if (status === 403) setError('Admin role required to save.');
else if (status === 400 && detail) setError(`Validation failed: ${detail}`);
else setError('Save failed.');
} finally {
setSaving(false);
}
};
const handleReset = () => {
if (!window.confirm(
'Reset to baked-in defaults? This will overwrite the saved config on next save.',
)) return;
setConfig(DEFAULTS);
};
const totals = useMemo(() => ({
user: config.user_class_weights.reduce((s, w) => s + Math.max(0, w.weight), 0),
system: config.system_class_weights.reduce((s, w) => s + Math.max(0, w.weight), 0),
canary: config.canary_class_weights.reduce((s, w) => s + Math.max(0, w.weight), 0),
}), [config]);
return (
<div className="fleet-root realism-config-root">
<div className="page-header">
<div className="page-title-group">
<h1>REALISM CONFIG</h1>
<span className="page-sub">
USER {totals.user} · SYSTEM {totals.system} · CANARY {totals.canary} ·
{' '}CANARY PROB {(config.canary_probability * 100).toFixed(1)}%
</span>
</div>
<div className="actions">
<button
className="btn ghost"
onClick={handleReset}
disabled={saving || loading}
title="Reset form fields to baked-in defaults (does not save until you press SAVE)"
>
<RotateCcw size={12} /> RESET
</button>
<button
className="btn violet"
onClick={handleSave}
disabled={saving || loading}
title="Persist current values to realism_config; orchestrator picks them up within one refresh tick (~5 min)."
>
<Save size={12} /> {saving ? 'SAVING…' : 'SAVE'}
</button>
</div>
</div>
<div className="info-banner">
<div>
<strong>Scope:</strong> tunes the orchestrator's <em>realism planner</em>
{' '}— how often each kind of synthetic file lands on a decky, and
how rare canary plants are. Persisted in the{' '}
<span className="mono matrix-text">realism_config</span> table; the
orchestrator refreshes from the DB every ~5 minutes.
</div>
{error && (
<div className="info-line alert-text" style={{ marginTop: 8 }}>
<AlertTriangle size={12} /> {error}
</div>
)}
</div>
{loading ? (
<div className="dim" style={{ padding: '24px 0' }}>Loading</div>
) : (
<>
<WeightTable
title="User Class Weights"
help="Files written by personas during their work hours. The realism win when a persona looks busy."
weights={config.user_class_weights}
onChange={(next) => setConfig({ ...config, user_class_weights: next })}
/>
<WeightTable
title="System Class Weights"
help="Plausible OS-side filler — rotated logs, daemon noise, ephemeral cache."
weights={config.system_class_weights}
onChange={(next) => setConfig({ ...config, system_class_weights: next })}
/>
<WeightTable
title="Canary Class Weights"
help="Callback-bearing artifacts. Uniform across generators by default; raise one to bias toward a specific bait flavour."
weights={config.canary_class_weights}
onChange={(next) => setConfig({ ...config, canary_class_weights: next })}
/>
<div className="section-head">
<span>Canary Probability</span>
<span className="total">{(config.canary_probability * 100).toFixed(1)}%</span>
</div>
<div className="section-help">
Share of file picks that materialise a canary. Each plant
creates a real canary token row + DNS slug or HTTP URL
keeping this rare prevents a noisy alert surface.
</div>
<div className="prob-row">
<input
type="range"
min={0}
max={1}
step={0.005}
value={config.canary_probability}
onChange={(e) => setConfig({
...config,
canary_probability: parseFloat(e.target.value),
})}
/>
<span className="prob-value">
{(config.canary_probability * 100).toFixed(1)}%
</span>
</div>
</>
)}
</div>
);
};
export default RealismConfig;