From 630cd52579de50674b48e8718fe6ed384e9585c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 12:35:03 +0000 Subject: [PATCH] =?UTF-8?q?docs(adr):=20deep=20review=20of=20the=20RuView?= =?UTF-8?q?=20npm=20surface=20=E2=80=94=20ADR-263/264/265=20optimization?= =?UTF-8?q?=20strategies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-263 — @ruvnet/ruview@0.1.0 harness review (O1–O9): - HIGH: claim-check CLI fails open on empty input (no --text/--file -> PASS exit 0) - HIGH: MCP stdio server head-of-line blocking (spawnSync verify/calibrate up to 600s) - MEASURED: optionalDependencies triple the cold npx install (4 pkgs/620kB/71 files vs 1 pkg/172kB/22 files with --omit=optional) for a path that never imports them - maxBuffer truncation, python -c port interpolation, version drift, duplicate skills, guardrail METRIC_TERMS substring false positives ('map'/'F1' — found by dogfooding claim-check on these very ADRs), zero CI ADR-264 — @ruvnet/rvagent@0.1.0 + @ruv/ruview-cli review (O1–O9), verified against the published registry tarball: - HIGH: exports.require -> dist/index.cjs which is never built nor published - MEASURED: 44 dead source-map files = 62,698B of the 188kB unpacked payload - stdio-only server described as dual-transport; mixed dot/underscore tool names; double Zod validation + hand-duplicated advertised schemas; 2-fd leak per training job; unbounded body in the unwired HTTP scaffold; dead detectCogBinary candidates; ruview bin-name collision ADR-265 — cross-cutting npm distribution strategy: npm-packages.yml CI matrix (test + pack-content/size gate + tarball-install smoke test), publish-from-CI-only with npm provenance, version single-sourcing from package.json, bin/namespace ownership (ruview bin belongs to @ruvnet/ruview), claim-check on package READMEs. Docs only — no runtime code changed. Index/CHANGELOG/CLAUDE.md/README counts updated. Co-Authored-By: claude-flow Claude-Session: https://claude.ai/code/session_01WrGfTGKv1oWZ6iwXZACULz --- CHANGELOG.md | 1 + CLAUDE.md | 6 +- README.md | 2 +- .../ADR-263-ruview-npm-harness-deep-review.md | 191 ++++++++++++++++++ ...264-rvagent-mcp-and-cli-npm-deep-review.md | 170 ++++++++++++++++ ...DR-265-ruview-npm-distribution-strategy.md | 123 +++++++++++ docs/adr/README.md | 5 +- 7 files changed, 495 insertions(+), 3 deletions(-) create mode 100644 docs/adr/ADR-263-ruview-npm-harness-deep-review.md create mode 100644 docs/adr/ADR-264-rvagent-mcp-and-cli-npm-deep-review.md create mode 100644 docs/adr/ADR-265-ruview-npm-distribution-strategy.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4173107f..beedbd1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **`homecore-recorder` security review (ADR-132 surfaces) — two real bounding fixes; SQL-injection & NaN-index dimensions confirmed clean with evidence.** Beyond-SOTA review of the HA-compat state recorder (DB persistence + history + ruvector semantic search), the crux being its DB-backed SQL-injection surface. **Findings + fixes:** (1) **Memory-DoS — unbounded `get_state_history`.** The history query carried no `LIMIT`, so a wide `[since, until]` window over a high-frequency entity (a per-second sensor ≈ 86k rows/day) would load an unbounded row set into a single in-memory `Vec`. Added a hard `LIMIT MAX_HISTORY_ROWS` (1,000,000 — generous enough never to truncate a realistic history graph, bounded enough to cap the worst case); the sibling search paths were already `k`-bounded. (2) **Disk-DoS / documented-but-missing `purge`.** The README + HA-compat table advertised `Recorder::purge(older_than)` as a capability, but **no such method existed** — i.e. no retention path at all → unbounded disk growth. Implemented a **transactional** `purge` that deletes `states` + `events` strictly **older than** the cutoff (**exclusive** boundary — idempotent, no off-by-one; a row at the cutoff instant is kept) and **garbage-collects** orphaned `state_attributes` blobs (a dedup-shared blob is dropped only once its last referencing state is gone); all three deletes run in one transaction so a mid-purge failure rolls back cleanly (no states-deleted-but-events-kept corruption). **Confirmed clean with evidence:** SQL injection — **every** query in `db.rs` uses bound `?` parameters (no `format!`/string-concat of user data into SQL); the lone `format!` builds the LIKE *pattern*, which is itself bound as a parameter with `ESCAPE '\\'` and metacharacter escaping. Pinned: a state value `'; DROP TABLE states; --` is stored/queried **literally** (table survives), and a `%`/`_` in a search query matches **literally**, not as a wildcard. NaN-index poisoning (the calibration/vitals/geo class) — **structurally impossible** here: embeddings are SHA-256 → `i32` → `f32` (an `i32` cast to `f32` is always finite, never NaN/Inf), with an all-zero-digest norm guard; probed empty-index search, empty-string query, and `k=0` — all return `Ok(0)`, **no panic**. Fail-closed write path — a removal event yields `Ok(None)`, semantic-index failure is logged not propagated (best-effort, never blocks the durable SQLite write), and `EntityId` parsing failures fall back rather than panic. **6 new pinning tests** (SQL-injection literal-storage, LIKE-metacharacter literalness, history `LIMIT`, purge exclusive-boundary, purge attribute-GC-keeps-shared, purge old-events): `homecore-recorder` **19 → 25** (`--no-default-features`) / **25 → 31** (`--features ruvector`), 0 failed; the purge-boundary test is a true pin (fails deleting 2 rows under an inclusive cutoff, passes deleting 1 under the exclusive cutoff). Behaviour otherwise unchanged; Python deterministic proof unchanged (recorder is off the signal proof path). ### Added +- **ADR-263/264/265: deep review of the RuView npm surface (`@ruvnet/ruview`, `@ruvnet/rvagent`, `@ruv/ruview-cli`) with optimization strategies recorded as ADRs.** ADR-263 reviews the published `@ruvnet/ruview@0.1.0` harness: fail-open `claim-check` on empty input (HIGH), `spawnSync` head-of-line blocking of the MCP stdio server during long `verify`/`calibrate` runs (HIGH), optionalDependencies tripling the cold `npx` install for a code path that never uses them (MEASURED, `npm i` in a clean prefix: 4 packages / 620 kB / 71 files default vs 1 package / 172 kB / 22 files with `--omit=optional`), 1 MiB `maxBuffer` truncation risk, `python -c` port-interpolation surface in `node_monitor`, hardcoded MCP server version, duplicated skill payload — optimizations O1–O8. ADR-264 reviews `@ruvnet/rvagent@0.1.0` + the private CLI **against the published registry tarball**: `exports.require` → nonexistent `dist/index.cjs` (HIGH, every CJS consumer breaks), 44 dead source-map files = 62,698 B of the 188 kB unpacked payload pointing at unshipped `../src` (MEASURED), stdio-only server described as "dual-transport" (CLAIMED capability), mixed dot/underscore tool naming, double Zod validation + hand-duplicated advertised schemas, 2-fd leak per training job, unbounded request body in the unwired HTTP scaffold, dead `detectCogBinary` candidate list, `ruview` bin-name collision — optimizations O1–O9. ADR-265 adds the cross-cutting distribution layer: an `npm-packages.yml` CI matrix (tests + pack-content/size gate + tarball-install smoke test — none of the three packages currently has any CI, and `ci.yml` pins Node 18 against `engines >= 20`), publish-from-CI-only with `npm publish --provenance`, version single-sourcing from package.json, bin/namespace ownership (the `ruview` bin belongs to `@ruvnet/ruview`), and claim-check enforcement on package READMEs/descriptions. Docs only — no runtime code changed; the findings are the work orders for the follow-up PRs. - **ADR-131 §11–§12: HOMECORE-UI wired to a real backend — single-origin BFF gateway + production front-end (no mock in prod).** Implements the §11 wiring decision so the dashboard stops rendering fabricated data. **Front-end (DONE + verified under Node):** `api.js` rewritten so every data accessor is async and calls the §11.2 gateway routes; the in-browser mock is demoted to a **dev-only fixture** reachable only via `?demo=1`/`HOMECORE_UI_DEMO` (§2.2); all ten panels now `await` and render a **typed empty/error state** on upstream failure (no mock fallback in production) — 3 panels converted by hand, 7 via a parallel agent swarm. **New `homecore-server` BFF gateway (`src/gateway.rs`, compile-pending — no Rust toolchain in the authoring env):** promotes `homecore-server` to the single origin (§2.1); adds `/api/homecore/*` + `/api/cal/*` merged into `build_app`, with `reqwest` + CLI/env flags (`--calibration-url`/`--calibration-token`/`--apps-dir`/`--gateway-timeout-ms`). Real handlers: calibration **reverse-proxy** (W2), `GET /api/homecore/rooms` with the §11.3 **RoomState adapter** (`breathing`→`breathing_bpm`, `heartbeat`→`heart_bpm`, `None`→`null` preserving not-trained-vs-withheld, injected `anomaly.threshold`/`room_id`), **COG supervisor** over `/var/lib/cognitum/apps/` (W4), and **appliance metrics** from `/proc` + TCP service probes (W6); SEED-device/appliance routes (seeds/federation/witness/privacy/settings/automations/events-history/hailo/tokens — W3/W5) return a typed `503 upstream_unavailable` and the UI shows error states. **Tests:** front-end **5 files green** — import-graph, boot, render-smoke (22), interaction (3), and a **new prod-errors suite (13)** that runs with demo OFF + gateway unreachable and proves every panel renders an error state, never mock, never throws (it caught + fixed a real unhandled-rejection in the events automation builder). **Gateway compiled, tested, and run on Rust 1.89:** `cargo test -p homecore-server --no-default-features` = **12/12 pass** (6 gateway + 6 UI mount); the binary was **run live** — `GET /api/homecore/appliance` returns real `/proc` metrics + TCP service probes, unauth → `401`, `cogs` → `[]` (no apps dir), SEED-tier → typed `503`, and against a mock calibration upstream the `/api/cal/*` proxy passes through (`200`) and `GET /api/homecore/rooms` adapts `RoomState` to the UI shape (`breathing`→`breathing_bpm`, `heartbeat:null`→`heart_bpm:null`, injected `anomaly.threshold`/`room_id`). **Live testing caught + fixed a real bug** — a double-`v1` segment in the `/api/cal/*` proxy URL. **Remaining (intrinsic, not an env limit):** W3/W5/W6-Hailo/federation depend on services/hardware **not in this repo** (recorder/automation HTTP wrappers, real SEED nodes, Hailo stat source), so they return honest `503`s rather than fabricate data; W1/W2/W4/W6-appliance are functional now. ADR-131 §10/§12.1 updated with per-wave status. - **ADR-131: HOMECORE-UI — the complete operational dashboard for the two-tier Cognitum stack, served by `homecore-server` at `/homecore`.** A zero-dependency, no-build-step vanilla TS/JS + CSS frontend (the `rufield-viewer` "Axum + vanilla-JS" pattern) that extends the Cognitum Appliance shell as a first-class nav section (Framework | Guide | Cog Store | **HOMECORE** | Status). **Complete, not a scaffold** (per the ADR's revised §2/§7): all **10 panels** ship fully built and rendered — §4.1 System Dashboard (v0 Appliance health strip + SEED fleet grid + ESP32 summary + COG status row + event-bus sparkline), §4.2 SEED Detail (vector store / witness chain / 5 onboard sensors / reflex rules / cognitive-fragility / ingest packet-type), §4.3 SEED Fleet Map (Appliance→SEED→ESP32 hierarchy, ESP-NOW mesh, cross-SEED fusion badges, ADR-105 federation), §4.4 Entity & State Browser (domain-grouped, **live WebSocket `subscribe_events` patching — never polls**, first-class provenance badges, keyword filter, context-causality slide-over), §4.5 RoomState/Sensing (mixture-of-specialists), §4.6 COG Management + App Registry, §4.7 Calibration Wizard (5-step baseline→enroll→train→verify), §4.8 Event Bus + Automation builder, §4.9 Witness/Audit log (two-tier SHA-256 + Ed25519 timeline, privacy-mode banner, pagination, export), §4.10 Settings. **Design system is the exact production Cognitum palette** (`tokens.css` carries `--cyan #4ecdc4` … `--r 10px` verbatim, §3.1) so there is no visual seam with the Cog Store (§3.3 invariant). **§6 UX invariants enforced in code and pinned by tests:** tier-origin provenance is always-visible (never collapsed); `stale`/`vetoed` flags and the kNN fragility score are prominent (amber/red tint + banners, never grey-on-grey); a `null` specialist renders "Not trained / calibrate to enable" **visually distinct from** veto-`withheld` (rendered as explicitly withheld, never zero) **distinct from** an error; all IDs/hashes/endpoints/payloads use `--mono`; Hailo-sourced COGs (`arch: hailo10`) are visually distinguished from CPU-only (`arch: arm`). **Wiring:** `homecore-server` gains a `--ui-dir`/`HOMECORE_UI_DIR` flag and mounts the assets via `tower-http` `ServeDir` at `/homecore` alongside the unchanged HA-compat `/api` surface (new testable `build_app()`), with **5 Rust integration tests** (`#[cfg(test)] mod ui_tests`, `tower::oneshot`) asserting index / design tokens / all-10-panels are served, the API coexists, and an empty `--ui-dir` disables the mount. **JS test + benchmark suite (`ui/`, runs under plain `node`, no npm install): 24 checks / 0 failed** — an import/export graph verifier (15 modules consistent), a DOM-shim render-smoke that *executes every panel* (21 checks: ui helpers + mock contracts + all 10 panels render without throwing), and an interaction suite (3 checks: live WS state-patch, ws.js handshake/parse, calibration backend contract). **Benchmark:** total bundle **136.8 KB uncompressed across 18 files — ~37× smaller than HA's ~5 MB Lit bundle** (the ADR-126 §1.1 foil), slowest panel **1.5 ms/cold-render**. **Honest scope (§7.1):** the live HOMECORE REST API (`/api/config|states|services`) and the WebSocket `subscribe_events` feed are driven for real; panels whose backing service is **not** in this binary (SEED HTTPS API, calibration ADR-151, ADR-105 federation) render against a **contract-conformant mock layer flagged with a DEMO banner** and swap to live the moment those endpoints land — no mock data is ever presented as real. **Not verified in this environment:** the Rust crate was edited and the integration tests written but **not compiled/run here** (no Rust toolchain present); `cargo test -p homecore-server` + `cargo build` must be run on a Rust host before merge. - **ADR-175: int8 quantization of the WiFlow-STD "half" pose model — MEASURED fp32-vs-int8 accuracy/size trade-off (honest negative).** Sub-deliverable 8.2 of the benchmark/optimization milestone, and the reading of the SOTA brief's "one untested edge lever" (QAT-int8 on the 843,834-param half model that strictly dominates the published 2.23M model). A new committed script `v2/crates/wifi-densepose-train/scripts/quantize_half_int8.py` quantizes `half_best.pth` to int8 two ways and scores both with the **same** upstream `calculate_pck`/`calculate_mpjpe` that produced the fp32 sweep numbers, under **one locked normalization** (ADR-173 torso-diameter PCK — neck idx2→pelvis idx12, `use_torso_norm=True`, the standard MM-Fi/GraphPose-Fi convention), on the **same** seed-42 file-level 70/15/15 test split (52,560 NaN-free / 54,000 full windows). **MEASURED on ruvultra (RTX 5080, torch 2.11.0+cu128, fbgemm; clean test, torso-PCK):** fp32 = 96.62% PCK@20 / 99.47% PCK@50 / 0.008981 MPJPE / 3.351 MB (fp32-CPU reproduces fp32-GPU to 4 dp, so the int8 deltas are pure quantization, not CPU/GPU drift); **int8 static PTQ = 40.98% PCK@20 (−55.64 pp), 1.046 MB** — naive static QDQ **collapses** on this model (the brief's 2.23M "sweet spot" does NOT transfer to the 843k half model at the tight @20 threshold); **int8 QAT (3-epoch FX fake-quant fine-tune from half_best) = 67.48% PCK@20 (−29.15 pp) / 98.69% PCK@50 (−0.78 pp), 1.043 MB.** **Verdict (honest no):** int8 is **not a win** at the strict PCK@20 edge target — QAT recovers a large share of the PTQ collapse and is near-lossless at the loose PCK@50 (coarse localization survives int8, fine does not), but a **3.2× size win at −29 pp PCK@20** is a bad trade when the half model already fits edge flash at fp32 → **keep fp32/fp16 on the edge for now.** **Disclosed gap:** the QAT *fake-quant* val PCK@20 reached 83.45% but the *converted* int8 model scores 67.48% — a real ~16 pp `convert_fx` gap (fbgemm int8 kernels ≠ straight-through estimate, esp. the axial-attention einsum/softmax); we report the converted-int8 number, not the fake-quant proxy. **MEASURED:** every table number + the PTQ collapse + the QAT partial recovery + the conversion gap. **CLAIMED/not done:** ONNX/TFLite export, on-edge-SoC latency/energy (int8 measured on x86 fbgemm — size transfers, latency does NOT), mixed-precision keeping attention fp32, longer/better-tuned QAT. **Honest limitations:** single in-domain eval split (no cross-environment split), x86-int8 not edge-SoC-int8, lightly-tuned QAT. Additive only — no production Rust or signal-pipeline change; Python deterministic proof unchanged (`f8e76f21…46f7a`, bit-exact — off the signal proof path). diff --git a/CLAUDE.md b/CLAUDE.md index 226d09a4..262884dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -62,7 +62,7 @@ All 5 ruvector crates integrated in workspace: - `ruvector-attention` → `model.rs` (apply_spatial_attention) + `bvp.rs` ### Architecture Decisions -43 ADRs in `docs/adr/` (ADR-001 through ADR-043). Key ones: +182 ADRs in `docs/adr/` (numbered ADR-001 through ADR-265, with gaps). Key ones: - ADR-014: SOTA signal processing (Accepted) - ADR-015: MM-Fi + Wi-Pose training datasets (Accepted) - ADR-016: RuVector training pipeline integration (Accepted — complete) @@ -77,6 +77,10 @@ All 5 ruvector crates integrated in workspace: - ADR-148: Drone swarm control system / `ruview-swarm` (In Progress) - ADR-152: WiFi-Pose SOTA 2026 intake — geometry conditioning, WiFlow-STD benchmark (measurement (a) complete: claims MEASURED-EQUIVALENT at ~96% PCK@20), MAE recipe (Proposed; §2.1–2.3, 2.6 implemented) - ADR-153: IEEE 802.11bf-2025 forward-compatibility protocol model (Accepted — amends ADR-152 §2.4) +- ADR-182: `npx ruview` harness minted via MetaHarness (Accepted — P1+P2 shipped as `@ruvnet/ruview`) +- ADR-263: `@ruvnet/ruview` npm harness deep review + optimization strategy (Proposed) +- ADR-264: `@ruvnet/rvagent` MCP server + `@ruv/ruview-cli` deep review + optimization strategy (Proposed) +- ADR-265: RuView npm distribution strategy — CI gate, provenance, version single-sourcing (Proposed) ### Supported Hardware diff --git a/README.md b/README.md index 8e23fdb8..7b8c77e4 100644 --- a/README.md +++ b/README.md @@ -617,7 +617,7 @@ Verify the plugin structure: `bash plugins/ruview/scripts/smoke.sh`. Full detail | [Semantic Primitives — Precision/Recall](docs/integrations/semantic-primitives-metrics.md) | Per-primitive F1 on the held-out paired-capture set: someone-sleeping, possible-distress, room-active, elderly-inactivity-anomaly, meeting, bathroom, fall-risk, bed-exit, no-movement, multi-room. | | [Claude Code / Codex Plugin](plugins/ruview/README.md) | The `ruview` plugin + marketplace — skills, `/ruview-*` commands, agents, and the Codex prompt mirror | | [Portable harness — `npx @ruvnet/ruview`](harness/ruview/README.md) | MetaHarness-minted, host-portable RuView operator harness — `ruview.*` MCP tools + the MEASURED-vs-CLAIMED honesty guardrail enforced in code ([ADR-182](docs/adr/ADR-182-npx-ruview-harness-via-metaharness.md)). A lighter, multi-host companion to the in-repo plugin. | -| [Architecture Decisions](docs/adr/README.md) | 96 ADRs — why each technical choice was made, organized by domain (hardware, signal processing, ML, platform, infrastructure) | +| [Architecture Decisions](docs/adr/README.md) | 182 ADRs — why each technical choice was made, organized by domain (hardware, signal processing, ML, platform, infrastructure) | | [Domain Models](docs/ddd/README.md) | 8 DDD models (RuvSense, Signal Processing, Training Pipeline, Hardware Platform, Sensing Server, WiFi-Mat, CHCI, rvCSI) — bounded contexts, aggregates, domain events, and ubiquitous language | | [rvCSI — edge RF sensing runtime](https://github.com/ruvnet/rvcsi) | Rust-first / TypeScript-accessible / hardware-abstracted CSI runtime: multi-source ingestion (incl. real nexmon_csi `.pcap` from a **Raspberry Pi 5** / Pi 4 / Pi 3B+ — CYW43455 / BCM43455c0) → validation → DSP → typed events → RuVector RF memory ([ADR-095](docs/adr/ADR-095-rvcsi-edge-rf-sensing-platform.md), [ADR-096](docs/adr/ADR-096-rvcsi-ffi-crate-layout.md), [domain model](docs/ddd/rvcsi-domain-model.md)). Now its own repo — [`ruvnet/rvcsi`](https://github.com/ruvnet/rvcsi) — vendored here under `vendor/rvcsi`; 9 `rvcsi-*` crates on crates.io, `@ruv/rvcsi` on npm, plus a Claude Code plugin. | | [Desktop App](v2/crates/wifi-densepose-desktop/README.md) | **WIP** — Tauri v2 desktop app for node management, OTA updates, WASM deployment, and mesh visualization | diff --git a/docs/adr/ADR-263-ruview-npm-harness-deep-review.md b/docs/adr/ADR-263-ruview-npm-harness-deep-review.md new file mode 100644 index 00000000..76c4a6a0 --- /dev/null +++ b/docs/adr/ADR-263-ruview-npm-harness-deep-review.md @@ -0,0 +1,191 @@ +# ADR-263: `@ruvnet/ruview` npm Harness — Deep Review + Optimization Strategy + +| Field | Value | +|-------|-------| +| **Status** | Proposed — review complete (findings recorded with evidence), optimizations O1–O8 not yet implemented | +| **Date** | 2026-07-02 | +| **Deciders** | ruv | +| **Codename** | **RUVIEW-NPM-REVIEW-1** | +| **Supersedes / amends** | none (records review of the ADR-182 P1+P2 artifact; feeds ADR-265 distribution strategy) | + +## Context + +ADR-182 minted and published **`@ruvnet/ruview@0.1.0`** (`harness/ruview/`) — the +`npx ruview` operator harness: a dependency-free ESM CLI + minimal MCP stdio server +exposing six `ruview.*` tools (onboard / claim_check / verify / node_monitor / +calibrate / node_flash), five skill playbooks, and the executable +MEASURED-vs-CLAIMED guardrail (`src/guardrails.js`). The package is live on npm +(0.1.0, 49.5 kB unpacked / 21 files — MEASURED, `npm view @ruvnet/ruview` + +`npm pack --dry-run`) and is the recommended MCP registration path +(`npx -y @ruvnet/ruview mcp start` in the bundled `.claude/settings.json`). + +This ADR is the first dedicated deep review of that npm artifact: correctness, +fail-open/fail-closed posture, performance (cold start + request handling), +packaging hygiene, and security of the subprocess surface. All 17 bundled tests +pass on Node 22 (MEASURED, `node --test test/*.test.mjs`, 17/17, ~108 ms). + +## Findings + +Severity reflects impact on the package's stated contract: *fail-closed operator +tools + an honesty guardrail that must never fail open*. + +### F1 (HIGH, fail-open): `claim-check` passes silently on empty input + +`bin/cli.js` `claim-check` with **neither `--text` nor `--file`** sends +`text: undefined` → `claimCheck(String(args.text ?? ''))` → `''` → `ok: true`, +**exit 0**. A CI hook wired as `npx ruview claim-check --text "$BODY"` where +`$BODY` expands empty therefore reports PASS. This is the single tool whose whole +purpose is to fail closed; empty input must be an error, not a pass. +Reproducer: `node bin/cli.js claim-check` → `{"ok": true}`, exit 0. + +### F2 (HIGH, head-of-line blocking): MCP server is fully synchronous + +`src/mcp-server.js` dispatches `tools/call` inside the readline `line` handler, +and every heavyweight handler in `src/tools.js` uses **`spawnSync`** +(`ruview.verify` up to 180 s, `ruview.calibrate` up to 300–600 s, +`ruview.node_monitor` up to `seconds+10`). While one call runs, the event loop is +blocked: `ping`, `tools/list`, and concurrent `tools/call` requests are not even +read from stdin. Hosts that health-check with `ping` during a long `calibrate` +will conclude the server is dead and kill it mid-run. + +### F3 (MEDIUM, cold start): optionalDependencies triple the `npx` install for a path that never uses them + +`package.json` declares `optionalDependencies` on `@metaharness/kernel` and +`@metaharness/host-claude-code`. npm installs optional deps **by default**, so +every cold `npx -y @ruvnet/ruview mcp start` fetches 3 extra packages (kernel + +host + transitive `@ruvector/emergent-time`). MEASURED (npm 10.9.7, this +container): default install = **4 packages, 620 kB, 71 files**; with +`--omit=optional` = **1 package, 172 kB, 22 files**. The operator-tool and MCP +paths never import these — only `doctor`/`install` do, and both already +dynamic-import inside `try/catch` and degrade gracefully when absent +(`kernel/host: not installed (ok…)`). The optional deps buy nothing on the hot +path and cost 3 registry round-trips + ~450 kB on every cold start. + +### F4 (MEDIUM, silent truncation): `spawnSync` default `maxBuffer` (1 MiB) + +`run()` in `src/tools.js` never sets `maxBuffer`. `cargo run -p +wifi-densepose-cli` (the `calibrate` fallback path) and a chatty `verify.py` can +exceed 1 MiB of stdout, at which point the child is killed with `ENOBUFS` and the +tool reports a spawn error that looks like a proof/calibration failure. The +handlers only ever consume the last 8 kB/1.5 kB; buffering should be bounded but +generous (e.g. `maxBuffer: 16 MiB`) or streamed with a tail ring. + +### F5 (MEDIUM, injection surface): `node_monitor` interpolates the port into Python source + +The handler builds a `python -c` script by string interpolation: +`` `ser=serial.Serial(${JSON.stringify(port)},115200,…)` `` and +`` `while time.time()-t<${dur}:` ``. `JSON.stringify` produces a *JavaScript* +string literal; Python string-literal semantics differ at the edges (`\uXXXX` is +shared, but e.g. JS emits raw U+2028/U+2029 unescaped pre-ES2019 rules aside, and +any future non-JSON-safe field added the same way would be executable). `port` +arrives from the MCP caller (an agent), so this is an agent-controlled string +concatenated into an interpreter invocation. `dur` is `Number()`-guarded; `port` +should be passed out-of-band (`sys.argv`/env), never spliced into source. + +### F6 (LOW, drift): server version hardcoded + +`SERVER_INFO = { name: 'ruview', version: '0.1.0' }` in `src/mcp-server.js` +duplicates `package.json.version` (the CLI's `--version` already reads +package.json at runtime). First release bump will drift the MCP handshake +version. + +### F7 (LOW, duplication): every skill ships twice + +`skills/*.md` and `.claude/skills/*/SKILL.md` are byte-identical (same sha256 in +`.harness/manifest.json`). ~8 kB of the 49.5 kB unpacked payload is duplicate +content, and — worse than size — two copies must be kept in sync by hand. + +### F8 (LOW, perf + portability): `which()` is uncached and shells out + +`which()` runs up to twice per tool call (`python` then `python3`), each a +blocking `spawnSync`; the POSIX branch spawns a shell (`shell: true`). Results +are stable for the process lifetime and should be memoized; the lookup can be +done dep-free with a PATH scan instead of a shell. + +### F9 (LOW, interop): dot-named tools + minimal protocol surface + +Tool names (`ruview.onboard`, `ruview.claim_check`, …) contain dots. MCP itself +does not restrict names, but downstream host APIs commonly enforce +`^[a-zA-Z0-9_-]{1,64}$` for tool names; hosts must then sanitize or reject. +The server also answers `resources/list` / `prompts/list` with `-32601` (it does +not advertise those capabilities, so this is spec-legal, but empty-list stubs are +cheaper than every host's error path). Protocol version is pinned to +`2024-11-05` with no negotiation fallback. None of this breaks Claude Code today; +it narrows portability, which is the harness's whole pitch (9 hosts, ADR-182). + +### F10 (LOW, CI gap): the published package has zero CI + +No workflow under `.github/workflows/` runs `harness/ruview` tests (checked: +no workflow references `harness/ruview`, `ruview-mcp`, or `ruview-cli`), and +`ci.yml` pins `NODE_VERSION: '18'` while the package declares +`engines.node >= 20`. Note also `node --test test/` (directory form) fails on +Node 22 while the documented glob form passes — CI should pin the working +invocation. Consolidated CI/publish strategy is ADR-265. + +### F11 (MEDIUM, guardrail precision): `METRIC_TERMS` substring matching false-positives on ordinary prose + +Found by dogfooding this review: `claimCheck` matches metric terms with +`lower.includes(t)`, so the two-character terms `'map'` and `'f1'` fire inside +ordinary words and labels — "source **map**s", "the **map**s can never +resolve", finding IDs like "**F1** (HIGH…)". MEASURED reproducer: running +`npx ruview claim-check --file` over this ADR and ADR-264 yields 4 and 16 +medium findings respectively, the majority of which are `map`/`F1` +false positives on lines carrying no accuracy claim. A guardrail that cries +wolf trains people to ignore it — precision is part of its fail-closed +contract. Short/ambiguous terms need word-boundary matching (`\bmap\b`, +`\bf1\b`, likewise `auc`, `iou`), and section-heading label patterns +(`F\d+`, `O\d+`) should not count as metric mentions. + +## Decision + +Adopt the following optimization strategy, in priority order. Each item is +independently shippable; F-numbers map to findings. + +- **O1 (F1):** `claim-check` with no `--text`/`--file` (or empty text after read) + exits 2 with a usage error. Add a regression test pinning exit ≠ 0. +- **O2 (F2):** make the MCP dispatch async: convert `run()`/`which()` to + promise-based `spawn`, make `tools/call` handlers `async`, and keep reading + stdin while calls run (respond to `ping`/`tools/list` concurrently; serialize + only same-tool hardware operations). Acceptance: `ping` round-trips < 50 ms + while a synthetic 30 s `calibrate` is in flight. +- **O3 (F3):** drop the two `optionalDependencies`; `doctor`/`install` already + degrade and should print the exact `npm i @metaharness/kernel + @metaharness/host-claude-code` hint on the miss path. Acceptance: cold + `npm i @ruvnet/ruview` installs exactly 1 package (MEASURED baseline above). +- **O4 (F4):** set `maxBuffer: 16 * 1024 * 1024` in `run()` (or stream + tail). +- **O5 (F5):** pass `port` to the monitor script via `sys.argv` + (`python -c script -- `), never by source interpolation. +- **O6 (F6):** read the MCP `serverInfo.version` from `package.json` once at + startup (same pattern the CLI already uses). +- **O7 (F7):** make `skills/*.md` the single source and generate + `.claude/skills/*/SKILL.md` in a `prepack` script (or vice versa); manifest + hashes then pin one canonical set. +- **O8 (F8, F9):** memoize `which()`; add underscore aliases for the dot-named + tools (accept both in `tools/call`, advertise the underscore form) and add + empty `resources/list` / `prompts/list` stubs. +- **O9 (F11):** switch `METRIC_TERMS` matching to word-boundary regexes for + short terms (`map`, `f1`, `auc`, `iou`) and skip label tokens matching + `\b[FO]\d+\b`. Acceptance: `claim-check --file` over ADR-263/264/265 reports + only the genuinely tagged-or-taggable percentage lines, and the existing 17 + guardrail tests still pass plus new false-positive pins ("source maps", + "F1 (HIGH)" → no finding). + +Non-goals: no new runtime dependencies (the zero-dep MCP server is a feature, +not an accident — keep it), no build step, no change to the fail-closed tool +contracts. + +## Consequences + +- The honesty guardrail becomes fail-closed end-to-end (its current empty-input + pass is the exact failure mode the guardrail exists to prevent). +- `npx` cold start drops ~450 kB / 3 packages (MEASURED baseline in F3) with no + feature loss; `doctor` output already communicates the optional-dep story. +- Long-running `verify`/`calibrate` no longer starve the MCP channel — the + harness survives host health checks during real calibration runs. +- Two-copy skill drift becomes impossible at pack time. +- Costs: async conversion touches every handler signature in `src/tools.js` + (mechanical, ~6 handlers); alias tools add a small compatibility table. +- Verification for the implementing PR: bundled tests extended for O1/O2/O5 + (target ≥ 20 tests), `npm pack --dry-run` file-count asserted, and the F3 + install measurement re-run and quoted MEASURED in the PR body — which must + itself pass `npx ruview claim-check`. diff --git a/docs/adr/ADR-264-rvagent-mcp-and-cli-npm-deep-review.md b/docs/adr/ADR-264-rvagent-mcp-and-cli-npm-deep-review.md new file mode 100644 index 00000000..f5774695 --- /dev/null +++ b/docs/adr/ADR-264-rvagent-mcp-and-cli-npm-deep-review.md @@ -0,0 +1,170 @@ +# ADR-264: `@ruvnet/rvagent` MCP Server + `@ruv/ruview-cli` — Deep Review + Optimization Strategy + +| Field | Value | +|-------|-------| +| **Status** | Proposed — review complete (findings verified against the published 0.1.0 tarball), optimizations O1–O9 not yet implemented | +| **Date** | 2026-07-02 | +| **Deciders** | ruv | +| **Codename** | **RUVIEW-NPM-REVIEW-2** | +| **Supersedes / amends** | none (reviews the ADR-104/ADR-124 artifacts; feeds ADR-265 distribution strategy) | + +## Context + +Two TypeScript npm packages expose RuView sensing to agents and shells: + +- **`@ruvnet/rvagent@0.1.0`** (`tools/ruview-mcp/`) — SENSE-BRIDGE, the MCP + server over the sensing-server HTTP API + cog binaries: 12 tools + (csi/pose/count/registry/train/job + ADR-124 BFLD/presence/vitals). Published + (188 kB unpacked — MEASURED, `npm view @ruvnet/rvagent`). Deps: + `@modelcontextprotocol/sdk` + `zod`. +- **`@ruv/ruview-cli@0.0.1`** (`tools/ruview-cli/`) — `private: true` yargs CLI + mirroring the same capabilities; intentionally duplicates `http.ts`/`cog.ts`/ + `config.ts` (~150 lines) to stay standalone. + +This ADR records a deep review of both: packaging correctness (verified against +the **published** tarball, not just the source tree), protocol/interop, resource +lifecycle, and the honesty of the package's own self-description — the same +MEASURED-vs-CLAIMED bar the project applies to accuracy numbers. + +## Findings + +### F1 (HIGH, broken export): `require` condition points at a file that does not exist + +`package.json` `exports["."].require = "./dist/index.cjs"`, but the build is +plain `tsc` (ESM only) and **the published 0.1.0 tarball contains no +`index.cjs`** (verified by listing the registry tarball). Any CJS consumer doing +`require('@ruvnet/rvagent')` resolves to a nonexistent file → +`ERR_MODULE_NOT_FOUND`. Additionally the `types` condition is listed **after** +`import`/`require`; TypeScript requires `types` first or it may be ignored under +`moduleResolution: bundler/node16`. + +### F2 (MEDIUM, tarball bloat): a third of the published package is dead source maps + +The 0.1.0 tarball ships **44 `.map` files = 62,698 B** against 78,209 B of +actual `.js` (MEASURED, extracted registry tarball). `src/` is not published, so +every `sourceMappingURL` points at `../src/*.ts` that consumers do not have — +the maps can never resolve. Also `files` lists `CHANGELOG.md`, which does not +exist in `tools/ruview-mcp/` (npm silently skips it), so the advertised file set +is partly fictional. + +### F3 (MEDIUM, honesty): the package description claims a transport it does not start + +The description reads "**dual-transport MCP server (stdio + Streamable HTTP)**", +but `main()` in `src/index.ts` wires **stdio only**. `http-transport.ts` is a +complete, tested scaffold that nothing imports at runtime — there is no flag, +env var, or subcommand that starts it. By this project's own rule this is a +CLAIMED capability presented as shipped. Either wire it (`--http` / +`RVAGENT_HTTP_PORT` gate) or de-claim the description until it is. + +### F4 (MEDIUM, interop + inconsistency): two tool-naming conventions, one of them dot-based + +Six tools use `ruview_snake_case`; six (ADR-124 additions) use +`ruview.dotted.names`. Same interop caveat as ADR-263 F9 (host tool-name +regexes commonly `^[a-zA-Z0-9_-]{1,64}$`), plus the split convention makes the +tool surface look like two products. Standardize on underscores and accept the +dotted forms as aliases for one deprecation cycle. + +### F5 (MEDIUM, double work + drift): every tool input is validated twice from two hand-maintained schemas + +`CallToolRequestSchema` handler runs `TOOL_INPUT_SCHEMAS[name].safeParse(args)`, +then each tool handler runs its own `schema.parse(args)` again — two full Zod +passes per call. Separately, the `inputSchema` JSON advertised via `tools/list` +is **hand-written** and duplicates the Zod schema field-by-field (defaults, +min/max, descriptions) — schema drift between what is advertised and what is +enforced is a matter of time. Parse once at the gate, pass the typed result to +handlers, and generate the advertised JSON Schema from the Zod source +(`zod-to-json-schema` at build time, or Zod 4's native `z.toJSONSchema` when the +SDK's peer range allows). + +### F6 (MEDIUM, resource lifecycle): `train_count` leaks 2 fds per job; job registry is process-local + +`trainCount` opens `logFdOut`/`logFdErr` with `openSync` and never closes them +in the parent — the spawned cargo child inherits duplicates, but the parent's +descriptors stay open for the MCP server's lifetime: 2 leaked fds per training +job. `jobRegistry` is an in-memory `Map`, so `ruview_job_status` after a server +restart reports "not found" for a training run that is still burning GPU (the +source comments acknowledge this; the fix — persist `~/.ruview/jobs/.json`, +already the documented layout — is small). Also `jobStatus` re-`import`s +`node:fs` on every poll and reads the entire log to return 20 lines. + +### F7 (MEDIUM, security/robustness of the HTTP scaffold): unbounded body + one shared session transport + +`http-transport.ts` buffers the request body with no size cap (memory DoS the +moment it is wired to a socket), reuses a **single** +`StreamableHTTPServerTransport` with `sessionIdGenerator` for all clients (the +SDK's stateful mode expects one transport per session — a second client's +`initialize` collides), and the Origin allowlist is exact-match +(`http://localhost` will not match a real browser origin `http://localhost:5173`). +Must be fixed **before** F3 wires it in; bearer-token + 127.0.0.1 defaults are +already right. + +### F8 (LOW, dead/misleading code): `detectCogBinary` always returns the bare name + +It builds a 4-candidate appliance-path array and then returns +`candidates[candidates.length - 1]` — i.e. always `name` — without checking +existence. The candidates are dead weight that reads as if path detection +happens. Either probe with `existsSync` or delete the array. + +### F9 (LOW, drift + hygiene): hardcoded versions, unused/mismatched devDeps, bin-name collision + +`PACKAGE_VERSION = "0.1.0"` (index.ts) duplicates package.json; +`@types/express` is unused (`http-transport` uses `node:http`); `@types/jest@30` +against `jest@29`; `ruview-cli` hardcodes `.version("0.0.1")`. And +`@ruv/ruview-cli` claims the **`ruview`** bin name, which collides with +`@ruvnet/ruview`'s bin (ADR-182) if both are ever installed globally — +ADR-263/265 give the `ruview` name to the harness; the CLI must rename or fold. + +## Decision + +- **O1 (F1):** fix `exports`: drop the `require` condition (ESM-only is fine for + a bin-first package) or add a real CJS build; put `types` first. Add a CI + smoke test that does `npm pack` + `node -e "import('')"`. +- **O2 (F2):** publish without maps: `declarationMap: false`, `sourceMap: false` + in a `tsconfig.build.json` used by `prepack` (or add `!dist/**/*.map` to + `files`). Remove the phantom `CHANGELOG.md` entry or create the file. + Acceptance: unpacked size ≤ ~125 kB (from 188 kB, −62.7 kB MEASURED map + weight). +- **O3 (F3, F7):** wire the HTTP transport behind an explicit opt-in + (`RVAGENT_HTTP_PORT` or `--http`), after F7 fixes: per-session transport map + keyed by `mcp-session-id`, 1 MiB body cap, origin matching that honors ports + (compare `URL.origin` prefixes or document exact origins). Until then, change + the description to "stdio MCP server (Streamable HTTP scaffold, unwired)". +- **O4 (F4):** rename dotted tools to underscore (`ruview_bfld_last_scan`, …), + keep dotted aliases in the call router for one release, note it in the README. +- **O5 (F5):** single validation gate: the registry maps name → Zod schema → + typed handler; advertised `inputSchema` generated from Zod at build time. +- **O6 (F6):** close parent fds after spawn (`closeSync` post-`spawn` — the + child holds its own copies), persist job records to + `/.json`, and read log tails with a bounded read. +- **O7 (F8):** make `detectCogBinary` actually probe (`existsSync` over the + candidates) — it is the entire reason the function exists. +- **O8 (F9):** single-source versions from package.json; drop `@types/express`; + align `@types/jest` with jest 29 (or move to `node:test` like the harness and + drop the jest toolchain entirely — it is the heaviest devDep in both + packages). +- **O9 (F9, scope):** fold `@ruv/ruview-cli` into `rvagent` as a second bin + (`rvagent-cli`) sharing `http/cog/config`, or keep it private-forever and say + so in its README. Its `ruview` bin name is surrendered to `@ruvnet/ruview` + either way. + +## Consequences + +- CJS consumers stop hitting a guaranteed-broken export path (F1 is the only + finding that fails for every consumer of that entry point deterministically). +- The published artifact shrinks ~33% (MEASURED, F2 tarball listing: 62,698 B + of maps in a 188 kB unpacked payload) and stops advertising files/transports + it does not contain — the package description itself passes the project's + claim-check bar. +- One schema source ends advertised-vs-enforced drift and halves per-call + validation cost; naming unification makes the 12-tool surface read as one + product and survive strict host tool-name validation. +- Long-lived MCP servers stop accumulating fds during training campaigns, and + job polling survives restarts. +- Costs: the alias cycle (O4) briefly doubles the advertised tool count unless + aliases are router-only (recommended: router-only, advertise underscore names + exclusively); folding the CLI (O9) retires a package name already in use in + scripts, so it needs a deprecation note. +- Verification for the implementing PR: `npm pack --dry-run` asserted file list + (no `.map`, no phantom entries), pack-size budget in CI (ADR-265), jest/`node + --test` suite green, and a tarball-install smoke test for both `import` and + the `rvagent` bin. diff --git a/docs/adr/ADR-265-ruview-npm-distribution-strategy.md b/docs/adr/ADR-265-ruview-npm-distribution-strategy.md new file mode 100644 index 00000000..db8ebd49 --- /dev/null +++ b/docs/adr/ADR-265-ruview-npm-distribution-strategy.md @@ -0,0 +1,123 @@ +# ADR-265: RuView npm Distribution Strategy — CI Gate, Provenance, Version Single-Sourcing, Namespace + +| Field | Value | +|-------|-------| +| **Status** | Proposed | +| **Date** | 2026-07-02 | +| **Deciders** | ruv | +| **Codename** | **RUVIEW-NPM-DIST** | +| **Supersedes / amends** | none (cross-cutting layer above ADR-263 and ADR-264; complements ADR-182 P3/P4) | + +## Context + +The monorepo now ships (or stages) **three Node packages** with no shared +distribution engineering: + +| Package | Dir | Published | Bin(s) | Tests in CI | +|---------|-----|-----------|--------|-------------| +| `@ruvnet/ruview` | `harness/ruview/` | 0.1.0 (live) | `ruview` | **none** | +| `@ruvnet/rvagent` | `tools/ruview-mcp/` | 0.1.0 (live) | `rvagent`, `ruview-mcp` | **none** | +| `@ruv/ruview-cli` | `tools/ruview-cli/` | private | `ruview` (collides) | **none** | + +Cross-cutting facts established during the ADR-263/264 reviews: + +- **Zero CI coverage.** No workflow under `.github/workflows/` references any of + the three directories. Two of the packages are *live on the registry* and were + published from a laptop state CI never saw. Meanwhile the Rust side has a + 1,031+-test gate and a witness-bundle culture (ADR-028) — the npm surface is + the only shipped artifact class with no verification gate at all. +- **`ci.yml` pins `NODE_VERSION: '18'`** while all three packages declare + `engines.node >= 20`. +- **Version triplication.** Each package hardcodes its version in source at + least once beyond package.json (harness `SERVER_INFO`, rvagent + `PACKAGE_VERSION`, cli `.version("0.0.1")`). +- **Bin-name collision.** Two packages claim the `ruview` bin. +- **No provenance.** Neither published package carries npm provenance + attestations, in a project whose differentiator is signed, reproducible + evidence (ADR-028 witness bundles, ADR-182 P4 ed25519/SLSA design). +- **No pack-content gate.** ADR-264 F1/F2 (broken `require` target, 33% dead + map weight — MEASURED, ADR-264 F2 tarball listing — and a phantom + `CHANGELOG.md` in `files`) are exactly the defect class an + `npm pack --dry-run` assertion catches in seconds. + +## Decision + +Adopt one distribution layer for all Node packages. Per-package code fixes live +in ADR-263/264; this ADR fixes the machinery around them. + +### D1 — One `npm-packages.yml` CI workflow (the gate) + +Matrix over `[harness/ruview, tools/ruview-mcp, tools/ruview-cli]` × +Node `[20, 22]`: + +1. `npm ci` (lockfiles are already committed for the TS packages; add one for + the harness — it has no prod deps after ADR-263 O3, so the lockfile is tiny). +2. `npm test` (harness: `node --test test/*.test.mjs` — pin the glob form, + the directory form fails on Node 22; TS packages: build + jest or `node:test` + per ADR-264 O8). +3. **Pack gate:** `npm pack --dry-run --json` asserted against a checked-in + expected file list + a max unpacked-size budget per package (harness ≤ 60 kB; + rvagent ≤ 130 kB post ADR-264 O2). Any new/missing/renamed shipped file is a + reviewed diff, not a surprise. +4. **Tarball smoke test:** install the packed tarball into a temp dir; run + `ruview --version`, `ruview doctor`, `rvagent` `--help`-equivalent, and a + Node `import()` of each declared export condition — this is the test that + would have caught ADR-264 F1 (`require` → nonexistent `dist/index.cjs`). +5. Bump `ci.yml` `NODE_VERSION` to `'20'` (independent of the matrix above). + +### D2 — Publish only from CI, with provenance + +Manual `npm publish` from laptops stops. A tag-triggered workflow +(`ruview-npm-release.yml`, mirroring the firmware release discipline) runs the +D1 gate, then `npm publish --provenance --access public` under the GitHub OIDC +token. Consequence: every published version is attested to a public commit + +workflow run — the npm-side analogue of the ADR-028 witness bundle. The +`prepublishOnly` script in each package runs the pack gate locally as a +belt-and-braces (publishing outside CI fails loudly, not silently). + +### D3 — Version single-sourcing + +Rule: **package.json is the only place a version string lives.** Runtime code +reads it (`createRequire(import.meta.url)('./package.json').version` or a +build-time define for the TS packages). CI greps for `\d+\.\d+\.\d+` literals in +`src/` of each package and fails on match (allowlist: test fixtures). This +retires ADR-263 F6 and ADR-264 F9 permanently instead of per-incident. + +### D4 — Namespace and bin ownership + +- `@ruvnet/ruview` **owns the `ruview` bin** (it is the published front door, + ADR-182). `@ruv/ruview-cli` renames its bin or folds into `rvagent` + (ADR-264 O9) — decided here so neither package ADR relitigates it. +- New Node packages in this repo use the `@ruvnet/` scope (the `@ruv/` scope + holds `rvcsi` legacies; do not grow it). +- Every package README + description must pass + `npx ruview claim-check` — enforced in the D1 gate. The guardrail package + linting its sibling packages' claims is the cheapest dogfooding we have + (ADR-264 F3 is the standing example of why). + +### D5 — Shared-code policy (bounded) + +Do **not** introduce an npm workspace or a shared runtime package yet: three +packages, two of which may merge (ADR-264 O9), do not justify workspace +machinery, and the harness's zero-dep property is load-bearing. Revisit if a +fourth package appears or if the `http/cog/config` duplication survives the +ADR-264 O9 fold. Record the duplication as intentional in each file header (the +CLI already does this). + +## Consequences + +- The npm artifacts get the same class of gate the Rust workspace has had since + ADR-028: no publish without tests, no shipped file set without an asserted + manifest, no version without provenance. The two defects that reached the + registry (broken `require` condition, dead maps) become CI-impossible. +- Cold-path costs stay near zero: the D1 matrix is 6 fast jobs (the harness + suite runs in ~108 ms MEASURED; TS builds dominate at a few tens of seconds). +- Publishing gains one constraint (must go through CI) and loses one failure + mode (laptop-state publishes) — the right trade for a project whose brand is + reproducible evidence. +- D3's grep gate is blunt but cheap; if it over-fires, scope it to + `version`-adjacent identifiers before weakening it. +- Follow-ups tracked elsewhere: per-package code fixes (ADR-263 O1–O8, ADR-264 + O1–O9); ADR-182 P4 (metaharness router + ed25519 provenance chain) remains + the deeper provenance story that D2's npm attestations complement, not + replace. diff --git a/docs/adr/README.md b/docs/adr/README.md index 4826fef0..94c0f218 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -1,6 +1,6 @@ # Architecture Decision Records -This folder contains 45 Architecture Decision Records (ADRs) that document every significant technical choice in the RuView / WiFi-DensePose project. +This folder contains 182 Architecture Decision Records (ADRs) that document every significant technical choice in the RuView / WiFi-DensePose project. (The index tables below list a curated subset per domain; see the directory listing for the full set.) ## Why ADRs? @@ -120,6 +120,9 @@ Statuses: **Proposed** (under discussion), **Accepted** (approved and/or impleme | [ADR-097](ADR-097-adopt-rvcsi-as-ruview-csi-runtime.md) | Adopt rvCSI as RuView's primary CSI runtime (phased adoption) | Proposed | | [ADR-098](ADR-098-evaluate-midstream-fit.md) | Evaluate `ruvnet/midstream` for RuView's CSI / WebSocket / mesh pipeline | Rejected | | [ADR-099](ADR-099-midstream-introspection-tap.md) | Adopt midstream as RuView's real-time introspection + low-latency tap | Proposed | +| [ADR-263](ADR-263-ruview-npm-harness-deep-review.md) | `@ruvnet/ruview` npm harness — deep review + optimization strategy | Proposed | +| [ADR-264](ADR-264-rvagent-mcp-and-cli-npm-deep-review.md) | `@ruvnet/rvagent` MCP server + `@ruv/ruview-cli` — deep review + optimization strategy | Proposed | +| [ADR-265](ADR-265-ruview-npm-distribution-strategy.md) | RuView npm distribution strategy — CI gate, provenance, version single-sourcing, namespace | Proposed | ---