feat(privshield): add veil custom terminal harness + TUI (ADR-288 §harness)

A dependency-free native binary (`src/bin/veil.rs`) — the in-repo counterpart to
the npm metaharness — that drives the same crate API the tests use:

- Interactive ANSI dashboard (TUI): live status, re-ID off/on vs chance,
  throughput, compliance (energy 1.000× / not jamming), a block-sparkline
  collapse curve, config, and PASS/OUT-OF-SPEC verdict. Command-driven redraw
  loop (std-only, no crossterm/ratatui): on/off, passes/bits/n/snr,
  metric euclid|cosine, preset scif|board|ward|hotel, optimize, proof.
- Scriptable subcommands: report, sweep, optimize, adaptive <N>, proof, doctor
  (exit 0 = healthy). Auto-picks TUI on a terminal, one-shot report when piped;
  honors NO_COLOR.

Std-only, so it builds with no extra deps and runs in any pipe/CI. The wasm leaf
story is unchanged (validated with `--lib`; the bin is native-only). All
readouts are SYNTHETIC/L0 and never relabeled.

Validated: 38 tests + doctest pass, clippy --all-targets -D warnings clean,
rustfmt clean, wasm --lib builds; all subcommands + a scripted TUI session
exercised. Documented in the crate README and ADR-288.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01WEXNqzs7UsfNFBcP5yW21p
This commit is contained in:
Claude
2026-08-09 15:26:07 +00:00
parent 80b1715cb8
commit cb67be117a
4 changed files with 594 additions and 0 deletions
@@ -139,6 +139,17 @@ better on **both** privacy (re-ID 0.047 vs 0.078) and throughput (0.976 vs 0.974
and is now verified rather than assumed. See
`docs/research/privacy-shield/08-optimization.md`.
### harness. Native terminal harness + TUI (`src/bin/veil.rs`)
A custom, dependency-free binary (`veil`) ships with the crate — the in-repo,
native counterpart to the npm metaharness (ADR-289). It drives the same public
API the tests use, as an interactive ANSI dashboard plus scriptable subcommands
(`report`, `sweep`, `optimize`, `adaptive <N>`, `proof`, `doctor`, `tui`).
Std-only (no `crossterm`/`ratatui`): the TUI is a command-driven redraw loop, so
it runs in any terminal, pipe, or CI and keeps the crate a pure leaf. It reports
only SYNTHETIC/L0 numbers and never relabels them. The wasm leaf story is
unchanged (validated with `--lib`; the bin is native-only).
## 3. What this explicitly is NOT
- **Not a radio driver.** No RF frontend, no transmit path, no
@@ -23,3 +23,10 @@ readme = "README.md"
[lib]
name = "wifi_densepose_privshield"
path = "src/lib.rs"
# `veil` — the custom, dependency-free terminal harness + TUI (ADR-288 §harness).
# Native counterpart to the npm metaharness. Std-only; builds without any extra
# deps. Excluded from the wasm leaf story (that stays `--lib`).
[[bin]]
name = "veil"
path = "src/bin/veil.rs"
@@ -66,6 +66,40 @@ anything.
cargo test -p wifi-densepose-privshield --no-default-features
```
## `veil` — terminal harness & TUI
A custom, **dependency-free** native harness ships with the crate (the in-repo
counterpart to the npm metaharness). It drives the same model the tests use — an
interactive ANSI dashboard plus scriptable subcommands, std-only (no
`crossterm`/`ratatui`), so it runs in any terminal, pipe, or CI.
```bash
cargo run -p wifi-densepose-privshield --bin veil # interactive TUI (or a one-shot report when piped)
cargo run -p wifi-densepose-privshield --bin veil -- sweep # re-ID vs passes + throughput vs bits
cargo run -p wifi-densepose-privshield --bin veil -- optimize
cargo run -p wifi-densepose-privshield --bin veil -- doctor # self-check, exit 0 = healthy
```
```text
┌──────────────────────────────────────────────────────────
│ VEIL · wifi-sensing privacy shield ● PROTECTED
│ re-ID off 100.0% re-ID on 4.7% (chance 6.25%)
│ throughput 97.6% emission 1.000× · not jamming
│ collapse ████▇▆▅▂▂▂▁▂ passes 2→112 · op 96
│ config passes 96 · bits 5 · N 16 · snr 20dB · euclid
│ verdict ✓ PASS — re-ID at chance · throughput ≥95% · compliant
└──────────────────────────────────────────────────────────
```
In the TUI, type commands to steer the shield live: `on`/`off`, `passes <n>`,
`bits <n>`, `n <k>`, `snr <db>`, `metric euclid|cosine`,
`preset scif|board|ward|hotel`, `optimize`, `proof`, `quit`. All readouts are
**SYNTHETIC / L0**. (A graphical VEIL Console web dashboard mirrors this same
instrument; `veil` is the terminal-native version.)
## Modules
| Module | Purpose |
@@ -0,0 +1,542 @@
//! `veil` — a custom, dependency-free terminal harness for the VEIL privacy
//! shield (ADR-288). It is the in-repo, native counterpart to the npm
//! metaharness (`harness/wifi-densepose-privshield/`, ADR-289): where that one
//! assists *development*, this one *drives the model* — an interactive TUI plus
//! scriptable subcommands over the same crate API the tests use.
//!
//! Std-only on purpose: no `crossterm`/`ratatui`, no external deps. The TUI is
//! a command-driven ANSI dashboard (line input, redraw on change), which keeps
//! the crate a pure leaf and lets the harness run in any pipe or CI.
//!
//! ```text
//! veil # TUI if attached to a terminal, else a one-shot report
//! veil tui # force the interactive dashboard
//! veil report # print the dashboard once (plain, pipe-friendly)
//! veil sweep # re-ID vs passes and throughput vs bits tables
//! veil optimize # run the hyper-optimizer, print the recommendation
//! veil adaptive <N> # derive the shield for a room of N candidate identities
//! veil proof # verify the deterministic witness
//! veil doctor # self-check (exit 0 = healthy)
//! ```
//!
//! All numbers are **SYNTHETIC / L0** — reproduced by `cargo test`, describing
//! the model, not real hardware.
use std::io::{self, BufRead, IsTerminal, Write};
use veil::optimize;
use veil::{run, ExperimentConfig, ExperimentReport, Metric, Proof};
use wifi_densepose_privshield as veil;
// ---- ANSI palette (matches the VEIL Console: teal shield, amber threat) ----
const TEAL: &str = "\x1b[38;2;32;211;192m";
const AMBER: &str = "\x1b[38;2;245;158;75m";
const GOOD: &str = "\x1b[38;2;62;207;142m";
const CRIT: &str = "\x1b[38;2;242;107;111m";
const MUTE: &str = "\x1b[38;2;139;160;159m";
const BOLD: &str = "\x1b[1m";
const RST: &str = "\x1b[0m";
const BLOCKS: [char; 8] = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
/// Emit color only for a real terminal, and never when `NO_COLOR` is set.
fn color_enabled() -> bool {
io::stdout().is_terminal() && std::env::var_os("NO_COLOR").is_none()
}
/// Wrap `s` in `code` when color is on.
fn c(s: &str, code: &str, on: bool) -> String {
if on {
format!("{code}{s}{RST}")
} else {
s.to_string()
}
}
/// A raw color code, or "" when color is off — for inline `format!` colouring.
fn k(code: &'static str, on: bool) -> &'static str {
if on {
code
} else {
""
}
}
/// Shield-on re-ID at a given mixing budget, holding the rest of `cfg`.
fn reid_at(cfg: &ExperimentConfig, passes: usize) -> f32 {
let mut c = cfg.clone();
c.shield.givens_passes = passes;
run(&c).accuracy_shield_on
}
/// A block-sparkline character for a value in `[0, 1]`.
fn spark(v: f32) -> char {
let i = (v.clamp(0.0, 1.0) * 7.0).round() as usize;
BLOCKS[i.min(7)]
}
/// Render the full dashboard as colored lines (left-bar panel; no right border,
/// so ANSI escape width never has to be counted).
fn dashboard(cfg: &ExperimentConfig, on: bool) -> Vec<String> {
let rep: ExperimentReport = run(cfg);
let chance = rep.chance_level * 100.0;
let off = rep.accuracy_shield_off * 100.0;
let onp = rep.accuracy_shield_on * 100.0;
let tp = rep.throughput_ratio * 100.0;
let (state, scode) = if !cfg.shield.enabled {
("EXPOSED", CRIT)
} else if rep.passed() {
("PROTECTED", GOOD)
} else {
("AT RISK", AMBER)
};
let on_code = if onp <= rep.chance_band * 100.0 {
GOOD
} else {
AMBER
};
let tp_code = if tp >= 95.0 { GOOD } else { CRIT };
let bar = c("", MUTE, on);
let mut out = Vec::new();
out.push(c(
"┌──────────────────────────────────────────────────────────",
MUTE,
on,
));
out.push(format!(
"{} {}{}VEIL{} {}· wifi-sensing privacy shield{} {}{}{}",
bar,
k(BOLD, on),
k(TEAL, on),
k(RST, on),
k(MUTE, on),
k(RST, on),
k(scode, on),
state,
k(RST, on),
));
out.push(bar.clone());
out.push(format!(
"{} re-ID off {}{:>6.1}%{} re-ID on {}{:>5.1}%{} {}(chance {:.2}%){}",
bar,
k(AMBER, on),
off,
k(RST, on),
k(on_code, on),
onp,
k(RST, on),
k(MUTE, on),
chance,
k(RST, on),
));
out.push(format!(
"{} throughput {}{:>6.1}%{} emission {}{:.3}×{} {}· not jamming{}",
bar,
k(tp_code, on),
tp,
k(RST, on),
k(GOOD, on),
rep.compliance.energy_ratio,
k(RST, on),
k(MUTE, on),
k(RST, on),
));
out.push(bar.clone());
let cand = optimize::PASS_CANDIDATES;
let line: String = cand.iter().map(|&p| spark(reid_at(cfg, p))).collect();
out.push(format!(
"{} {}collapse{} {}{}{} {}passes {}{} · op {}{}",
bar,
k(MUTE, on),
k(RST, on),
k(TEAL, on),
line,
k(RST, on),
k(MUTE, on),
cand[0],
cand[cand.len() - 1],
cfg.shield.givens_passes,
k(RST, on),
));
out.push(bar.clone());
let metric = match cfg.attacker_metric {
Metric::Euclidean => "euclid",
Metric::Cosine => "cosine",
};
out.push(format!(
"{} {}config{} passes {} · bits {} · N {} · snr {:.0}dB · {}",
bar,
k(MUTE, on),
k(RST, on),
cfg.shield.givens_passes,
cfg.shield.feedback_bits,
cfg.scene.identities,
cfg.link.snr_db,
metric,
));
let (vlabel, vcode) = if !cfg.shield.enabled {
("SHIELD OFF — room exposed", CRIT)
} else if rep.passed() {
(
"✓ PASS — re-ID at chance · throughput ≥95% · compliant",
GOOD,
)
} else {
(
"△ OUT OF SPEC — raise passes/bits to re-enter the chance band",
AMBER,
)
};
out.push(format!(
"{} {}verdict{} {}{}{}",
bar,
k(MUTE, on),
k(RST, on),
k(vcode, on),
vlabel,
k(RST, on)
));
out.push(c(
"└──────────────────────────────────────────────────────────",
MUTE,
on,
));
out
}
/// Deployment presets (mirror `optimize::adaptive_shield` results per room).
fn preset(name: &str, cfg: &mut ExperimentConfig) -> bool {
let (n, passes, bits, snr) = match name {
"scif" => (64, 96, 5, 20.0),
"board" => (16, 96, 5, 25.0),
"ward" => (32, 96, 5, 15.0),
"hotel" => (48, 64, 5, 20.0),
_ => return false,
};
cfg.scene.identities = n;
cfg.shield.givens_passes = passes;
cfg.shield.feedback_bits = bits;
cfg.link.snr_db = snr;
true
}
fn print_dashboard(cfg: &ExperimentConfig, on: bool) {
for l in dashboard(cfg, on) {
println!("{l}");
}
}
fn cmd_sweep(cfg: &ExperimentConfig, on: bool) {
println!(
"{}re-ID (shield on) vs Givens passes — N={}{}",
k(MUTE, on),
cfg.scene.identities,
k(RST, on)
);
for &p in &optimize::PASS_CANDIDATES {
let robust =
optimize::passes_collapse_at_n(cfg, p, cfg.shield.feedback_bits, cfg.scene.identities);
println!(
" passes {:>3} re-ID {:>5.1}% {}",
p,
reid_at(cfg, p) * 100.0,
if robust {
c("collapses", GOOD, on)
} else {
c("above chance", AMBER, on)
}
);
}
println!(
"\n{}throughput vs feedback bits — snr={:.0}dB{}",
k(MUTE, on),
cfg.link.snr_db,
k(RST, on)
);
for bits in 1..=12u32 {
let mut s = cfg.shield.clone();
s.feedback_bits = bits;
let tp = cfg.link.throughput_ratio(&s) * 100.0;
let barlen = ((tp - 90.0).clamp(0.0, 10.0) / 10.0 * 24.0) as usize;
println!(
" {:>2} bit {:>6.3}% {}{}{}",
bits,
tp,
k(TEAL, on),
"".repeat(barlen),
k(RST, on)
);
}
let (sb, _) = optimize::spec_optimal_feedback_bits(cfg);
println!(" {}spec-optimal: {} bit{}", k(MUTE, on), sb, k(RST, on));
}
fn cmd_optimize(cfg: &ExperimentConfig, on: bool) {
let opt = veil::hyper_optimize(cfg);
let r = &opt.report;
println!("{}hyper-optimizer{}", k(BOLD, on), k(RST, on));
println!(" min robust passes : {}", opt.min_passes);
println!(
" shipped passes : {} {}(min × 2 margin, throughput-free){}",
opt.shipped_passes,
k(MUTE, on),
k(RST, on)
);
println!(
" spec-optimal bits : {} {}(model optimum {}){}",
opt.spec_optimal_bits,
k(MUTE, on),
opt.model_optimal_bits,
k(RST, on)
);
println!(
" result : re-ID {}{:.1}%{} · throughput {}{:.1}%{} · {}",
k(GOOD, on),
r.accuracy_shield_on * 100.0,
k(RST, on),
k(GOOD, on),
r.throughput_ratio * 100.0,
k(RST, on),
if r.passed() {
c("PASS", GOOD, on)
} else {
c("FAIL", CRIT, on)
}
);
println!(
" {}SNR → model-optimal bits: {:?}{}",
k(MUTE, on),
optimize::optimal_bits_across_snr(cfg),
k(RST, on)
);
}
fn cmd_adaptive(cfg: &ExperimentConfig, n: usize, on: bool) {
let sh = veil::adaptive_shield(cfg, n);
println!(
"adaptive shield for N={}: passes {} · bits {} {}(mixing budget is N-independent in this model){}",
n, sh.givens_passes, sh.feedback_bits, k(MUTE, on), k(RST, on)
);
}
fn cmd_proof(on: bool) -> i32 {
let w = Proof::witness(&Proof::run_reference());
let ok = w == Proof::EXPECTED_WITNESS;
println!(
"witness {:#018x} expected {:#018x} {}",
w,
Proof::EXPECTED_WITNESS,
if ok {
c("MATCH", GOOD, on)
} else {
c("DRIFT", CRIT, on)
}
);
i32::from(!ok)
}
fn cmd_doctor(on: bool) -> i32 {
let rep = run(&ExperimentConfig::default());
let checks = [
("reference experiment passes", rep.passed()),
(
"attack is real without shield",
rep.attack_is_effective_without_shield(),
),
("collapse drives to chance", rep.drives_to_chance()),
("throughput ≥ 95%", rep.preserves_throughput()),
("emission is compliant", rep.compliance.is_compliant()),
(
"deterministic witness matches",
Proof::witness(&Proof::run_reference()) == Proof::EXPECTED_WITNESS,
),
];
let mut ok = true;
for (label, pass) in checks {
ok &= pass;
println!(
"{} {label}",
if pass {
c("PASS", GOOD, on)
} else {
c("FAIL", CRIT, on)
}
);
}
println!(
"\nveil doctor: {}",
if ok {
c("all checks passed", GOOD, on)
} else {
c("problems found", CRIT, on)
}
);
i32::from(!ok)
}
fn help() {
println!(
"veil — VEIL privacy-shield harness (SYNTHETIC / L0)\n\n\
USAGE\n veil [command]\n\n\
COMMANDS\n\
\x20 tui interactive dashboard (default on a terminal)\n\
\x20 report print the dashboard once\n\
\x20 sweep re-ID vs passes + throughput vs bits\n\
\x20 optimize run the hyper-optimizer\n\
\x20 adaptive <N> derive the shield for N candidate identities\n\
\x20 proof verify the deterministic witness\n\
\x20 doctor self-check (exit 0 = healthy)\n\
\x20 help this text\n\n\
TUI COMMANDS (type + Enter)\n\
\x20 on | off toggle the shield\n\
\x20 passes <n> · bits <n> · n <k> · snr <db>\n\
\x20 metric euclid|cosine\n\
\x20 preset scif|board|ward|hotel\n\
\x20 run | optimize | proof | help | quit"
);
}
fn tui(mut cfg: ExperimentConfig, on: bool) {
let stdin = io::stdin();
let interactive = stdin.is_terminal();
let redraw = |cfg: &ExperimentConfig, msg: &str| {
if interactive {
print!("\x1b[2J\x1b[H");
}
print_dashboard(cfg, on);
if !msg.is_empty() {
println!(" {}{}{}", k(MUTE, on), msg, k(RST, on));
}
print!("{}veil{} ", k(TEAL, on), k(RST, on));
let _ = io::stdout().flush();
};
redraw(&cfg, "type `help` for commands");
for line in stdin.lock().lines() {
let line = match line {
Ok(l) => l,
Err(_) => break,
};
let mut it = line.split_whitespace();
let cmd = it.next().unwrap_or("");
let arg = it.next().unwrap_or("");
let mut msg = String::new();
match cmd {
"" => {}
"quit" | "q" | "exit" => break,
"help" | "h" => {
if interactive {
print!("\x1b[2J\x1b[H");
}
help();
continue;
}
"on" => cfg.shield.enabled = true,
"off" => cfg.shield.enabled = false,
"passes" => match arg.parse::<usize>() {
Ok(v) => cfg.shield.givens_passes = v.clamp(1, 512),
Err(_) => msg = "passes: need a number".into(),
},
"bits" => match arg.parse::<u32>() {
Ok(v) => cfg.shield.feedback_bits = v.clamp(1, 12),
Err(_) => msg = "bits: need 1..12".into(),
},
"n" => match arg.parse::<usize>() {
Ok(v) => cfg.scene.identities = v.clamp(2, 128),
Err(_) => msg = "n: need 2..128".into(),
},
"snr" => match arg.parse::<f64>() {
Ok(v) => cfg.link.snr_db = v.clamp(0.0, 60.0),
Err(_) => msg = "snr: need a number (dB)".into(),
},
"metric" => match arg {
"euclid" | "euclidean" => cfg.attacker_metric = Metric::Euclidean,
"cosine" | "cos" => cfg.attacker_metric = Metric::Cosine,
_ => msg = "metric: euclid | cosine".into(),
},
"preset" => {
if !preset(arg, &mut cfg) {
msg = "preset: scif | board | ward | hotel".into();
}
}
"run" => msg = "ran — numbers above reflect current settings".into(),
"optimize" | "opt" => {
cfg.shield = veil::hyper_optimize(&cfg).shield;
msg = format!(
"optimized → passes {} · bits {}",
cfg.shield.givens_passes, cfg.shield.feedback_bits
);
}
"proof" => {
let w = Proof::witness(&Proof::run_reference());
msg = format!(
"witness {:#018x} ({})",
w,
if w == Proof::EXPECTED_WITNESS {
"match"
} else {
"drift"
}
);
}
other => msg = format!("unknown: {other} (try `help`)"),
}
redraw(&cfg, &msg);
}
if interactive {
println!();
}
}
fn main() {
let on = color_enabled();
let args: Vec<String> = std::env::args().skip(1).collect();
let cfg = ExperimentConfig::default();
let code = match args.first().map(String::as_str).unwrap_or("") {
"" => {
if io::stdout().is_terminal() {
tui(cfg, on);
} else {
print_dashboard(&cfg, on);
}
0
}
"tui" => {
tui(cfg, on);
0
}
"report" => {
print_dashboard(&cfg, on);
0
}
"sweep" => {
cmd_sweep(&cfg, on);
0
}
"optimize" | "opt" => {
cmd_optimize(&cfg, on);
0
}
"adaptive" => {
let n = args
.get(1)
.and_then(|s| s.parse().ok())
.unwrap_or(cfg.scene.identities);
cmd_adaptive(&cfg, n, on);
0
}
"proof" => cmd_proof(on),
"doctor" => cmd_doctor(on),
"help" | "-h" | "--help" => {
help();
0
}
other => {
eprintln!("unknown command: {other}. Try `veil help`.");
2
}
};
std::process::exit(code);
}