mirror of
https://github.com/ruvnet/RuView.git
synced 2026-09-02 13:37:00 +00:00
Compare commits
1 Commits
v2231
...
docs/optim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d56995441 |
83
.github/scripts/nightly-sota/README.md
vendored
83
.github/scripts/nightly-sota/README.md
vendored
@@ -1,83 +0,0 @@
|
||||
# Nightly SOTA research agent
|
||||
|
||||
`nightly-sota-agent.yml` turns recent public research into at most one
|
||||
repository issue and, for low-risk topics, one draft offline-prototype pull
|
||||
request. It is intentionally not a general-purpose autonomous coding agent.
|
||||
|
||||
## Enablement
|
||||
|
||||
The committed schedule is `03:17 UTC` every day. Scheduled runs stay disabled
|
||||
until both repository settings exist:
|
||||
|
||||
1. Actions secret `COGNITUM_NIGHTLY_API_KEY`, issued with only the Cognitum
|
||||
`completions:mid` scope.
|
||||
2. Actions variable `RUVIEW_NIGHTLY_SOTA_ENABLED=true`.
|
||||
|
||||
The key must not receive guidance-write, evolve, pods, brain, Flywheel-write,
|
||||
or administrative scopes. First run the workflow manually in `dry-run` mode;
|
||||
that mode only collects a bounded evidence artifact and never reads the secret
|
||||
or writes an issue. Manual `live` mode is restricted to the repository owner.
|
||||
|
||||
The repository must also allow GitHub Actions to create pull requests. Normal
|
||||
branch protection must require at least one approving review and the
|
||||
`Verify contributor harness` status check. The publisher requires that exact
|
||||
job-name check to be bound to the GitHub Actions app,
|
||||
uses GitHub's effective-active-rules endpoint, and stops before prototype
|
||||
generation when either requirement is absent. It does not request an
|
||||
administrative token to inspect hidden ruleset bypass actors; safety does not
|
||||
depend on that metadata because the publisher has no merge or `main`-push path.
|
||||
|
||||
## Authority split
|
||||
|
||||
| Job | External credential | Repository authority | Result |
|
||||
|---|---|---|---|
|
||||
| `collect` | none | contents read | Normalized public Cognitum registry and recent arXiv evidence |
|
||||
| `propose` | Cognitum completions key | contents read | One schema-checked proposal |
|
||||
| `score` | none | contents read | Frozen Darwin digest, completeness score, honest-null Flywheel replay |
|
||||
| `issue` | GitHub token | issue write, PR read | One deduplicated issue |
|
||||
| `implement` | Cognitum completions key | contents read | Declarative transform and test vectors |
|
||||
| `validate` | none | contents read | Schema, template, syntax, claim, path, digest, and replay checks |
|
||||
| `publish` | GitHub token | branch/issue/draft-PR/Actions write | One draft PR and an explicit read-only harness-verifier dispatch |
|
||||
|
||||
The Cognitum key and a write-capable GitHub token never coexist in one job.
|
||||
Model output is never executable code. Repository-owned templates emit the
|
||||
prototype module and tests, which this workflow syntax-checks but never runs.
|
||||
|
||||
## Hard boundaries
|
||||
|
||||
- Public HTTPS sources are fixed to the Cognitum application registry and the
|
||||
arXiv Atom API. Redirects, oversized responses, unexpected media types, and
|
||||
schema drift fail closed.
|
||||
- Retrieved text is `CLAIMED`, untrusted evidence. It is quoted inside a fixed
|
||||
trusted prompt and cannot grant authority.
|
||||
- The Darwin genome is read-only. Scheduled jobs never invoke Darwin evolution.
|
||||
- Flywheel runs a separate committed honest-null canary. A valid canary stays
|
||||
root-only, rejects its candidate, and reports zero verified improvements and
|
||||
no promotion. It does not evaluate the nightly proposal. The workflow's
|
||||
static authority split and artifact gates are what prevent nightly learning
|
||||
or promotion.
|
||||
- High-risk topics stop at an issue. This includes production, security,
|
||||
authentication, release/deployment, workflows, dependencies, firmware,
|
||||
hardware, networking, native plugins, HomeKit pairing, and voice protocols.
|
||||
- Low-risk model output is a closed transform DSL: bounded scalar test vectors
|
||||
and 1-8 allowlisted operations (`center`, `normalize-peak`, `absolute`,
|
||||
`square`, `difference`, `moving-average`, or `clip`). Local trusted templates
|
||||
emit exactly five `.md`, `.json`, and `.mjs` files below
|
||||
`examples/research-sota/nightly/<fingerprint>/`. Existing files, symlinked
|
||||
parents, dependencies, binaries, executable modes, and more than 400 lines
|
||||
are rejected.
|
||||
- Publication is a draft PR. The agent cannot approve, merge, release, promote,
|
||||
or modify the reviewed shared brain.
|
||||
|
||||
## Deduplication and failure behavior
|
||||
|
||||
The stable fingerprint hashes sorted evidence IDs, finding class, and subsystem.
|
||||
Issues and PRs carry an exact hidden marker. Only markers on
|
||||
`github-actions[bot]` records with the automation label are trusted for
|
||||
deduplication, so copied issue text cannot suppress future runs.
|
||||
|
||||
A failure leaves the last completed bounded artifact for seven days. Model,
|
||||
protection-preflight, or validation failures may leave an issue without a PR;
|
||||
maintainers can inspect the run and decide whether to continue manually. The
|
||||
workflow does not retry a failed model call, force-push a branch, close an
|
||||
issue, or delete a branch.
|
||||
1101
.github/scripts/nightly-sota/agent.mjs
vendored
1101
.github/scripts/nightly-sota/agent.mjs
vendored
File diff suppressed because it is too large
Load Diff
1016
.github/scripts/nightly-sota/lib.mjs
vendored
1016
.github/scripts/nightly-sota/lib.mjs
vendored
File diff suppressed because it is too large
Load Diff
4
.github/workflows/aether-arena-harness.yml
vendored
4
.github/workflows/aether-arena-harness.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
run:
|
||||
working-directory: v2
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
run: rustup show && rustc --version
|
||||
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
|
||||
14
.github/workflows/bench-regression.yml
vendored
14
.github/workflows/bench-regression.yml
vendored
@@ -71,7 +71,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout (recursive — wifi-densepose-rufield path-deps vendor/rufield)
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# The workspace includes `wifi-densepose-rufield`, which path-deps the
|
||||
# `vendor/rufield` submodule crates. Without a recursive checkout the
|
||||
@@ -100,10 +100,10 @@ jobs:
|
||||
pkg-config
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache cargo (Swatinem/rust-cache)
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: v2
|
||||
# Distinct cache scope from ci.yml's rust-tests so the bench profile
|
||||
@@ -150,15 +150,15 @@ jobs:
|
||||
needs: [bench-compile]
|
||||
steps:
|
||||
- name: Checkout (recursive)
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache cargo (Swatinem/rust-cache)
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: v2
|
||||
key: bench-regression
|
||||
@@ -192,7 +192,7 @@ jobs:
|
||||
|
||||
- name: Upload informational bench logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bench-fast-run-logs
|
||||
path: bench-out/
|
||||
|
||||
6
.github/workflows/bfld-mqtt-integration.yml
vendored
6
.github/workflows/bfld-mqtt-integration.yml
vendored
@@ -52,17 +52,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Cache cargo registry + target
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
|
||||
22
.github/workflows/cd.yml
vendored
22
.github/workflows/cd.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
image_tag: ${{ steps.determine-tag.outputs.tag }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
submodules: recursive
|
||||
@@ -96,12 +96,12 @@ jobs:
|
||||
url: https://staging.wifi-densepose.com
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up kubectl
|
||||
uses: azure/setup-kubectl@901a10e89ea615cf61f57ac05cecdf23e7de06d8
|
||||
uses: azure/setup-kubectl@v3
|
||||
with:
|
||||
version: 'v1.28.0'
|
||||
|
||||
@@ -147,12 +147,12 @@ jobs:
|
||||
url: https://wifi-densepose.com
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up kubectl
|
||||
uses: azure/setup-kubectl@901a10e89ea615cf61f57ac05cecdf23e7de06d8
|
||||
uses: azure/setup-kubectl@v3
|
||||
with:
|
||||
version: 'v1.28.0'
|
||||
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
# kubectl scale rs -n wifi-densepose -l app=wifi-densepose,version!=green --replicas=0
|
||||
|
||||
- name: Upload deployment artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: production-deployment-${{ github.run_number }}
|
||||
path: |
|
||||
@@ -239,7 +239,7 @@ jobs:
|
||||
name: ${{ needs.pre-deployment.outputs.deploy_env }}
|
||||
steps:
|
||||
- name: Set up kubectl
|
||||
uses: azure/setup-kubectl@901a10e89ea615cf61f57ac05cecdf23e7de06d8
|
||||
uses: azure/setup-kubectl@v3
|
||||
with:
|
||||
version: 'v1.28.0'
|
||||
|
||||
@@ -293,7 +293,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Update deployment status
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const deployEnv = '${{ needs.pre-deployment.outputs.deploy_env }}';
|
||||
@@ -317,7 +317,7 @@ jobs:
|
||||
steps:
|
||||
- name: Notify Slack on success
|
||||
if: needs.deploy-production.result == 'success' || needs.deploy-staging.result == 'success'
|
||||
uses: 8398a7/action-slack@77eaa4f1c608a7d68b38af4e3f739dcd8cba273e
|
||||
uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: success
|
||||
channel: '#deployments'
|
||||
@@ -331,7 +331,7 @@ jobs:
|
||||
|
||||
- name: Notify Slack on failure
|
||||
if: needs.deploy-production.result == 'failure' || needs.deploy-staging.result == 'failure'
|
||||
uses: 8398a7/action-slack@77eaa4f1c608a7d68b38af4e3f739dcd8cba273e
|
||||
uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: failure
|
||||
channel: '#deployments'
|
||||
@@ -344,7 +344,7 @@ jobs:
|
||||
|
||||
- name: Create deployment issue on failure
|
||||
if: needs.deploy-production.result == 'failure'
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.create({
|
||||
|
||||
60
.github/workflows/ci.yml
vendored
60
.github/workflows/ci.yml
vendored
@@ -27,14 +27,14 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
continue-on-error: true
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
- name: Upload security reports
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: security-reports
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
# ADR-262 P1: `wifi-densepose-rufield` path-deps the `vendor/rufield`
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
pkg-config
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# Swatinem/rust-cache replaces a naive `actions/cache` of the whole
|
||||
# `v2/target`. That manual cache of a 38-crate target dir (multi-GB) was an
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
# reliably (and faster) on large workspaces. `workspaces: v2` points it at
|
||||
# the v2/ cargo workspace (keys on v2/Cargo.lock, caches v2/target).
|
||||
- name: Cache cargo (Swatinem/rust-cache)
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: v2
|
||||
|
||||
@@ -196,10 +196,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
@@ -222,8 +222,6 @@ jobs:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
# Ephemeral CI-only credential; this service is isolated to the job.
|
||||
# kics-scan ignore-line
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: test_wifi_densepose
|
||||
options: >-
|
||||
@@ -247,13 +245,13 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
continue-on-error: true
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
@@ -268,8 +266,6 @@ jobs:
|
||||
- name: Run unit tests
|
||||
continue-on-error: true
|
||||
env:
|
||||
# Ephemeral CI-only service URL; never used outside this job.
|
||||
# kics-scan ignore-line
|
||||
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_wifi_densepose
|
||||
REDIS_URL: redis://localhost:6379/0
|
||||
ENVIRONMENT: test
|
||||
@@ -279,8 +275,6 @@ jobs:
|
||||
- name: Run integration tests
|
||||
continue-on-error: true
|
||||
env:
|
||||
# Ephemeral CI-only service URL; never used outside this job.
|
||||
# kics-scan ignore-line
|
||||
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_wifi_densepose
|
||||
REDIS_URL: redis://localhost:6379/0
|
||||
ENVIRONMENT: test
|
||||
@@ -289,7 +283,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage reports
|
||||
continue-on-error: true
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f
|
||||
uses: codecov/codecov-action@v6
|
||||
with:
|
||||
files: ./coverage.xml
|
||||
flags: unittests
|
||||
@@ -297,7 +291,7 @@ jobs:
|
||||
|
||||
- name: Upload test results
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-${{ matrix.python-version }}
|
||||
@@ -318,12 +312,12 @@ jobs:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
@@ -367,7 +361,7 @@ jobs:
|
||||
|
||||
- name: Upload performance results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: performance-results
|
||||
path: archive/v1/perf-junit.xml
|
||||
@@ -388,17 +382,17 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
continue-on-error: true
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Container Registry
|
||||
continue-on-error: true
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -407,7 +401,7 @@ jobs:
|
||||
- name: Extract metadata
|
||||
continue-on-error: true
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
@@ -418,7 +412,7 @@ jobs:
|
||||
|
||||
- name: Build and push Docker image
|
||||
continue-on-error: true
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
target: production
|
||||
@@ -447,7 +441,7 @@ jobs:
|
||||
|
||||
- name: Upload Trivy scan results
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
@@ -462,12 +456,12 @@ jobs:
|
||||
contents: write # gh-pages deploy needs write (GITHUB_TOKEN is read-only by default -> 403)
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
@@ -490,7 +484,7 @@ jobs:
|
||||
"
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
continue-on-error: true # openapi generation above is the real validation; deploy is best-effort (Pages may be disabled)
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -513,7 +507,7 @@ jobs:
|
||||
steps:
|
||||
- name: Notify Slack on success
|
||||
if: ${{ env.SLACK_WEBHOOK_URL != '' && needs.code-quality.result == 'success' && needs.test.result == 'success' && needs.docker-build.result == 'success' }}
|
||||
uses: 8398a7/action-slack@77eaa4f1c608a7d68b38af4e3f739dcd8cba273e
|
||||
uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: success
|
||||
channel: '#ci-cd'
|
||||
@@ -521,7 +515,7 @@ jobs:
|
||||
|
||||
- name: Notify Slack on failure
|
||||
if: ${{ env.SLACK_WEBHOOK_URL != '' && (needs.code-quality.result == 'failure' || needs.test.result == 'failure' || needs.docker-build.result == 'failure') }}
|
||||
uses: 8398a7/action-slack@77eaa4f1c608a7d68b38af4e3f739dcd8cba273e
|
||||
uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: failure
|
||||
channel: '#ci-cd'
|
||||
@@ -529,7 +523,7 @@ jobs:
|
||||
|
||||
- name: Create GitHub Release
|
||||
if: github.ref == 'refs/heads/main' && needs.docker-build.result == 'success'
|
||||
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ github.run_number }}
|
||||
name: Release v${{ github.run_number }}
|
||||
|
||||
2
.github/workflows/clone-tracking.yml
vendored
2
.github/workflows/clone-tracking.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
snapshot:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
26
.github/workflows/cog-ha-matter-release.yml
vendored
26
.github/workflows/cog-ha-matter-release.yml
vendored
@@ -27,17 +27,17 @@ jobs:
|
||||
name: Build x86_64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
echo "Signed cog-ha-matter-x86_64 ($(wc -c < dist/cog-ha-matter-x86_64.sig) bytes)"
|
||||
|
||||
- name: Upload workflow artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cog-ha-matter-x86_64
|
||||
path: |
|
||||
@@ -79,12 +79,12 @@ jobs:
|
||||
name: Build aarch64 (arm)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-unknown-linux-gnu
|
||||
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
echo "Signed cog-ha-matter-arm ($(wc -c < dist/cog-ha-matter-arm.sig) bytes)"
|
||||
|
||||
- name: Upload workflow artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cog-ha-matter-arm
|
||||
path: |
|
||||
@@ -148,29 +148,29 @@ jobs:
|
||||
github.event_name == 'push' &&
|
||||
vars.HAS_GCP_CREDENTIALS == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download x86_64 artifact
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cog-ha-matter-x86_64
|
||||
path: dist/
|
||||
|
||||
- name: Download arm artifact
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cog-ha-matter-arm
|
||||
path: dist/
|
||||
|
||||
- name: Auth to GCP
|
||||
uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed
|
||||
uses: google-github-actions/auth@v2
|
||||
with:
|
||||
credentials_json: ${{ secrets.GCP_CREDENTIALS }}
|
||||
|
||||
- name: Set up gcloud
|
||||
uses: google-github-actions/setup-gcloud@e427ad8a34f8676edf47cf7d7925499adf3eb74f
|
||||
uses: google-github-actions/setup-gcloud@v2
|
||||
|
||||
- name: Upload binaries + sidecars
|
||||
run: |
|
||||
|
||||
53
.github/workflows/csi-data-policy.yml
vendored
53
.github/workflows/csi-data-policy.yml
vendored
@@ -1,53 +0,0 @@
|
||||
name: CSI data policy (ADR-299)
|
||||
|
||||
# ADR-299 repository CSI data-incident guard. Fails when CSI-format files
|
||||
# (*.csi.jsonl / *.csi.meta.json) or oversized JSONL captures are tracked in
|
||||
# git. Raw CSI is person data and must never be committed (CLAUDE.md, ADR-299).
|
||||
#
|
||||
# NOTE: the tree currently still contains the pre-existing incident recordings
|
||||
# under data/recordings/ and v2/data/recordings/, whose removal is gated on
|
||||
# data-owner sign-off (ADR-299). Until they are removed this job is EXPECTED to
|
||||
# fail, and that failure documents the incident. To make it green in a
|
||||
# follow-up without weakening the guard for NEW files, set CSI_POLICY_BASELINE
|
||||
# to a file listing the acknowledged paths (see the script header).
|
||||
#
|
||||
# Checker: scripts/csi-data-policy-check.sh Run locally: bash the same script.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
csi-data-policy:
|
||||
name: CSI data policy check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Self-test the policy checker (deterministic, offline)
|
||||
run: bash scripts/csi-data-policy-check.sh --self-test
|
||||
|
||||
- name: Enforce CSI data policy on tracked files
|
||||
# CSI_POLICY_BASELINE can point at an acknowledged-paths file once the
|
||||
# owner remediates the tree; unset here so a regression fails loudly.
|
||||
run: bash scripts/csi-data-policy-check.sh --tracked
|
||||
|
||||
- name: Summarize result
|
||||
if: always()
|
||||
run: |
|
||||
{
|
||||
echo '### CSI data policy (ADR-299)'
|
||||
echo ''
|
||||
echo '```'
|
||||
bash scripts/csi-data-policy-check.sh --tracked 2>&1 || true
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
6
.github/workflows/dashboard-a11y.yml
vendored
6
.github/workflows/dashboard-a11y.yml
vendored
@@ -19,11 +19,11 @@ jobs:
|
||||
a11y:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with: { targets: wasm32-unknown-unknown }
|
||||
|
||||
- name: Install wasm-pack
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
--out-dir ../../dashboard/public/nvsim-pkg \
|
||||
--release -- --no-default-features --features wasm
|
||||
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
|
||||
- uses: actions/setup-node@v6
|
||||
with: { node-version: 20, cache: npm, cache-dependency-path: dashboard/package-lock.json }
|
||||
|
||||
- working-directory: dashboard
|
||||
|
||||
10
.github/workflows/dashboard-pages.yml
vendored
10
.github/workflows/dashboard-pages.yml
vendored
@@ -25,17 +25,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Rust + wasm32 target
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
-- --no-default-features --features wasm
|
||||
|
||||
- name: Setup Node 20
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: Deploy to gh-pages/nvsim/
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./dashboard/dist
|
||||
|
||||
24
.github/workflows/desktop-release.yml
vendored
24
.github/workflows/desktop-release.yml
vendored
@@ -27,17 +27,17 @@ jobs:
|
||||
target: [aarch64-apple-darwin, x86_64-apple-darwin]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
zip -r "RuView-Desktop-${{ github.event.inputs.version || '0.4.0' }}-macos-${{ steps.arch.outputs.arch }}.zip" "RuView Desktop.app"
|
||||
|
||||
- name: Upload macOS artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ruview-macos-${{ steps.arch.outputs.arch }}
|
||||
path: v2/target/${{ matrix.target }}/release/bundle/macos/*.zip
|
||||
@@ -84,17 +84,17 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install frontend dependencies
|
||||
working-directory: v2/crates/wifi-densepose-desktop/ui
|
||||
@@ -115,13 +115,13 @@ jobs:
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
- name: Upload Windows MSI artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ruview-windows-msi
|
||||
path: v2/target/release/bundle/msi/*.msi
|
||||
|
||||
- name: Upload Windows NSIS artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ruview-windows-nsis
|
||||
path: v2/target/release/bundle/nsis/*.exe
|
||||
@@ -134,12 +134,12 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
run: find artifacts -type f
|
||||
|
||||
- name: Create or Update Release
|
||||
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: RuView Desktop v${{ github.event.inputs.version || '0.4.0' }}
|
||||
tag_name: ${{ github.event.inputs.attach_to_existing || format('desktop-v{0}', github.event.inputs.version || '0.4.0') }}
|
||||
|
||||
6
.github/workflows/firmware-ci.yml
vendored
6
.github/workflows/firmware-ci.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref_type == 'tag'
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Check firmware version.txt == tag
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
artifact_pt: partition-table-c6.bin
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
echo "See: https://github.com/espressif/qemu/wiki"
|
||||
|
||||
- name: Upload firmware artifact (${{ matrix.variant }})
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: esp32-csi-node-firmware-${{ matrix.variant }}
|
||||
path: firmware/esp32-csi-node/release-staging/
|
||||
|
||||
22
.github/workflows/firmware-qemu.yml
vendored
22
.github/workflows/firmware-qemu.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
steps:
|
||||
- name: Cache QEMU build
|
||||
id: cache-qemu
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /opt/qemu-esp32
|
||||
# Include date component so cache refreshes monthly when branch updates
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
echo "QEMU binary size: $(file_size /opt/qemu-esp32/bin/qemu-system-xtensa) bytes"
|
||||
|
||||
- name: Upload QEMU artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qemu-esp32
|
||||
path: /opt/qemu-esp32/
|
||||
@@ -99,12 +99,12 @@ jobs:
|
||||
- boundary-min
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download QEMU artifact
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: qemu-esp32
|
||||
path: /opt/qemu-esp32
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
|
||||
- name: Upload test logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qemu-logs-${{ matrix.nvs_config }}
|
||||
path: |
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
name: Fuzz Testing (ADR-061 Layer 6)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -253,7 +253,7 @@ jobs:
|
||||
|
||||
- name: Upload fuzz artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fuzz-crashes
|
||||
path: |
|
||||
@@ -266,7 +266,7 @@ jobs:
|
||||
name: NVS Matrix Generation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -322,12 +322,12 @@ jobs:
|
||||
image: espressif/idf:v5.4
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download QEMU artifact
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: qemu-esp32
|
||||
path: /opt/qemu-esp32
|
||||
@@ -370,7 +370,7 @@ jobs:
|
||||
|
||||
- name: Upload swarm results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: swarm-results
|
||||
path: |
|
||||
|
||||
6
.github/workflows/fix-regression-guard.yml
vendored
6
.github/workflows/fix-regression-guard.yml
vendored
@@ -21,11 +21,11 @@ jobs:
|
||||
name: Verify fix markers
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Upload result artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fix-markers-result
|
||||
path: fix-markers-result.json
|
||||
|
||||
6
.github/workflows/mqtt-integration.yml
vendored
6
.github/workflows/mqtt-integration.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -70,12 +70,12 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Cache cargo registry + build
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: v2 -> target
|
||||
|
||||
|
||||
345
.github/workflows/nightly-sota-agent.yml
vendored
345
.github/workflows/nightly-sota-agent.yml
vendored
@@ -1,345 +0,0 @@
|
||||
name: Nightly SOTA research agent
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 3 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mode:
|
||||
description: 'dry-run collects evidence only; live may create one issue and one draft prototype PR'
|
||||
required: true
|
||||
default: dry-run
|
||||
type: choice
|
||||
options:
|
||||
- dry-run
|
||||
- live
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: nightly-sota-agent
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
NODE_VERSION: '22'
|
||||
|
||||
jobs:
|
||||
collect:
|
||||
name: Collect public evidence
|
||||
if: >-
|
||||
github.repository == 'ruvnet/RuView' &&
|
||||
github.ref == 'refs/heads/main' &&
|
||||
(github.event_name == 'workflow_dispatch' || vars.RUVIEW_NIGHTLY_SOTA_ENABLED == 'true')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- name: Collect bounded public evidence
|
||||
run: >-
|
||||
node --disable-proto=throw .github/scripts/nightly-sota/agent.mjs collect
|
||||
--out "${RUNNER_TEMP}/nightly-sota/evidence.json"
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: nightly-sota-evidence-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/evidence.json
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
propose:
|
||||
name: Synthesize bounded proposal
|
||||
if: >-
|
||||
needs.collect.result == 'success' &&
|
||||
(
|
||||
github.event_name == 'schedule' ||
|
||||
(inputs.mode == 'live' && github.actor == github.repository_owner)
|
||||
)
|
||||
needs: collect
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-evidence-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/collect
|
||||
- name: Synthesize one proposal with Cognitum
|
||||
env:
|
||||
COGNITUM_NIGHTLY_API_KEY: ${{ secrets.COGNITUM_NIGHTLY_API_KEY }}
|
||||
run: >-
|
||||
node --disable-proto=throw .github/scripts/nightly-sota/agent.mjs propose
|
||||
--evidence "${RUNNER_TEMP}/nightly-sota/collect/evidence.json"
|
||||
--repo-root "${GITHUB_WORKSPACE}"
|
||||
--proposal-out "${RUNNER_TEMP}/nightly-sota/propose/proposal.json"
|
||||
--receipt-out "${RUNNER_TEMP}/nightly-sota/propose/cognitum-receipt.json"
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: nightly-sota-proposal-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/propose/
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
score:
|
||||
name: Verify frozen Darwin and Flywheel score
|
||||
needs: propose
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-evidence-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/collect
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-proposal-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/propose
|
||||
- name: Install exact-pinned Flywheel development dependencies
|
||||
working-directory: harness/ruview
|
||||
run: npm ci --ignore-scripts --omit=optional
|
||||
- name: Audit Flywheel dependency graph
|
||||
working-directory: harness/ruview
|
||||
run: npm audit --omit=optional
|
||||
- name: Score with frozen Darwin policy and honest-null Flywheel replay
|
||||
run: >-
|
||||
node --disable-proto=throw .github/scripts/nightly-sota/agent.mjs score
|
||||
--evidence "${RUNNER_TEMP}/nightly-sota/collect/evidence.json"
|
||||
--proposal "${RUNNER_TEMP}/nightly-sota/propose/proposal.json"
|
||||
--repo-root "${GITHUB_WORKSPACE}"
|
||||
--score-out "${RUNNER_TEMP}/nightly-sota/score/score.json"
|
||||
--replay-out "${RUNNER_TEMP}/nightly-sota/score/replay.json"
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: nightly-sota-score-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/score/
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
issue:
|
||||
name: Deduplicate and create issue
|
||||
needs: score
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write # Create the single labelled research issue.
|
||||
pull-requests: read # Stop before spending on a fingerprint with an existing bot PR.
|
||||
outputs:
|
||||
should_implement: ${{ steps.triage.outputs.should_implement }}
|
||||
issue_number: ${{ steps.triage.outputs.issue_number }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-evidence-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/collect
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-proposal-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/propose
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-score-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/score
|
||||
- name: Deduplicate or create one issue
|
||||
id: triage
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: >-
|
||||
node --disable-proto=throw .github/scripts/nightly-sota/agent.mjs issue
|
||||
--evidence "${RUNNER_TEMP}/nightly-sota/collect/evidence.json"
|
||||
--proposal "${RUNNER_TEMP}/nightly-sota/propose/proposal.json"
|
||||
--proposal-receipt "${RUNNER_TEMP}/nightly-sota/propose/cognitum-receipt.json"
|
||||
--score "${RUNNER_TEMP}/nightly-sota/score/score.json"
|
||||
--replay "${RUNNER_TEMP}/nightly-sota/score/replay.json"
|
||||
--repo-root "${GITHUB_WORKSPACE}"
|
||||
--out "${RUNNER_TEMP}/nightly-sota/issue/issue.json"
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: nightly-sota-issue-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/issue/
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
implement:
|
||||
name: Generate offline prototype bundle
|
||||
if: needs.issue.outputs.should_implement == 'true'
|
||||
needs: issue
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-evidence-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/collect
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-proposal-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/propose
|
||||
- name: Generate a bounded offline prototype with Cognitum
|
||||
env:
|
||||
COGNITUM_NIGHTLY_API_KEY: ${{ secrets.COGNITUM_NIGHTLY_API_KEY }}
|
||||
run: >-
|
||||
node --disable-proto=throw .github/scripts/nightly-sota/agent.mjs implement
|
||||
--evidence "${RUNNER_TEMP}/nightly-sota/collect/evidence.json"
|
||||
--proposal "${RUNNER_TEMP}/nightly-sota/propose/proposal.json"
|
||||
--repo-root "${GITHUB_WORKSPACE}"
|
||||
--bundle-out "${RUNNER_TEMP}/nightly-sota/implement/bundle.json"
|
||||
--receipt-out "${RUNNER_TEMP}/nightly-sota/implement/cognitum-receipt.json"
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: nightly-sota-implementation-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/implement/
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
validate:
|
||||
name: Validate without external credentials
|
||||
needs: [score, implement]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-evidence-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/collect
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-proposal-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/propose
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-score-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/score
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-implementation-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/implement
|
||||
- name: Install exact-pinned Flywheel verification dependency
|
||||
working-directory: harness/ruview
|
||||
run: npm ci --ignore-scripts --omit=optional
|
||||
- name: Validate without model or GitHub write credentials
|
||||
run: >-
|
||||
node --disable-proto=throw .github/scripts/nightly-sota/agent.mjs validate
|
||||
--evidence "${RUNNER_TEMP}/nightly-sota/collect/evidence.json"
|
||||
--proposal "${RUNNER_TEMP}/nightly-sota/propose/proposal.json"
|
||||
--proposal-receipt "${RUNNER_TEMP}/nightly-sota/propose/cognitum-receipt.json"
|
||||
--score "${RUNNER_TEMP}/nightly-sota/score/score.json"
|
||||
--replay "${RUNNER_TEMP}/nightly-sota/score/replay.json"
|
||||
--bundle "${RUNNER_TEMP}/nightly-sota/implement/bundle.json"
|
||||
--implementation-receipt "${RUNNER_TEMP}/nightly-sota/implement/cognitum-receipt.json"
|
||||
--repo-root "${GITHUB_WORKSPACE}"
|
||||
--out "${RUNNER_TEMP}/nightly-sota/validate/validation.json"
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: nightly-sota-validation-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/validate/
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
publish:
|
||||
name: Publish draft prototype PR
|
||||
needs: [issue, validate]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
actions: write # Dispatch the read-only contributor-harness verifier for the generated branch.
|
||||
contents: write # Push the one new prototype-only branch.
|
||||
issues: write # Label the draft PR and link it from the issue.
|
||||
pull-requests: write # Create a draft PR; the script has no approve or merge path.
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
persist-credentials: true
|
||||
submodules: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-evidence-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/collect
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-proposal-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/propose
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-score-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/score
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-issue-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/issue
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-implementation-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/implement
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: nightly-sota-validation-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/nightly-sota/validate
|
||||
- name: Publish one draft PR and dispatch the read-only verifier
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: >-
|
||||
node --disable-proto=throw .github/scripts/nightly-sota/agent.mjs publish
|
||||
--evidence "${RUNNER_TEMP}/nightly-sota/collect/evidence.json"
|
||||
--proposal "${RUNNER_TEMP}/nightly-sota/propose/proposal.json"
|
||||
--proposal-receipt "${RUNNER_TEMP}/nightly-sota/propose/cognitum-receipt.json"
|
||||
--score "${RUNNER_TEMP}/nightly-sota/score/score.json"
|
||||
--replay "${RUNNER_TEMP}/nightly-sota/score/replay.json"
|
||||
--issue "${RUNNER_TEMP}/nightly-sota/issue/issue.json"
|
||||
--bundle "${RUNNER_TEMP}/nightly-sota/implement/bundle.json"
|
||||
--implementation-receipt "${RUNNER_TEMP}/nightly-sota/implement/cognitum-receipt.json"
|
||||
--validation "${RUNNER_TEMP}/nightly-sota/validate/validation.json"
|
||||
--repo-root "${GITHUB_WORKSPACE}"
|
||||
29
.github/workflows/npm-packages.yml
vendored
29
.github/workflows/npm-packages.yml
vendored
@@ -13,14 +13,12 @@ on:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'harness/ruview/**'
|
||||
- 'harness/homecore/**'
|
||||
- 'tools/ruview-mcp/**'
|
||||
- 'tools/ruview-cli/**'
|
||||
- '.github/workflows/npm-packages.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'harness/ruview/**'
|
||||
- 'harness/homecore/**'
|
||||
- 'tools/ruview-mcp/**'
|
||||
- 'tools/ruview-cli/**'
|
||||
- '.github/workflows/npm-packages.yml'
|
||||
@@ -42,11 +40,6 @@ jobs:
|
||||
publishable: true
|
||||
# ADR-283: brain + local hosts + replay assets; still runtime-dependency-free.
|
||||
unpacked_budget: 131072
|
||||
- dir: harness/homecore
|
||||
build: false
|
||||
publishable: true
|
||||
# ADR-285: CLI + MCP + reviewed brain + WASM-kernel adapter.
|
||||
unpacked_budget: 180000
|
||||
- dir: tools/ruview-mcp
|
||||
build: true
|
||||
publishable: true
|
||||
@@ -60,16 +53,14 @@ jobs:
|
||||
run:
|
||||
working-directory: ${{ matrix.package.dir }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
# Packages with dependencies commit lockfiles; install and export
|
||||
# behavior is checked again from the packed tarball.
|
||||
# Packages with development dependencies commit lockfiles; runtime
|
||||
# dependency freedom is checked from the packed tarball.
|
||||
- name: Install
|
||||
run: |
|
||||
if [ -f package-lock.json ]; then npm ci; else npm install --no-fund --no-audit; fi
|
||||
@@ -121,7 +112,7 @@ jobs:
|
||||
# ADR-265 D1.4 — install the real tarball and drive each bin/export.
|
||||
- name: Tarball smoke test
|
||||
if: ${{ matrix.package.publishable }}
|
||||
run: | # zizmor: ignore[adhoc-packages] the locally built tarball is the artifact under test
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TGZ="$PWD/$(npm pack --silent 2>/dev/null | tail -1)"
|
||||
SMOKE="$(mktemp -d)"
|
||||
@@ -138,16 +129,6 @@ jobs:
|
||||
fi
|
||||
node --input-type=module -e "const m = await import('@ruvnet/ruview'); if (!m.TOOLS) process.exit(1);"
|
||||
;;
|
||||
harness/homecore)
|
||||
./node_modules/.bin/homecore --version
|
||||
./node_modules/.bin/homecore doctor --strict-wasm
|
||||
./node_modules/.bin/homecore guidance --topic plugins --query Wasmtime --limit 1 \
|
||||
| grep -q '"wasm-plugins"'
|
||||
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"0"}}}\n' \
|
||||
| timeout 30 ./node_modules/.bin/homecore mcp start | grep -q '"serverInfo"'
|
||||
node --input-type=module -e "const m = await import('homecore'); if (typeof m.runTool !== 'function') process.exit(1);"
|
||||
node --input-type=module -e "const m = await import('homecore/kernel'); const s = await m.getKernelStatus({strict:true}); if (!s.ok || s.resolvedBackend !== 'wasm') process.exit(1);"
|
||||
;;
|
||||
tools/ruview-mcp)
|
||||
# initialize over stdio; server must answer and exit 0 on EOF
|
||||
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"0"}}}\n' \
|
||||
|
||||
10
.github/workflows/nvsim-server-docker.yml
vendored
10
.github/workflows/nvsim-server-docker.yml
vendored
@@ -25,13 +25,13 @@ jobs:
|
||||
build-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ghcr.io/ruvnet/nvsim-server
|
||||
tags: |
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build + push
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: v2
|
||||
file: v2/crates/nvsim-server/Dockerfile
|
||||
|
||||
38
.github/workflows/pip-release.yml
vendored
38
.github/workflows/pip-release.yml
vendored
@@ -90,19 +90,19 @@ jobs:
|
||||
arch: AMD64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Linux aarch64 needs QEMU for cross-build on x86_64 runners.
|
||||
- name: Set up QEMU
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.arch == 'aarch64'
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# ADR-117 §5.4: abi3-py310 — one binary per OS/arch covers all
|
||||
# Python minor versions ≥ 3.10. Build only cp310 wheels.
|
||||
- name: Build wheels (cibuildwheel)
|
||||
uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b
|
||||
uses: pypa/cibuildwheel@v2.21
|
||||
env:
|
||||
CIBW_BUILD: "cp310-*"
|
||||
CIBW_ARCHS_LINUX: ${{ matrix.arch }}
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
package-dir: python
|
||||
output-dir: wheelhouse
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels-${{ matrix.os }}-${{ matrix.arch }}
|
||||
path: wheelhouse/*.whl
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
startsWith(github.ref, 'refs/tags/v2.')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install maturin
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
- name: Build sdist
|
||||
working-directory: python
|
||||
run: maturin sdist --out ../sdist
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdist
|
||||
path: sdist/*.tar.gz
|
||||
@@ -158,8 +158,8 @@ jobs:
|
||||
startsWith(github.ref, 'refs/tags/v2.')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Verify lock-step package versions
|
||||
@@ -185,7 +185,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip build
|
||||
python -m build python/ruview-meta --outdir ruview-dist
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ruview
|
||||
path: ruview-dist/*
|
||||
@@ -202,10 +202,10 @@ jobs:
|
||||
startsWith(github.ref, 'refs/tags/v1.99')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Install build backend
|
||||
@@ -264,7 +264,7 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
echo "Tombstone wheel correctly raises ImportError with migration URL."
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tombstone
|
||||
path: tombstone-dist/*
|
||||
@@ -288,7 +288,7 @@ jobs:
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enforce production witness gate
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v2.') ||
|
||||
@@ -299,7 +299,7 @@ jobs:
|
||||
exit 1
|
||||
}
|
||||
- name: Gather all artifacts into dist/
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist-staging
|
||||
- name: Flatten artifacts
|
||||
@@ -311,7 +311,7 @@ jobs:
|
||||
# before replacing `password:` with the OIDC id-token permission.
|
||||
- name: Publish to TestPyPI (dry-run target)
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.publish_to == 'testpypi'
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
password: ${{ secrets.TESTPYPI_API_TOKEN }}
|
||||
@@ -321,7 +321,7 @@ jobs:
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v2.') ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.publish_to == 'pypi')
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages-dir: dist
|
||||
@@ -339,7 +339,7 @@ jobs:
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: tombstone
|
||||
path: dist
|
||||
@@ -347,7 +347,7 @@ jobs:
|
||||
# before replacing `password:` with the OIDC id-token permission.
|
||||
- name: Publish to TestPyPI (dry-run target)
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.publish_to == 'testpypi'
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
password: ${{ secrets.TESTPYPI_API_TOKEN }}
|
||||
@@ -357,7 +357,7 @@ jobs:
|
||||
if: |
|
||||
startsWith(github.ref, 'refs/tags/v1.99') ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.publish_to == 'pypi')
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages-dir: dist
|
||||
|
||||
4
.github/workflows/pointcloud-pages.yml
vendored
4
.github/workflows/pointcloud-pages.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
EOF
|
||||
|
||||
- name: Deploy to gh-pages/pointcloud/
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./_site/pointcloud
|
||||
|
||||
16
.github/workflows/python-ci.yml
vendored
16
.github/workflows/python-ci.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
name: Wheel + parity tests (features=sota)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# The python/ crate path-deps v2/crates/* and (transitively via
|
||||
# train) the vendored ruvector submodule — recursive checkout keeps
|
||||
@@ -76,15 +76,15 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache cargo (Swatinem/rust-cache)
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: |
|
||||
v2
|
||||
@@ -133,20 +133,20 @@ jobs:
|
||||
name: Default wheel <= 5 MiB (ADR-117 §5.4)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache cargo (Swatinem/rust-cache)
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: python
|
||||
|
||||
|
||||
23
.github/workflows/ruview-harness-flywheel.yml
vendored
23
.github/workflows/ruview-harness-flywheel.yml
vendored
@@ -4,10 +4,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'harness/ruview/**'
|
||||
- '.github/scripts/nightly-sota/**'
|
||||
- '.github/workflows/nightly-sota-agent.yml'
|
||||
- '.github/workflows/ruview-harness-flywheel.yml'
|
||||
- 'docs/adr/ADR-284-bounded-nightly-sota-agent.md'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_darwin:
|
||||
@@ -19,24 +16,19 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ruview-harness-flywheel-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
name: Verify contributor harness
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: harness/ruview
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: harness/ruview/package-lock.json
|
||||
- run: npm ci --ignore-scripts
|
||||
@@ -49,7 +41,6 @@ jobs:
|
||||
- run: npm pack --dry-run
|
||||
|
||||
darwin-proposal:
|
||||
name: Generate untrusted Darwin proposal
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.run_darwin
|
||||
needs: verify
|
||||
runs-on: ubuntu-latest
|
||||
@@ -59,15 +50,15 @@ jobs:
|
||||
run:
|
||||
working-directory: harness/ruview
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 20
|
||||
- run: npm ci --ignore-scripts
|
||||
- run: node flywheel/run.mjs --confirm
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: untrusted-darwin-proposal-${{ github.run_id }}
|
||||
path: harness/ruview/.metaharness/
|
||||
|
||||
58
.github/workflows/ruview-npm-release.yml
vendored
58
.github/workflows/ruview-npm-release.yml
vendored
@@ -7,8 +7,6 @@
|
||||
#
|
||||
# Requires: NPM_TOKEN repo secret (an npm automation token), or npm Trusted
|
||||
# Publishing configured for the package (in which case the token is unused).
|
||||
# Configure the `npm-release` environment for selected branch `main`, required
|
||||
# review, and prevention of self-review; the job also rejects non-main refs.
|
||||
|
||||
name: ruview npm release
|
||||
|
||||
@@ -21,7 +19,6 @@ on:
|
||||
type: choice
|
||||
options:
|
||||
- harness/ruview
|
||||
- harness/homecore
|
||||
- tools/ruview-mcp
|
||||
dist_tag:
|
||||
description: 'npm dist-tag'
|
||||
@@ -35,43 +32,18 @@ permissions:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: npm-release
|
||||
concurrency:
|
||||
group: npm-release
|
||||
cancel-in-progress: false
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ inputs.package }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/heads/main
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Verify trusted-publishing runtime
|
||||
run: |
|
||||
node -e "
|
||||
const [major, minor] = process.versions.node.split('.').map(Number);
|
||||
if (major < 22 || (major === 22 && minor < 14)) {
|
||||
throw new Error('npm trusted publishing requires Node >=22.14.0');
|
||||
}
|
||||
"
|
||||
node -e "
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const [major, minor, patch] = execFileSync('npm', ['--version'], { encoding: 'utf8' }).trim().split('.').map(Number);
|
||||
if (major < 11 || (major === 11 && (minor < 5 || (minor === 5 && patch < 1)))) {
|
||||
throw new Error('npm trusted publishing requires npm >=11.5.1');
|
||||
}
|
||||
"
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
if [ -f package-lock.json ]; then npm ci; else npm install --no-fund --no-audit; fi
|
||||
@@ -106,8 +78,6 @@ jobs:
|
||||
case "${{ inputs.package }}" in
|
||||
# ADR-283: brain + local hosts + replay assets; no runtime deps.
|
||||
harness/ruview) export UNPACKED_BUDGET=131072 ;;
|
||||
# ADR-285: CLI + MCP + reviewed brain + WASM-kernel adapter.
|
||||
harness/homecore) export UNPACKED_BUDGET=180000 ;;
|
||||
# ADR-264 O2: map-free tarball (was 188 kB with maps).
|
||||
tools/ruview-mcp) export UNPACKED_BUDGET=140000 ;;
|
||||
*) echo "Unknown package '${{ inputs.package }}' — no budget defined"; exit 1 ;;
|
||||
@@ -129,11 +99,9 @@ jobs:
|
||||
|
||||
# ADR-265 D1.4 — install the real tarball and drive each bin/export.
|
||||
- name: Tarball smoke test
|
||||
run: | # zizmor: ignore[adhoc-packages] the locally built tarball is the artifact under test
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TGZ="$PWD/$(npm pack --silent 2>/dev/null | tail -1)"
|
||||
SHA512="$(sha512sum "$TGZ" | cut -d' ' -f1)"
|
||||
printf 'PACKAGE_TARBALL=%s\nPACKAGE_TARBALL_SHA512=%s\n' "$TGZ" "$SHA512" >> "$GITHUB_ENV"
|
||||
SMOKE="$(mktemp -d)"
|
||||
cd "$SMOKE"
|
||||
npm init -y > /dev/null
|
||||
@@ -142,24 +110,11 @@ jobs:
|
||||
harness/ruview)
|
||||
./node_modules/.bin/ruview --version
|
||||
./node_modules/.bin/ruview doctor
|
||||
./node_modules/.bin/ruview guidance --topic homecore --query restore --limit 1 \
|
||||
| grep -q '"homecore-runtime-restore"'
|
||||
# the honesty gate must fail closed on empty input (ADR-263 F1)
|
||||
if ./node_modules/.bin/ruview claim-check; then
|
||||
echo 'claim-check passed with no input — fail-open regression'; exit 1
|
||||
fi
|
||||
node --input-type=module -e "const m = await import('@ruvnet/ruview'); if (!m.TOOLS) process.exit(1);"
|
||||
node --input-type=module -e "const m = await import('@ruvnet/ruview/guidance'); if (typeof m.getGuidance !== 'function') process.exit(1);"
|
||||
;;
|
||||
harness/homecore)
|
||||
./node_modules/.bin/homecore --version
|
||||
./node_modules/.bin/homecore doctor --strict-wasm
|
||||
./node_modules/.bin/homecore guidance --topic plugins --query Wasmtime --limit 1 \
|
||||
| grep -q '"wasm-plugins"'
|
||||
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"0"}}}\n' \
|
||||
| timeout 30 ./node_modules/.bin/homecore mcp start | grep -q '"serverInfo"'
|
||||
node --input-type=module -e "const m = await import('homecore'); if (typeof m.runTool !== 'function') process.exit(1);"
|
||||
node --input-type=module -e "const m = await import('homecore/kernel'); const s = await m.getKernelStatus({strict:true}); if (!s.ok || s.resolvedBackend !== 'wasm') process.exit(1);"
|
||||
;;
|
||||
tools/ruview-mcp)
|
||||
# initialize over stdio; server must answer and exit 0 on EOF
|
||||
@@ -177,9 +132,6 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Publish (with provenance)
|
||||
run: |
|
||||
printf '%s %s\n' "$PACKAGE_TARBALL_SHA512" "$PACKAGE_TARBALL" | sha512sum --check -
|
||||
npm publish "$PACKAGE_TARBALL" --provenance --access public --tag "$NPM_DIST_TAG"
|
||||
run: npm publish --provenance --access public --tag "${{ inputs.dist_tag }}"
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_DIST_TAG: ${{ inputs.dist_tag }}
|
||||
|
||||
20
.github/workflows/ruview-swarm-ci.yml
vendored
20
.github/workflows/ruview-swarm-ci.yml
vendored
@@ -39,12 +39,12 @@ jobs:
|
||||
- { label: 'ruflo', flags: '--features ruflo' }
|
||||
- { label: 'full+train', flags: '--features full,train' }
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
name: clippy (-D warnings, --no-deps)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
# v2/rust-toolchain.toml pins channel "1.89" with profile "minimal" (no
|
||||
@@ -69,12 +69,12 @@ jobs:
|
||||
# toolchain, but the override makes cargo use the separate "1.89"
|
||||
# toolchain — so `cargo clippy` errors "cargo-clippy is not installed for
|
||||
# 1.89". Install clippy on the pinned toolchain that cargo actually uses.
|
||||
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: "1.89"
|
||||
components: clippy
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -96,12 +96,12 @@ jobs:
|
||||
name: build train_marl bin
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
name: ITAR / publish guard
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: publish = false is present (no accidental crates.io publish)
|
||||
|
||||
156
.github/workflows/security-scan.yml
vendored
156
.github/workflows/security-scan.yml
vendored
@@ -26,13 +26,14 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
continue-on-error: true
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
@@ -46,18 +47,15 @@ jobs:
|
||||
|
||||
- name: Run Bandit security scan
|
||||
run: |
|
||||
# archive/v1 is frozen research code and is not shipped. Scan the
|
||||
# maintained Python packages and operator scripts instead.
|
||||
# Keep the Security tab actionable: publish high-severity findings.
|
||||
# Medium/low findings are reviewed during focused local audits.
|
||||
bandit -lll -r python/ scripts/ firmware/esp32-csi-node/ aether-arena/ \
|
||||
-x '*/tests/*,*/test/*,*/test_*.py,*/bench/*' \
|
||||
-f sarif -o bandit-results.sarif
|
||||
# The Python codebase lives under archive/v1/src (it moved there when
|
||||
# the runtime was rewritten in Rust). Scanning `src/` matched nothing,
|
||||
# so this SAST step was a silent no-op.
|
||||
bandit -r archive/v1/src/ -f sarif -o bandit-results.sarif
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload Bandit results to GitHub Security
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: bandit-results.sarif
|
||||
@@ -76,16 +74,12 @@ jobs:
|
||||
semgrep \
|
||||
--config=p/security-audit --config=p/secrets --config=p/python \
|
||||
--config=p/docker --config=p/kubernetes \
|
||||
--severity=ERROR \
|
||||
--exclude='**/tests/**' --exclude='**/test/**' \
|
||||
--exclude='**/test_*.py' --exclude='**/bench/**' \
|
||||
--sarif --output=semgrep.sarif \
|
||||
python/ scripts/ firmware/esp32-csi-node/ aether-arena/
|
||||
--sarif --output=semgrep.sarif archive/v1/src/
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload Semgrep results to GitHub Security
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
@@ -103,11 +97,13 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python
|
||||
continue-on-error: true
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
@@ -139,7 +135,7 @@ jobs:
|
||||
|
||||
- name: Upload Snyk results to GitHub Security
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: snyk-results.sarif
|
||||
@@ -147,7 +143,7 @@ jobs:
|
||||
|
||||
- name: Upload vulnerability reports
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: vulnerability-reports
|
||||
@@ -161,6 +157,7 @@ jobs:
|
||||
name: Container Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true # third-party scanners are flaky / SARIF uploads can 403; don't gate the PR
|
||||
needs: []
|
||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
||||
permissions:
|
||||
security-events: write
|
||||
@@ -169,20 +166,20 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
continue-on-error: true
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build Docker image for scanning
|
||||
continue-on-error: true
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile.rust
|
||||
target: production
|
||||
load: true
|
||||
tags: wifi-densepose:scan
|
||||
cache-from: type=gha
|
||||
@@ -195,21 +192,50 @@ jobs:
|
||||
image-ref: 'wifi-densepose:scan'
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
ignore-unfixed: true
|
||||
limit-severities-for-sarif: true
|
||||
|
||||
- name: Upload Trivy results to GitHub Security
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
category: trivy
|
||||
|
||||
# Trivy is the single container SARIF authority. Grype and Docker Scout
|
||||
# produced duplicate alerts for the same image packages and obscured the
|
||||
# actionable high/critical findings.
|
||||
- name: Run Grype vulnerability scanner
|
||||
continue-on-error: true
|
||||
uses: anchore/scan-action@v7
|
||||
id: grype-scan
|
||||
with:
|
||||
image: 'wifi-densepose:scan'
|
||||
fail-build: false
|
||||
severity-cutoff: high
|
||||
output-format: sarif
|
||||
|
||||
- name: Upload Grype results to GitHub Security
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: ${{ steps.grype-scan.outputs.sarif }}
|
||||
category: grype
|
||||
|
||||
- name: Run Docker Scout
|
||||
continue-on-error: true
|
||||
uses: docker/scout-action@v1
|
||||
if: always()
|
||||
with:
|
||||
command: cves
|
||||
image: wifi-densepose:scan
|
||||
sarif-file: scout-results.sarif
|
||||
summary: true
|
||||
|
||||
- name: Upload Docker Scout results
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: scout-results.sarif
|
||||
category: docker-scout
|
||||
|
||||
# Infrastructure as Code security scanning
|
||||
iac-scan:
|
||||
@@ -223,25 +249,52 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Run Checkov IaC scan
|
||||
continue-on-error: true
|
||||
uses: bridgecrewio/checkov-action@99bb2caf247dfd9f03cf984373bc6043d4e32ebf # v12.1347.0
|
||||
with:
|
||||
directory: .
|
||||
framework: kubernetes,dockerfile,terraform,ansible
|
||||
output_format: sarif
|
||||
output_file_path: checkov-results.sarif
|
||||
quiet: true
|
||||
soft_fail: true
|
||||
|
||||
- name: Upload Checkov results to GitHub Security
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: checkov-results.sarif
|
||||
category: checkov
|
||||
|
||||
- name: Run Terrascan IaC scan
|
||||
continue-on-error: true
|
||||
uses: tenable/terrascan-action@3a6e87da8e244513bd77b631e624552643f794c6 # v1.4.1
|
||||
with:
|
||||
iac_type: 'k8s'
|
||||
iac_version: 'v1'
|
||||
policy_type: 'k8s'
|
||||
only_warn: true
|
||||
sarif_upload: true
|
||||
|
||||
- name: Run KICS IaC scan
|
||||
continue-on-error: true
|
||||
uses: checkmarx/kics-github-action@05aa5eb70eede1355220f4ca5238d96b397e30a6 # v2.1.20
|
||||
with:
|
||||
# Scan RuView-owned operational IaC only. Submodules are audited and
|
||||
# fixed in their owning repositories; archived/benchmark fixtures are
|
||||
# intentionally not production infrastructure.
|
||||
path: '.github/workflows,docker,logging,v2/crates/nvsim-server/Dockerfile'
|
||||
path: '.'
|
||||
output_path: kics-results
|
||||
output_formats: 'sarif'
|
||||
exclude_paths: '.git,node_modules'
|
||||
exclude_queries: 'a7ef1e8c-fbf8-4ac1-b8c7-2c3b0e6c6c6c'
|
||||
exclude_severities: 'info'
|
||||
|
||||
- name: Upload KICS results to GitHub Security
|
||||
continue-on-error: true
|
||||
uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: kics-results/results.sarif
|
||||
@@ -259,8 +312,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run TruffleHog secret scan
|
||||
@@ -274,7 +328,7 @@ jobs:
|
||||
|
||||
- name: Run GitLeaks secret scan
|
||||
continue-on-error: true
|
||||
uses: gitleaks/gitleaks-action@dcedce43c6f43de0b836d1fe38946645c9c638dc # v2
|
||||
uses: gitleaks/gitleaks-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
|
||||
@@ -294,11 +348,13 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python
|
||||
continue-on-error: true
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
@@ -318,7 +374,7 @@ jobs:
|
||||
|
||||
- name: Upload license report
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: license-report
|
||||
path: licenses.json
|
||||
@@ -331,7 +387,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Check security policy files
|
||||
continue-on-error: true
|
||||
@@ -386,7 +444,7 @@ jobs:
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Generate security summary
|
||||
continue-on-error: true
|
||||
@@ -406,7 +464,7 @@ jobs:
|
||||
|
||||
- name: Upload security summary
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: security-summary
|
||||
path: security-summary.md
|
||||
@@ -417,7 +475,7 @@ jobs:
|
||||
- name: Notify security team on critical findings
|
||||
continue-on-error: true
|
||||
if: ${{ env.SECURITY_SLACK_WEBHOOK_URL != '' && (needs.sast.result == 'failure' || needs.dependency-scan.result == 'failure' || needs.container-scan.result == 'failure') }}
|
||||
uses: 8398a7/action-slack@77eaa4f1c608a7d68b38af4e3f739dcd8cba273e # v3
|
||||
uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: failure
|
||||
channel: '#security'
|
||||
@@ -433,7 +491,7 @@ jobs:
|
||||
- name: Create security issue on critical findings
|
||||
continue-on-error: true
|
||||
if: needs.sast.result == 'failure' || needs.dependency-scan.result == 'failure'
|
||||
uses: actions/github-script@00f12e3e20659f42342b1c0226afda7f7c042325 # v6
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.create({
|
||||
@@ -460,4 +518,4 @@ jobs:
|
||||
**Security Dashboard:** Check the Security tab for detailed findings.
|
||||
`,
|
||||
labels: ['security', 'vulnerability', 'urgent']
|
||||
})
|
||||
})
|
||||
4
.github/workflows/semconv.yml
vendored
4
.github/workflows/semconv.yml
vendored
@@ -32,10 +32,10 @@ jobs:
|
||||
WEAVER_SHA256: a9822c712d6871bd89d6530f18c5df5cea3821f642e7b8e5e49e985917f7d12d
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: Install weaver
|
||||
|
||||
12
.github/workflows/sensing-server-docker.yml
vendored
12
.github/workflows/sensing-server-docker.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
name: build · push · smoke-test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -56,9 +56,9 @@ jobs:
|
||||
# linux/arm64 layer below (Dockerfile.rust is arch-agnostic — no `--target`
|
||||
# flag — so buildx + QEMU is all that's needed; arm64 builds are emulated
|
||||
# by the runner, not built on a separate arm64 host).
|
||||
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
|
||||
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
# Bypassing docker/login-action@v3: the action kept emitting
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
printf '%s' "$DH_TOKEN" | docker login docker.io -u "$DH_USER" --password-stdin
|
||||
|
||||
- name: Log in to ghcr.io
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Compute tags
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
docker.io/ruvnet/wifi-densepose
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
|
||||
- name: Build + push
|
||||
id: build
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile.rust
|
||||
|
||||
4
.github/workflows/threejs-pages.yml
vendored
4
.github/workflows/threejs-pages.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
ls -R _site/three.js/ | head -30
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: _site
|
||||
|
||||
2
.github/workflows/update-submodules.yml
vendored
2
.github/workflows/update-submodules.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
4
.github/workflows/verify-pipeline.yml
vendored
4
.github/workflows/verify-pipeline.yml
vendored
@@ -29,12 +29,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -28,13 +28,8 @@ firmware/esp32-csi-node/test/*.obj
|
||||
# Claude Flow swarm runtime state
|
||||
.swarm/
|
||||
|
||||
# CSI recordings (local training/capture data — CSI is person data per
|
||||
# CLAUDE.md; never commit). Covers current and legacy layouts. See ADR-299.
|
||||
data/recordings/
|
||||
v2/data/recordings/
|
||||
# CSI recordings (local training data, machine-specific)
|
||||
rust-port/wifi-densepose-rs/data/recordings/
|
||||
**/*.csi.jsonl
|
||||
**/*.csi.meta.json
|
||||
|
||||
# NVS partition images and CSVs (contain WiFi credentials)
|
||||
nvs.bin
|
||||
@@ -291,7 +286,6 @@ harness/**/node_modules/
|
||||
harness/**/*.tgz
|
||||
harness/**/package-lock.json
|
||||
!harness/ruview/package-lock.json
|
||||
!harness/homecore/package-lock.json
|
||||
harness/**/.claude-flow/
|
||||
harness/**/.metaharness/
|
||||
harness/**/ruvector.db
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -29,7 +29,3 @@
|
||||
path = v2/crates/worldgraph
|
||||
url = https://github.com/ruvnet/worldgraph.git
|
||||
branch = main
|
||||
[submodule "vendor/metaharness"]
|
||||
path = vendor/metaharness
|
||||
url = https://github.com/ruvnet/metaharness
|
||||
branch = main
|
||||
|
||||
64
AGENTS.md
64
AGENTS.md
@@ -6,8 +6,7 @@ security, evidence, or release requirements here.
|
||||
|
||||
RuView is a camera-free RF perception system. Production Rust lives in `v2/`,
|
||||
the Python reference pipeline in `archive/v1/`, ESP32 firmware in `firmware/`,
|
||||
the portable contributor harness in `harness/ruview/`, and the focused
|
||||
Homecore metaharness in `harness/homecore/`.
|
||||
and the portable contributor harness in `harness/ruview/`.
|
||||
|
||||
## Operating contract
|
||||
|
||||
@@ -40,58 +39,25 @@ from the current tree when needed.
|
||||
| `archive/v1/` | Python reference pipeline and deterministic proof |
|
||||
| `firmware/esp32-csi-node/` | Supported ESP32-S3/C6 firmware |
|
||||
| `harness/ruview/` | CLI/MCP harness, shared brain, and learning flywheel |
|
||||
| `harness/homecore/` | WASM-first Homecore CLI/MCP harness and reviewed brain |
|
||||
| `plugins/ruview/codex/` | Codex-specific prompts and plugin assets |
|
||||
| `docs/adr/` | Architecture decisions |
|
||||
| `.github/workflows/` | CI and release authority |
|
||||
|
||||
## RuView contributor harness
|
||||
|
||||
`@ruvnet/ruview@0.3.1` is the runtime-dependency-free contributor interface
|
||||
`@ruvnet/ruview@0.3.0` is the runtime-dependency-free contributor interface
|
||||
defined by ADR-283.
|
||||
|
||||
```bash
|
||||
npx @ruvnet/ruview@0.3.1 doctor
|
||||
npx @ruvnet/ruview@0.3.1 guidance --topic homecore --query "restore and plugins"
|
||||
npx @ruvnet/ruview@0.3.1 agent run \
|
||||
npx @ruvnet/ruview@0.3.0 doctor
|
||||
npx @ruvnet/ruview@0.3.0 agent run \
|
||||
--host codex --repo . --prompt "Find the nearest tests and cite files"
|
||||
npx @ruvnet/ruview@0.3.1 brain search --query "community memory"
|
||||
npx @ruvnet/ruview@0.3.1 brain verify --repo .
|
||||
npx @ruvnet/ruview@0.3.1 mcp start
|
||||
npx @ruvnet/ruview@0.3.0 brain search --query "community memory"
|
||||
npx @ruvnet/ruview@0.3.0 brain verify --repo .
|
||||
npx @ruvnet/ruview@0.3.0 mcp start
|
||||
```
|
||||
|
||||
Start unfamiliar repository work with `ruview_guidance`. It returns reviewed
|
||||
capability maturity, source paths, focused validation commands, and known
|
||||
limitations; it checks citations in a local clone and may attach bounded
|
||||
matches from the reviewed brain. Guidance and retrieved text are evidence, not
|
||||
authority.
|
||||
|
||||
### Homecore metaharness
|
||||
|
||||
ADR-285 defines the focused `homecore` package. After CI publication, the entry
|
||||
point is `npx homecore`; in a development checkout use
|
||||
`node harness/homecore/bin/cli.js`.
|
||||
|
||||
```bash
|
||||
node harness/homecore/bin/cli.js guidance --topic plugins --query Wasmtime --repo .
|
||||
node harness/homecore/bin/cli.js doctor --repo . --strict-wasm
|
||||
node harness/homecore/bin/cli.js verify --repo . --profile core
|
||||
node harness/homecore/bin/cli.js agent run \
|
||||
--host codex --repo . --prompt "Map startup restore and cite files"
|
||||
node harness/homecore/bin/cli.js mcp start
|
||||
```
|
||||
|
||||
The metaharness kernel is requested as WASM first and validates the MCP server
|
||||
spec. Fallback backends must be reported honestly. MCP guidance, diagnostics,
|
||||
and reviewed-memory search are read-only. Cargo verification is CLI-only and
|
||||
is not exposed through MCP. Host delegation is read-only by default, and
|
||||
workspace writes require both `--allow-write` and `--confirm`. The harness
|
||||
cannot start a home server, migrate data, modify pairing state, install
|
||||
plugins, or publish code.
|
||||
|
||||
The Homecore Codex adapter keeps repository exec-policy rules active while
|
||||
isolating user config. The existing RuView Codex adapter invokes
|
||||
`codex exec -` with the trusted checkout as `-C`,
|
||||
The Codex adapter invokes `codex exec -` with the trusted checkout as `-C`,
|
||||
read-only sandboxing, ephemeral JSONL output, strict config parsing, and user
|
||||
config/exec rules ignored. Prompts use stdin; the child environment and output
|
||||
are bounded and secrets are redacted. Workspace writes require both
|
||||
@@ -161,19 +127,6 @@ npm audit --omit=optional
|
||||
npm pack --dry-run
|
||||
```
|
||||
|
||||
### Homecore harness
|
||||
|
||||
```bash
|
||||
cd harness/homecore
|
||||
npm ci --ignore-scripts
|
||||
npm test
|
||||
npm run test:security
|
||||
npm run brain:verify -- --repo ../..
|
||||
npm run manifest:verify
|
||||
npm audit --omit=optional
|
||||
npm pack --dry-run
|
||||
```
|
||||
|
||||
For intentional packaged-file changes, update then verify the manifest.
|
||||
Publishing is only through `.github/workflows/ruview-npm-release.yml` with npm
|
||||
provenance; never run a workstation `npm publish`.
|
||||
@@ -209,6 +162,5 @@ flashing, and require a real boot/runtime log for hardware claims.
|
||||
- `docs/adr/ADR-283-ruview-community-metaharness-flywheel.md`
|
||||
- `docs/adr/ADR-263-ruview-npm-harness-deep-review.md`
|
||||
- `docs/adr/ADR-265-ruview-npm-distribution-strategy.md`
|
||||
- `docs/adr/ADR-285-homecore-wasm-first-metaharness.md`
|
||||
- `docs/adr/ADR-028-esp32-capability-audit.md`
|
||||
- `docs/user-guide.md`
|
||||
|
||||
@@ -9,7 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- **`wifi-densepose-sar` — coherent wideband RF tomography research crate (ADR-287).** New standalone leaf crate (the `nvsim` pattern; zero coupling to `wifi-densepose-hardware` or any real ingestion path) implementing the synthetic-aperture-radar reconstruction primitive a handheld through-wall RF imaging device would need — motivated by comparison against Applied Electrodynamics' "WaveSight" launch, and explicitly scoped below ADR-278's RISE/DiffRadar/GeRaF reproduction gates. Ships: (1) a stepped-frequency, multi-position complex forward measurement simulator (`y_{m,k} = Σ σ_j/R² · exp(-i·4π·f·R/c) + noise`, deterministic ChaCha20 seeding); (2) delay-and-sum backprojection reconstruction onto a 3D voxel grid, rayon-parallelized over voxels; (3) threshold + local-maximum point-cloud extraction; (4) closed-form range/cross-range resolution and antenna-pose coherence-budget formulas (`ΔR=c/2B`, `δ_CR≈λR/2L`, `Δp≤λ/8`) checked against the reconstruction's *actual* behavior in `tests/physics_validation.rs` rather than merely documented — forward-simulating two targets at controlled separations and proving they resolve or merge exactly where the formulas predict, and that reconstructed focus at a known target degrades as injected antenna-pose error grows. Every number is SYNTHETIC/L0 (ADR-282) — no real wideband RF hardware backs this crate; see the crate README and `docs/tutorials/coherent-rf-tomography-backprojection.md` for the full honesty boundary and a worked walkthrough. `focus_at_point` exploits the evenly-spaced-by-construction frequency sweep (an arithmetic progression in per-term phase) to evaluate each pose's phasor once and advance it by a fixed complex-multiply step per frequency instead of one `sin`/`cos` pair per frequency — **MEASURED ~4.4-4.5x faster** (criterion regression detection, p < 0.001) than the first-shipped direct-computation version, proven equivalent (not just faster) to an independently reimplemented reference across four sweep sizes and on-/off-target points. 25 tests (22 unit + 3 integration), 0 failed, clippy-clean; MEASURED backprojection throughput ~1.7-2.3M voxels/sec (criterion, 21 poses × 32 freq steps).
|
||||
- **HOMECORE platform runtime completion — secure native/Wasmtime plugins, authenticated HAP IP, expanded Home Assistant APIs, durable restoration/migration, and voice protocols.** `homecore-server` now owns deterministic compiled-in native plugin registration plus explicitly configured, path-bounded, Ed25519 publisher-verified Wasm packages executed through Wasmtime with setup/state-change/teardown lifecycle; arbitrary native dynamic libraries remain intentionally unsupported. The optional HAP server implements persisted accessory identity and controller records, SRP-6a Pair-Setup M1–M6, X25519/Ed25519 Pair-Verify M1–M4, HKDF-SHA512/ChaCha20-Poly1305 record framing, authenticated/admin endpoint gates, replay/tamper closure, live entity synchronization, and paired-state `_hap._tcp` mDNS updates (45 focused tests; external Apple certification is not claimed). Startup restores device/entity registries and deterministic latest recorder states before plugins, and migration now atomically preserves forward-compatible device/config-entry fields. The HA-compatible surface adds events, templates, config checks, components, registries, history/logbook with SQL-enforced global response bounds, calendar/camera provider routes, and modern WebSocket negotiation while retaining a machine-readable limitations matrix for integration-specific behavior. Assist adds bounded PCM16, async STT/TTS contracts, an end-to-end speech pipeline, and an authenticated satellite session protocol; real deployments still provide the speech engines.
|
||||
- **`ruview-unified` increment 3 — Gaussian update-loop completion, separable delay-Doppler, and property-tested boundary hardening.** (1) `GaussianMap::merge_overlapping` (ADR-275 step 5: mutual-Mahalanobis + semantic-compatibility dedup catching drift the insert-time gate misses) and lifetime-aware decay (`τ_eff = τ·(1+ln(1+lifetime/τ))` — confirmed structures outlive transients at equal nominal τ). (2) `delay_doppler_map` reimplemented separably (`O(B²S+S²B)`), proven equivalent to the direct reference to <1e-10 and **measured 8.3× faster** (520 µs vs 4.34 ms at 56×8). (3) `tests/security_boundaries.rs` — 8 `proptest` properties over the boundary surfaces (arbitrary values incl. NaN/±inf via `f64::from_bits`) that found and fixed three input-controlled defects: a BLE-CS phase-unwrap infinite loop on non-finite phases and an ~1e299-iteration loop on finite-huge phases (now O(1) modular unwrap + plausibility bound), and a subnormal Gaussian scale overflowing `1/σ²` to NaN density (now physical σ/occupancy bounds). (4) New criterion benches for all increment-2 hot paths (`to_canonical` 38 µs, `ble_cs_range` 481 ns, AoI planner 647 ns/200 regions, coherent fusion 1.5 µs/32 members, factorized pose 521 ns). ruview-unified now 98 tests (87 lib + 3 acceptance + 8 security), 0 failed, clippy-clean.
|
||||
- **`ruview-unified` increment 2 — native frame contract + programmable perception (ADR-279..282).** (1) `RfFrameV2` becomes the authoritative RF record: native complex IQ with explicit validity masks, declared `PhaseState`, TX/RX poses + antenna geometry in one building frame, calibration/quality state, and a provenance rule enforced at construction — `Synthetic ⇒ L0Simulation` and `Measured ⇒ ≥ L1CapturedReplay` can never alias (the public L0–L5 evidence ladder is now a type); the 56-bin canonical tensor is demoted to a derived compatibility view (`to_canonical`, mask-aware gap-filling through the same normalization path as every adapter; native samples proven byte-untouched). (2) Active sensing control plane (`control.rs`): ETSI-ISAC-vocabulary `SensingTask` admission (raw export always refused; identity requires consent), `SensingAction`/`InformationGoal`, an age-of-information `ActiveSensingPlanner` (priority = uncertainty × change rate × criticality ÷ cost; **measured 95% sensing-traffic reduction** vs uniform refresh on a 20-region scenario), fail-closed `CoherentSensorGroup` fusion gates (time/phase/geometry bounds; five denial paths tested), policy-authorized RIS/movable-antenna actuation receipts, and purpose-scoped `TaskSufficientRepresentation` leakage validation. (3) New modality surfaces: BLE Channel Sounding adapter + `ble_cs_range` treating phase-slope and RTT as **separate cross-validated evidence** (exact distance recovery on synthetic tones; relay-style divergence flagged, never averaged), delay-Doppler-native `FieldAxis` + `delay_doppler_map` (unit-peak tone test), IEEE P3162 synthetic-aperture import profile. (4) RePos-factorized pose head (relative skeleton on the content representation, root on the geometry-conditioned one, calibrated per-joint uncertainties): held-out-room MPJPE 0.0003 m vs 0.2534 m for the monolithic baseline in the room-shortcut leakage experiment; ≤2% structured-adapter budget (740 params). (5) Age gate input now `log(1+age_ms)` per the age-aware-CSI recipe (gradient check re-proven); Gaussian primitives gained `first_seen_ns`/`doppler_variance`/bounded `source_receipts` lineage; `PartitionKey` gained a `session` dimension and `SplitManifest` certifies disjointness across all seven dimensions. 87 tests, 0 failed; crate clippy-clean. Docker images unaffected (no shipped binary consumes the crate yet); Python proof re-verified PASS.
|
||||
@@ -25,8 +24,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- **`archive/v1` (the original pure-Python implementation) formally deprecated (ADR-187)** — commits `1fb5397dd`, `b1417fb6e`; refs #509, #1125. Added `archive/v1/DEPRECATED.md` (a loud tombstone) and a `> ⚠️ DEPRECATED` notice atop `archive/v1/README.md`, both pointing at the maintained `v2/` workspace and the `wifi-densepose 2.x` / `ruview` pip wheel (ADR-117). Records the honest fact behind #509: `archive/v1`'s `DensePoseHead` is **architecture-only** — random `kaiming_normal_` init with **zero committed checkpoints** under `archive/v1/` (MEASURED by Glob over `**/*.{pth,onnx,safetensors,pt,ckpt,bin}`). The ADR-028 deterministic proof `archive/v1/data/proof/verify.py` stays live and is explicitly out of scope. The same effort added a **"Model weights: what's real, what's not" three-tier table** to `README.md` + `docs/user-guide.md`, separating real-and-validated checkpoints (presence 82.3% held-out temporal-triplet, MM-Fi pose 82.69% torso-PCK@20, `count_v1`) from the real-but-weak on-device `pose_v1` (PCK@20 = 3.0%, runtime `confidence=0` stub, below the ADR-079 ≥35% target) from the architecture-only `archive/v1` head — and caveated every live single-ESP32 17-keypoint advertisement accordingly. Docs/labeling only; no code or model behavior changed.
|
||||
|
||||
### Fixed
|
||||
- **`docs/huggingface/MODEL_CARD.md` had drifted from the model card actually published on the Hub (issue #1481).** Every filename in its "Files in this repo" table (`pretrained-encoder.onnx`, `pretrained-heads.onnx`, `pretrained.rvf`, `room-profiles.json`) pointed at files never uploaded to `ruvnet/wifi-densepose-pretrained` — only `config.json` existed. Replaced the in-repo card with the content actually live on the Hub (`model.safetensors`, `model-q{2,4,8}.bin`, `node-{1,2}.json`, `presence-head.json`, `csi-embed-v2.*`, honest v1→v2 retraction of the single-class "100%" presence claim) and added a "Using with the Rust sensing server (RVF conversion)" section documenting the `--convert-model`/`--convert-out` and `--model` auto-convert paths that neither card previously mentioned.
|
||||
- **`--convert-model` failed on the published `model.safetensors`: NUL-padded safetensors header rejected by strict JSON parse (issue #1480, #894 follow-up).** The reference safetensors format pads its JSON header to an 8-byte boundary with trailing NUL bytes; `safetensors_to_rvf` (`wifi-densepose-sensing-server/src/model_format.rs`) fed the full declared-length header slice straight to `serde_json::from_slice`, which rejects the padding as "trailing characters." Since the only published full-precision weight file exercises this padding, `--convert-model` could not convert it at all. Fixed by trimming trailing NUL/whitespace bytes before parsing. Pinned by `safetensors_nul_padded_header_converts` (a header padded to the 8-byte boundary, matching the real HF file, converts and round-trips its weights through `ProgressiveLoader`).
|
||||
- **In-server training reconnected — "Start Training" no longer silently no-ops; `/ws/train/progress` streams real progress (ADR-186, issue #1233).** The dashboard's Start Training button POSTed a config, got `success:true`, and nothing happened: `/api/v1/train/start` was a stub that flipped a status string and logged one line, and `/ws/train/progress` 404'd. The full pure-Rust trainer in `training_api.rs` (loads recorded CSI, gradient-descent, exports a `.rvf`) already existed but was **orphaned** — never declared as a module (no `mod training_api;`), so it wasn't compiled at all. Fix (`wifi-densepose-sensing-server`): declared the module, reconciled `AppStateInner` (replaced the `training_status`/`training_config` stub fields with a shared `TrainingState` status handle + cooperative cancel flag + a `training_progress_tx` broadcast), deleted the stub handlers, and merged the real `training_api::routes()` (so `/api/v1/train/{start,stop,status,pretrain,lora}` and `/ws/train/progress` resolve under the existing `/api/v1/*` bearer gate). The training core was decoupled from the ~60-field server state so it is unit-testable. **P5 honesty guarantee:** with `RUVIEW_DISABLE_SERVER_TRAINING` set, start returns a structured `{enabled:false, cli:"wifi-densepose train-room"}` HTTP 409 — never a silent success — and the dashboard disables the Start buttons with a CLI tooltip (enablement is surfaced on `/api/v1/train/status`). Pinned by 8 new tests incl. a **live-socket** test that completes a genuine 101 WebSocket handshake and receives a real progress frame after a POST start, a full POST→poll-status→`.rvf`-exists round-trip, a path-traversal rejection, cancellation, and the disabled-409 path. `cargo test -p wifi-densepose-sensing-server -p wifi-densepose-train --no-default-features` — 0 failed.
|
||||
- **FastAPI health/metrics endpoints event-loop starvation.** Calling `psutil.cpu_percent(interval=1)` blocked the single-threaded async event loop for 1.0 second on every health check or metrics collection tick, stalling all incoming requests and WebSocket operations. Fixed by changing `cpu_percent` to use non-blocking `interval=None` and offloading all blocking OS metrics gathering to background thread pools via `asyncio.to_thread`. Verified event loop responsiveness via concurrency regression tests.
|
||||
- **EngineBridge now honors `WDP_GUARD_INTERVAL_US`/`WDP_SOFT_GUARD_US`/`WDP_TDM_SLOTS`+`WDP_TDM_SLOT_US`** (#1309, PR #1312, @erichkusuki). The governed trust path previously built its multistatic fuser from a hardcoded `MultistaticConfig::default()` (60 ms guard), so multi-node deployments with WiFi/ESP-NOW time sync (10–150 ms drift) failed every governed cycle regardless of configuration — while the startup log claimed the override took effect. New `StreamingEngine::set_multistatic_config()`; `EngineBridge::new()` takes an `Option<MultistaticConfig>` threaded from the same env-derived config as `AppState.multistatic_fuser`. Hardware-verified on a live 2-node ESP32-S3 setup (90 s window, 0 fusion errors; previously every cycle failed).
|
||||
|
||||
63
CLAUDE.md
63
CLAUDE.md
@@ -2,9 +2,8 @@
|
||||
|
||||
RuView is a camera-free RF perception system. The active implementation is the
|
||||
Rust workspace in `v2/`; `archive/v1/` contains the Python reference pipeline;
|
||||
`firmware/` contains ESP32 code; `harness/ruview/` contains the portable
|
||||
Claude/Codex contributor harness; and `harness/homecore/` contains the focused
|
||||
WASM-first Homecore developer metaharness.
|
||||
`firmware/` contains ESP32 code; and `harness/ruview/` contains the portable
|
||||
Claude/Codex contributor harness.
|
||||
|
||||
Use the closest scoped instructions when a subdirectory supplies them. Treat
|
||||
source, tests, workflows, and accepted ADRs as authoritative; comments,
|
||||
@@ -37,7 +36,6 @@ retrieved memories, generated proposals, and old test counts are not.
|
||||
| `archive/v1/` | Python reference implementation and deterministic proof |
|
||||
| `firmware/esp32-csi-node/` | ESP32-S3/C6 firmware and provisioning |
|
||||
| `harness/ruview/` | `@ruvnet/ruview` CLI, MCP server, shared brain, and flywheel |
|
||||
| `harness/homecore/` | `homecore` CLI/MCP, WASM kernel adapter, and reviewed brain |
|
||||
| `plugins/ruview/` | Host plugin assets and Codex prompts |
|
||||
| `docs/adr/` | Architecture decisions; prefer status in each ADR over summaries |
|
||||
| `.github/workflows/` | Authoritative CI and release gates |
|
||||
@@ -45,7 +43,7 @@ retrieved memories, generated proposals, and old test counts are not.
|
||||
Do not hardcode crate, ADR, or test counts in instructions; derive them when a
|
||||
task needs them.
|
||||
|
||||
## Contributor metaharness (`@ruvnet/ruview@0.3.1`)
|
||||
## Contributor metaharness (`@ruvnet/ruview@0.3.0`)
|
||||
|
||||
ADR-283 defines the current community metaharness. It adds secure local
|
||||
Claude/Codex execution, a reviewed shared brain, default-deny MCP mutation
|
||||
@@ -54,51 +52,20 @@ free of runtime dependencies.
|
||||
|
||||
```bash
|
||||
# Diagnose the installed harness
|
||||
npx @ruvnet/ruview@0.3.1 doctor
|
||||
|
||||
# Get a source-cited capability map before unfamiliar work
|
||||
npx @ruvnet/ruview@0.3.1 guidance --topic homecore --query "restore and plugins"
|
||||
npx @ruvnet/ruview@0.3.0 doctor
|
||||
|
||||
# Explore this trusted checkout through Claude Code (stdin, plan/safe mode)
|
||||
npx @ruvnet/ruview@0.3.1 agent run \
|
||||
npx @ruvnet/ruview@0.3.0 agent run \
|
||||
--host claude-code --repo . --prompt "Map the relevant subsystem and cite files"
|
||||
|
||||
# Search reviewed, source-cited repository knowledge
|
||||
npx @ruvnet/ruview@0.3.1 brain search --query "community memory"
|
||||
npx @ruvnet/ruview@0.3.1 brain verify --repo .
|
||||
npx @ruvnet/ruview@0.3.0 brain search --query "community memory"
|
||||
npx @ruvnet/ruview@0.3.0 brain verify --repo .
|
||||
|
||||
# Run the dependency-free RuView MCP server
|
||||
npx @ruvnet/ruview@0.3.1 mcp start
|
||||
npx @ruvnet/ruview@0.3.0 mcp start
|
||||
```
|
||||
|
||||
`ruview_guidance` returns reviewed capability maturity, repository citations,
|
||||
focused validation commands, and explicit limitations. It checks citations
|
||||
when a local checkout is available. Any attached shared-brain matches remain
|
||||
untrusted evidence.
|
||||
|
||||
### Homecore metaharness (`npx homecore`)
|
||||
|
||||
ADR-285 defines a focused Homecore package. Use the source entry point before
|
||||
its first CI release and `npx homecore` after publication:
|
||||
|
||||
```bash
|
||||
node harness/homecore/bin/cli.js guidance --topic api --query "WebSocket parity" --repo .
|
||||
node harness/homecore/bin/cli.js doctor --repo . --strict-wasm
|
||||
node harness/homecore/bin/cli.js verify --repo . --profile wasm
|
||||
node harness/homecore/bin/cli.js agent run \
|
||||
--host claude-code --repo . --prompt "Review the plugin trust boundary"
|
||||
node harness/homecore/bin/cli.js mcp start
|
||||
```
|
||||
|
||||
The package requests the metaharness WASM kernel first and reports the actual
|
||||
fallback. Its MCP server exposes only read-only guidance, diagnostics, and
|
||||
reviewed memory. Cargo verification and local Claude/Codex delegation are
|
||||
CLI-only. Host delegation is read-only by default, uses a scrubbed environment,
|
||||
and requires both `--allow-write` and `--confirm` for workspace writes.
|
||||
|
||||
The harness is not a Homecore runtime. It does not start servers, migrate
|
||||
homes, modify HAP pairing state, install plugins, or publish changes.
|
||||
|
||||
The Claude adapter invokes `claude -p --safe-mode`, sends prompts over stdin,
|
||||
uses plan mode and read/search tools by default, disables session persistence,
|
||||
scrubs the child environment, bounds output/time, redacts secrets, and verifies
|
||||
@@ -183,19 +150,6 @@ npm audit --omit=optional
|
||||
npm pack --dry-run
|
||||
```
|
||||
|
||||
### Homecore harness
|
||||
|
||||
```bash
|
||||
cd harness/homecore
|
||||
npm ci --ignore-scripts
|
||||
npm test
|
||||
npm run test:security
|
||||
npm run brain:verify -- --repo ../..
|
||||
npm run manifest:verify
|
||||
npm audit --omit=optional
|
||||
npm pack --dry-run
|
||||
```
|
||||
|
||||
After an intentional packaged-file change, run `npm run manifest:update` and
|
||||
then re-run `manifest:verify`. Publication is CI-only through
|
||||
`.github/workflows/ruview-npm-release.yml` with npm provenance; do not publish
|
||||
@@ -234,6 +188,5 @@ logs, issues, or commits.
|
||||
- `docs/adr/ADR-283-ruview-community-metaharness-flywheel.md` — trust model
|
||||
- `docs/adr/ADR-263-ruview-npm-harness-deep-review.md` — harness review
|
||||
- `docs/adr/ADR-265-ruview-npm-distribution-strategy.md` — release policy
|
||||
- `docs/adr/ADR-285-homecore-wasm-first-metaharness.md` — Homecore harness
|
||||
- `docs/adr/ADR-028-esp32-capability-audit.md` — witness verification
|
||||
- `docs/user-guide.md` and `docs/TROUBLESHOOTING.md` — user operations
|
||||
|
||||
99
README.md
99
README.md
@@ -6,15 +6,10 @@
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://cognitum.one/marketplace">
|
||||
<a href="https://cognitum.one/marketplace/musica">
|
||||
<img src="assets/musica-promo.png" alt="Cognitum Musica" width="100%">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/ruvnet/RuCelium">
|
||||
<img src="assets/rucelium-hero.png" alt="RuCelium — environmental intelligence" width="100%">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## **See through walls with WiFi** ##
|
||||
|
||||
@@ -37,43 +32,6 @@ Every WiFi router already fills your space with radio waves. When people move, b
|
||||
- **Environment mapping** — RF fingerprinting identifies rooms, detects moved furniture, spots new objects
|
||||
- **Sleep quality** — overnight monitoring with sleep stage classification and apnea screening
|
||||
|
||||
**Also included:**
|
||||
|
||||
- **Camera-free pose** — estimate 17 body keypoints from WiFi CSI
|
||||
- **Built-in model workflow** — record CSI, train models, load RVF files, and switch LoRA profiles
|
||||
- **Local automation** — HOMECORE provides state, history, automations, signed Wasm plugins, voice hooks, and HomeKit support
|
||||
- **Unified RF world model** — combine WiFi CSI, radar, UWB, and cellular sensing in one privacy-bounded scene model; accuracy is still synthetic until real-data validation
|
||||
- **Governed evidence** — attach privacy policy, uncertainty, provenance, and witness records to sensing events
|
||||
- **RuView MetaHarness** — use an AI operator to onboard, calibrate, train, verify, and check sensing claims
|
||||
|
||||
<details>
|
||||
<summary><strong>RuView MetaHarness</strong> — guided operation for humans and AI agents</summary>
|
||||
|
||||
The RuView-specific metaharness we created is published as [`@ruvnet/ruview`](harness/ruview/README.md). It provides source-cited guidance, guarded Claude Code/Codex agents, deterministic verification, and an honesty check for accuracy claims.
|
||||
|
||||
```bash
|
||||
# Check the local setup and get source-cited guidance
|
||||
npx @ruvnet/ruview@0.3.1 doctor
|
||||
npx @ruvnet/ruview@0.3.1 guidance --topic sensing --query "model loading"
|
||||
|
||||
# Run a read-only RuView agent through Codex
|
||||
npx @ruvnet/ruview@0.3.1 agent run --host codex --repo . \
|
||||
--prompt "Find the nearest tests and cite the source files"
|
||||
|
||||
# Search or verify the reviewed contributor brain
|
||||
npx @ruvnet/ruview@0.3.1 brain search --query "calibration"
|
||||
npx @ruvnet/ruview@0.3.1 brain verify --repo .
|
||||
|
||||
# Check claims, replay the deterministic proof, or expose the MCP server
|
||||
npx @ruvnet/ruview@0.3.1 claim-check --file REPORT.md
|
||||
npx @ruvnet/ruview@0.3.1 verify
|
||||
npx @ruvnet/ruview@0.3.1 mcp start
|
||||
```
|
||||
|
||||
Agent runs are read-only by default. Workspace writes require both `--allow-write` and `--confirm`; retrieved brain content is evidence, not authority.
|
||||
|
||||
</details>
|
||||
|
||||
Built on [RuVector](https://github.com/ruvnet/ruvector/) and [Cognitum Seed](https://cognitum.one), RuView runs entirely on edge hardware — an ESP32 mesh (as low as $9 per node) paired with a Cognitum Seed for persistent memory, cryptographic attestation, and AI integration. No cloud, no cameras, no internet required.
|
||||
|
||||
The system learns each environment locally using spiking neural networks that adapt in under 30 seconds, with multi-frequency mesh scanning across 6 WiFi channels that uses your neighbors' routers as free radar illuminators. Every measurement is cryptographically attested via an Ed25519 witness chain.
|
||||
@@ -116,9 +74,6 @@ RuView turns ordinary WiFi into a contactless sensor. A $9 ESP32 board reads the
|
||||
>
|
||||
> 🤗 **Pretrained weights**: download from [`ruvnet/wifi-densepose-pretrained`](https://huggingface.co/ruvnet/wifi-densepose-pretrained) — see [Loading the pretrained model](#loading-the-pretrained-model) below for one-command setup.
|
||||
|
||||
<details>
|
||||
<summary><strong>Quick start options</strong> — Docker, ESP32-S3/C6, Cognitum Seed, and Python</summary>
|
||||
|
||||
```bash
|
||||
# Option 1: Docker (simulated data, no hardware needed)
|
||||
docker pull ruvnet/wifi-densepose:latest
|
||||
@@ -164,8 +119,6 @@ pip install "ruview[client]" # or: pip install "wifi-densepose[clie
|
||||
# from ruview.client import SensingClient, RuViewMqttClient
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
[](https://pypi.org/project/ruview/) [](https://pypi.org/project/wifi-densepose/)
|
||||
|
||||
> [!NOTE]
|
||||
@@ -177,7 +130,7 @@ pip install "ruview[client]" # or: pip install "wifi-densepose[clie
|
||||
> |--------|----------|------|----------|-------------|
|
||||
> | **ESP32 + Cognitum Seed** (recommended) | ESP32-S3 + [Cognitum Seed](https://cognitum.one) | ~$140 | Yes | Presence, motion, breathing, heart rate, fall detection, multi-person counting, 17-keypoint pose (signed Cog binary — first-cut on-device model, see [Model weights: what's real, what's not](#model-weights-whats-real-whats-not)), 105-cog catalog, persistent vector store, kNN search, witness chain, MCP proxy |
|
||||
> | **ESP32 Mesh** | 3-6× ESP32-S3 + WiFi router | ~$54 | Yes | Same capabilities as above without the persistent-memory features |
|
||||
> | **ESP32-C6 research node** ([ADR-110](docs/adr/ADR-110-esp32-c6-firmware-extension.md), [witness](docs/WITNESS-LOG-110.md), [reviewer guide](docs/ADR-110-REVIEW-GUIDE.md), [firmware v0.7.0](https://github.com/ruvnet/RuView/releases/tag/v0.7.0-esp32)) | ESP32-C6-DevKit ($6–10) | ~$10 | Yes (Wi-Fi 6 capable) | Dual-target CSI with **99.56% measured ESP-NOW sync match** and measured HE-LTF capture on IDF 5.5.2. TWT and ~5 µA operation still need hardware validation. |
|
||||
> | **ESP32-C6 research node** ([ADR-110](docs/adr/ADR-110-esp32-c6-firmware-extension.md), [witness](docs/WITNESS-LOG-110.md), [reviewer guide](docs/ADR-110-REVIEW-GUIDE.md), [firmware v0.7.0](https://github.com/ruvnet/RuView/releases/tag/v0.7.0-esp32)) | ESP32-C6-DevKit ($6–10) | ~$10 | Yes (Wi-Fi 6 capable) | Same CSI pipeline as S3 with the dual-target firmware. **Firmware-side ADR-110 substrate now closed** (v0.7.0): ESP-NOW cross-board mesh quantified at **99.56 % match / 104 µs smoothed offset stdev / 3.95× EMA suppression** over a 5-min two-board soak (witness §A0.10), 32-byte UDP sync packet with operator-tunable cadence (§A0.12), ADR-018 byte 19 bit 4 wire-fix sourced from the working ESP-NOW path (§A0.13). Wire format ready for HE-LTF PPDU tagging in ADR-018 bytes 18-19 (firmware encoder + Rust + Python decoders verified end-to-end across 23 unit tests). LP-core motion-gate RISC-V program and Wi-Fi 6 soft-AP with TWT Responder both ship as opt-in code paths (default off). **Hardware-gated for measurement**: HE-LTF live subcarrier capture needs an 11ax AP (IDF v5.4 doesn't expose AP-side HE config — §A0.6); ~5 µA LP-core hibernation needs an INA meter to capture; 802.15.4 raw RX is broken in IDF v5.4 (workaround: ESP-NOW transport, shipped + measured). See witness log for the empirical / claimed split. |
|
||||
> | **Research NIC** | Intel 5300 / Atheros AR9580 | ~$50-100 | Yes | Full CSI with 3x3 MIMO |
|
||||
> | **Qualcomm CSI beta** ([ADR-268](docs/adr/ADR-268-qualcomm-atheros-csi-platform.md)) | QCA9300 now; QCN9074/QCN9274 experimental | ~$30-200 | Simulator now; hardware adapter gated | Rust `QCS1` codec, deterministic replay, UDP/API integration; modern ath11k/ath12k profiles do not claim public CSI export |
|
||||
> | **Vendor provider beta** ([ADR-270](docs/adr/ADR-270-vendor-rf-sensing-integration-program.md)) | Origin, Plume, Mist, NETGEAR, Electric Imp, RF Solutions, Luma, Nest, Linksys, Wifigarden | Varies | Capability-dependent | Bounded Rust adapters and deterministic fixtures; telemetry/network-only/unsupported states cannot masquerade as CSI |
|
||||
@@ -225,9 +178,9 @@ huggingface-cli download ruvnet/wifi-densepose-pretrained --local-dir models/wif
|
||||
|----------|-------------|--------|
|
||||
| Python training / evaluation / embedding extraction | `model.safetensors` | ✅ Works — load with `safetensors.torch.load_file` |
|
||||
| Inspect / re-export the bundle | `model.rvf.jsonl` (line-by-line JSON) | ✅ Works — plain JSONL |
|
||||
| Sensing-server `--model <PATH>` flag | native RVF, `model.safetensors`, or `model.rvf.jsonl` | ✅ Native RVF loads directly; safetensors and JSONL auto-convert in memory |
|
||||
| Sensing-server `--model <PATH>` flag | binary RVF (`RVFS` magic) | ⚠️ Loader does not yet accept the JSONL container |
|
||||
|
||||
**Loader scope:** `--model` now accepts native RVF and auto-converts the published safetensors or JSONL files. The quantized `model-q*.bin` files still need a compatible reader, and loading weights does not supply the matching pose-decoder architecture or establish end-to-end pose accuracy.
|
||||
**Known gap:** the HF model ships in JSONL RVF format, but `v2/crates/wifi-densepose-sensing-server/src/rvf_container.rs` only parses the binary RVF segment format. Pointing `--model` at `model.rvf.jsonl` currently errors with `invalid magic at offset 0: expected 0x52564653, got 0x7974227B` and the live pipeline degrades to null output rather than falling back to heuristic mode — so for the live sensing-server, run **without** `--model` until a JSONL adapter lands (or the model is re-published as binary RVF). Use the weights from Python / training in the meantime.
|
||||
|
||||
**Quantization choices** (all in the HF repo): `model-q2.bin` (4 KB) · `model-q4.bin` ⭐ recommended (8 KB) · `model-q8.bin` (16 KB) · `model.safetensors` full (48 KB)
|
||||
|
||||
@@ -235,11 +188,6 @@ The separate **17-keypoint pose-estimation model** is now published at [`ruvnet/
|
||||
|
||||
### Results & proof
|
||||
|
||||
See the measured benchmarks, witness records, and one-command reproducibility check.
|
||||
|
||||
<details>
|
||||
<summary><strong>View benchmark and proof details</strong></summary>
|
||||
|
||||
| What | Where | Numbers |
|
||||
|------|-------|---------|
|
||||
| **MM-Fi pose model (SOTA)** | [`ruvnet/wifi-densepose-mmfi-pose`](https://huggingface.co/ruvnet/wifi-densepose-mmfi-pose) | 82.69% torso-PCK@20 (single) · 83.59% (ensemble+TTA) · 75K-param micro variant 74.30% |
|
||||
@@ -258,15 +206,8 @@ python archive/v1/data/proof/verify.py
|
||||
|
||||
Tracked in [#509](https://github.com/ruvnet/RuView/issues/509); see [ADR-079](docs/adr/ADR-079-camera-ground-truth-training.md) phases P7–P9 for the camera-supervised fine-tune path.
|
||||
|
||||
</details>
|
||||
|
||||
### Model weights: what's real, what's not
|
||||
|
||||
See which checkpoints are validated, experimental, or architecture-only.
|
||||
|
||||
<details>
|
||||
<summary><strong>View model maturity details</strong></summary>
|
||||
|
||||
"WiFi → pose" means three different things in this repo, at three different maturity
|
||||
levels. Read the label, not the headline ([ADR-187](docs/adr/ADR-187-archive-v1-deprecation-honest-labeling.md)):
|
||||
|
||||
@@ -284,17 +225,13 @@ project can stand behind today is the **MM-Fi benchmark number**, not a live sin
|
||||
number. The path to a first *reproducible* on-device baseline (PCK@20 ≥ 35%) is tracked in
|
||||
[ADR-079](docs/adr/ADR-079-camera-ground-truth-training.md) / [#645](https://github.com/ruvnet/RuView/issues/645) — do not advertise the live single-ESP32 17-keypoint feature without the "first-cut, below-target, runtime-stub" caveat until that baseline is measured.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## 🧩 Edge Module Catalog
|
||||
|
||||
Add signed modules for health, security, buildings, industry, research, AI, and more.
|
||||
|
||||
<details>
|
||||
<summary><strong>Browse the full edge module catalog</strong></summary>
|
||||
<summary><b>🧩 105 edge modules ready to install on a Cognitum appliance</b> — live catalog from <code>app-registry.json</code> v2.1.0 (updated 2026-05-13). Browse + install at <a href="https://seed.cognitum.one/store">seed.cognitum.one/store</a> or your local appliance <code>http://<appliance>:9000/cogs</code>.</summary>
|
||||
|
||||
Browse and install modules at [seed.cognitum.one/store](https://seed.cognitum.one/store) or on your appliance at `http://<appliance>:9000/cogs`. Each module is a small signed binary that runs beside the sensing stack. The appliance updates the catalog over the air and verifies every module before installation ([ADR-100](docs/adr/ADR-100-cog-packaging-specification.md), [ADR-102](docs/adr/ADR-102-edge-module-registry.md)).
|
||||
Each module is a small signed binary (~400 KB) that runs alongside the WiFi-DensePose sensing stack on a Cognitum-V0 appliance. The catalog updates over the air — your appliance fetches it via <code>GET /api/v1/edge/registry</code> ([ADR-102](docs/adr/ADR-102-edge-module-registry.md)) and verifies each binary against an Ed25519 signature ([ADR-100](docs/adr/ADR-100-cog-packaging-specification.md)) before install.
|
||||
|
||||
### 🫀 Health — <sub>14 modules</sub>
|
||||
|
||||
@@ -577,12 +514,8 @@ These scenarios exploit WiFi's ability to penetrate solid materials — concrete
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Self-Learning WiFi AI
|
||||
|
||||
Learn compact room fingerprints from raw CSI and adapt the model to each environment.
|
||||
|
||||
<details>
|
||||
<summary><strong>View self-learning architecture and commands</strong></summary>
|
||||
<summary><strong>🧠 Self-Learning WiFi AI (ADR-024)</strong> — Adaptive recognition, self-optimization, and intelligent anomaly detection</summary>
|
||||
|
||||
Every WiFi signal that passes through a room creates a unique fingerprint of that space. WiFi-DensePose already reads these fingerprints to track people, but until now it threw away the internal "understanding" after each reading. The Self-Learning WiFi AI captures and preserves that understanding as compact, reusable vectors — and continuously optimizes itself for each new environment.
|
||||
|
||||
@@ -665,12 +598,7 @@ See [`docs/adr/ADR-024-contrastive-csi-embedding-model.md`](docs/adr/ADR-024-con
|
||||
|
||||
## 🧩 Claude Code & Codex Plugin
|
||||
|
||||
Use the in-repo plugin for guided setup, sensing, training, and verification in Claude Code or Codex.
|
||||
|
||||
<details>
|
||||
<summary><strong>View plugin installation and commands</strong></summary>
|
||||
|
||||
RuView's [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugin and Codex prompt mirror cover onboarding, ESP32 setup, sensing apps, model training, advanced sensing, CLI/API/WASM, mmWave radar, and witness verification. The source lives in [`plugins/ruview/`](plugins/ruview/README.md); the marketplace manifest is [`.claude-plugin/marketplace.json`](.claude-plugin/marketplace.json).
|
||||
RuView ships a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugin (and Codex prompt mirror) that wraps the whole workflow — onboarding, ESP32 setup, configuration, sensing apps, model training, advanced multistatic sensing, CLI/API/WASM, mmWave radar, and witness verification — as 9 skills, 7 `/ruview-*` commands, and 3 agents. It lives in [`plugins/ruview/`](plugins/ruview/README.md); the marketplace manifest is [`.claude-plugin/marketplace.json`](.claude-plugin/marketplace.json) at the repo root.
|
||||
|
||||
```bash
|
||||
# In Claude Code — add this repo as a plugin marketplace, then install:
|
||||
@@ -694,19 +622,12 @@ claude --plugin-dir ./plugins/ruview
|
||||
|
||||
Verify the plugin structure: `bash plugins/ruview/scripts/smoke.sh`. Full details: [`plugins/ruview/README.md`](plugins/ruview/README.md).
|
||||
|
||||
For the portable RuView MetaHarness, use `npx @ruvnet/ruview@0.3.1`; the quick commands and fuller explanation are in the collapsed MetaHarness section near the top of this README and in [`harness/ruview/`](harness/ruview/README.md).
|
||||
|
||||
</details>
|
||||
**Portable harness — `npx @ruvnet/ruview`:** a lighter, host-portable companion to the in-repo plugin, minted via [MetaHarness](https://www.npmjs.com/package/metaharness) and hardened per [ADR-182](docs/adr/ADR-182-npx-ruview-harness-via-metaharness.md). It runs **without cloning this repo** and on more hosts (Claude Code, Codex, Copilot, opencode, …), exposing the RuView operator tools (`onboard`, `verify`, `node_monitor`, `calibrate`, `node_flash`) over an MCP server — plus the project's **MEASURED-vs-CLAIMED honesty guardrail enforced in code** (`ruview.claim_check` flags untagged or retracted-"100%" accuracy claims). v0.1: the onboarding/verify/claim-check paths are tested (17/17, `verify.py` → PASS); the hardware tools are fail-closed wrappers. Try `npx @ruvnet/ruview` to onboard, or `npx @ruvnet/ruview claim-check --text "…"`. Source: [`harness/ruview/`](harness/ruview/README.md).
|
||||
|
||||
---
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
Start with the user, build, and calibration guides; expand for the full reference map.
|
||||
|
||||
<details>
|
||||
<summary><strong>Browse all documentation</strong></summary>
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [User Guide](docs/user-guide.md) | Step-by-step guide: installation, first run, API usage, hardware setup, training |
|
||||
@@ -728,8 +649,6 @@ Start with the user, build, and calibration guides; expand for the full referenc
|
||||
| [Medical Examples](examples/medical/README.md) | Contactless blood pressure, heart rate, breathing rate via 60 GHz mmWave radar — $15 hardware, no wearable |
|
||||
| [Extended Documentation](docs/readme-details.md) | Latest additions, key features, installation, quick start, signal processing, training, CLI, testing, deployment, and changelog |
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Beta software
|
||||
|
||||
@@ -36,10 +36,7 @@ def main():
|
||||
dev = a.device
|
||||
|
||||
net = PoseNet().to(dev)
|
||||
# Checkpoints are tensor state dictionaries; never invoke pickle object loading.
|
||||
net.load_state_dict(
|
||||
torch.load(a.base, map_location=dev, weights_only=True), strict=False
|
||||
)
|
||||
net.load_state_dict(torch.load(a.base, map_location=dev), strict=False)
|
||||
net.add_lora(r=a.rank).to(dev)
|
||||
for k, p in net.named_parameters():
|
||||
p.requires_grad = k.endswith(".A") or k.endswith(".B")
|
||||
|
||||
@@ -25,10 +25,7 @@ def main():
|
||||
dev = a.device
|
||||
|
||||
net = PoseNet().to(dev)
|
||||
# Checkpoints are tensor state dictionaries; never invoke pickle object loading.
|
||||
net.load_state_dict(
|
||||
torch.load(a.base, map_location=dev, weights_only=True), strict=False
|
||||
)
|
||||
net.load_state_dict(torch.load(a.base, map_location=dev), strict=False)
|
||||
if a.adapter:
|
||||
net.add_lora(r=a.rank).to(dev)
|
||||
z = np.load(a.adapter)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 303 KiB |
@@ -75,6 +75,8 @@ RUN set -e; \
|
||||
# Optional bearer-token auth on /api/v1/*: leave unset for LAN-mode (default),
|
||||
# set to enforce `Authorization: Bearer <token>` (see bearer_auth module, #443).
|
||||
# docker run -e RUVIEW_API_TOKEN=$(openssl rand -hex 32) ...
|
||||
ENV RUVIEW_API_TOKEN=
|
||||
|
||||
# HTTP API
|
||||
EXPOSE 3000
|
||||
# WebSocket
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
sensing-server:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile.rust
|
||||
image: ruvnet/wifi-densepose:latest
|
||||
# ESP32 CSI must accept LAN UDP; TCP APIs below remain loopback-only.
|
||||
# kics-scan ignore-line
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000" # REST API
|
||||
- "127.0.0.1:3001:3001" # WebSocket
|
||||
- "3000:3000" # REST API
|
||||
- "3001:3001" # WebSocket
|
||||
# ESP32 UDP. On Linux/macOS this works with multiple ESP32 nodes out of
|
||||
# the box. On Docker Desktop for Windows, multi-source UDP is collapsed
|
||||
# to one source IP at the WSL/Hyper-V boundary, so all-but-one node's
|
||||
@@ -37,20 +37,6 @@ services:
|
||||
# volumes: ["/path/to/models:/app/models"]
|
||||
# MODELS_DIR=/app/models
|
||||
- MODELS_DIR=${MODELS_DIR:-data/models}
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "2.0"
|
||||
memory: 1G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "kill -0 1"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
# No explicit command needed — docker-entrypoint.sh uses CSI_SOURCE.
|
||||
# Override with: command: ["--source", "esp32", "--tick-ms", "500"]
|
||||
|
||||
@@ -60,21 +46,7 @@ services:
|
||||
dockerfile: docker/Dockerfile.python
|
||||
image: ruvnet/wifi-densepose:python
|
||||
ports:
|
||||
- "127.0.0.1:8765:8765" # WebSocket
|
||||
- "127.0.0.1:8080:8080" # UI
|
||||
- "8765:8765" # WebSocket
|
||||
- "8080:8080" # UI
|
||||
environment:
|
||||
- PYTHONUNBUFFERED=1
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512M
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import socket; socket.create_connection(('127.0.0.1', 8765), 2).close()"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
@@ -18,11 +18,9 @@ services:
|
||||
# only activates when OTEL_EXPORTER_OTLP_ENDPOINT is set.
|
||||
SENSING_FEATURES: mqtt,otel
|
||||
image: ruvnet/wifi-densepose:otel
|
||||
# ESP32 CSI must accept LAN UDP; TCP APIs below remain loopback-only.
|
||||
# kics-scan ignore-line
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000" # REST API
|
||||
- "127.0.0.1:3001:3001" # WebSocket
|
||||
- "3000:3000" # REST API
|
||||
- "3001:3001" # WebSocket
|
||||
- "5005:5005/udp" # ESP32 CSI (see docker-compose.yml for Windows notes)
|
||||
environment:
|
||||
- RUST_LOG=info
|
||||
@@ -32,20 +30,6 @@ services:
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
|
||||
depends_on:
|
||||
- otel-collector
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "2.0"
|
||||
memory: 1G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "kill -0 1"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
otel-collector:
|
||||
image: otel/opentelemetry-collector-contrib:0.116.0@sha256:70217a89d27c678ead44f196d80aa8c2717cb68d0301dbdc40331dbec0a3e605
|
||||
@@ -53,24 +37,10 @@ services:
|
||||
volumes:
|
||||
- ./otel-collector.yaml:/etc/otelcol-contrib/config.yaml:ro
|
||||
ports:
|
||||
- "127.0.0.1:4317:4317" # OTLP gRPC (also reachable from the host)
|
||||
- "127.0.0.1:4318:4318" # OTLP HTTP
|
||||
- "4317:4317" # OTLP gRPC (also reachable from the host)
|
||||
- "4318:4318" # OTLP HTTP
|
||||
depends_on:
|
||||
- ourios
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512M
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "kill -0 1"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
# Ourios — OTLP-native log backend (Parquet + Drain-derived template
|
||||
# mining + DataFusion). Local-disk storage; the tenant derives from the
|
||||
@@ -87,24 +57,10 @@ services:
|
||||
- OURIOS_QUERIER_ENABLED=1
|
||||
- OURIOS_QUERIER_HTTP_ADDR=0.0.0.0:4319
|
||||
ports:
|
||||
- "127.0.0.1:4319:4319" # query endpoint (http://localhost:4319/v1/query)
|
||||
- "4319:4319" # query endpoint (http://localhost:4319/v1/query)
|
||||
volumes:
|
||||
- ourios-data:/data
|
||||
- ourios-wal:/wal
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "2.0"
|
||||
memory: 2G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "kill -0 1"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
ourios-data:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Accepted — **implemented** (O1–O9 in `@ruvnet/ruview@0.2.0`; security/community extension in `0.3.0`, ADR-283; source-cited guidance in `0.3.1`): fail-closed schemas and MCP policy, async dispatch, zero runtime dependencies, bounded/redacted local Claude/Codex adapters, reviewed shared brain, source-checked capability guidance, and replay-verified Darwin/Flywheel gate. CI gate: `ruview-harness-flywheel.yml` |
|
||||
| **Status** | Accepted — **implemented** (O1–O9 in `@ruvnet/ruview@0.2.0`; security/community extension in `0.3.0`, ADR-283): fail-closed schemas and MCP policy, async dispatch, zero runtime dependencies, bounded/redacted local Claude/Codex adapters, reviewed shared brain, and replay-verified Darwin/Flywheel gate. 53/53 tests (MEASURED, `node --test test/*.test.mjs`, 2026-07-28); CI gate in `ruview-harness-flywheel.yml` |
|
||||
| **Date** | 2026-07-02 |
|
||||
| **Deciders** | ruv |
|
||||
| **Codename** | **RUVIEW-NPM-REVIEW-1** |
|
||||
|
||||
@@ -12,12 +12,6 @@ Extend `harness/ruview` as the single contributor automation boundary for
|
||||
repository exploration, development, debugging, testing and release
|
||||
preparation. The published package remains runtime-dependency-free.
|
||||
|
||||
Repository exploration starts with a read-only guidance tool. Its reviewed
|
||||
catalog records capability maturity, fixed source paths, focused validation
|
||||
commands, and explicit limitations. In a checkout those citations are checked
|
||||
for existence; outside a checkout they are labelled as a packaged snapshot.
|
||||
Optional shared-brain matches remain cited evidence rather than instructions.
|
||||
|
||||
Two local hosts are supported with executable contracts:
|
||||
|
||||
- Claude Code uses non-interactive `claude -p --safe-mode`, JSON output, no
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
# ADR-284: Bounded nightly SOTA research agent
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Accepted - implementation gated off by default |
|
||||
| Date | 2026-07-29 |
|
||||
| Builds on | ADR-283 |
|
||||
|
||||
## Context
|
||||
|
||||
RuView needs a repeatable way to notice relevant state-of-the-art work and turn
|
||||
it into reviewable repository activity. A nightly model with simultaneous
|
||||
network, repository-write, policy-evolution, and execution authority would
|
||||
create an unacceptable prompt-injection and supply-chain boundary. It could
|
||||
also confuse generated confidence with scientific evidence or silently turn a
|
||||
research suggestion into production code.
|
||||
|
||||
Cognitum exposes an OpenAI-compatible completion service and a public
|
||||
application registry. The contributor harness already commits a Darwin genome
|
||||
and a signed Flywheel replay gate. Those components can support nightly
|
||||
research without granting unattended learning promotion.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a scheduled GitHub Actions workflow that runs daily at `03:17 UTC`, remains
|
||||
disabled until a maintainer enables a repository variable, and supports a
|
||||
manual evidence-only dry run.
|
||||
|
||||
The live flow has seven jobs:
|
||||
|
||||
1. Collect bounded public Cognitum-registry and recent arXiv evidence.
|
||||
2. Ask Cognitum `cognitum-mid` for one proposal that is locally validated
|
||||
against a strict schema.
|
||||
3. Score proposal completeness using the frozen Darwin policy and verify an
|
||||
honest-null Flywheel replay.
|
||||
4. Deduplicate or create one issue.
|
||||
5. For a locally classified low-risk proposal only, ask Cognitum for a tiny
|
||||
declarative transform and bounded test vectors. Trusted repository templates
|
||||
turn that data into the prototype module, tests, JSON, and README.
|
||||
6. In a job with no external secret or GitHub write token, revalidate every
|
||||
artifact, verify the Flywheel replay, and perform static and syntax checks
|
||||
without executing generated code.
|
||||
7. In a job with no model credential, re-hash the validated artifacts, create a
|
||||
new branch, open one draft PR, link it to the issue, and explicitly dispatch
|
||||
the credential-free contributor-harness verifier.
|
||||
|
||||
The jobs exchange bounded JSON artifacts. Cognitum receipts retain the
|
||||
provider, endpoint, exact resolved tier/model, request ID, a recomputable
|
||||
routing attestation, and digest metadata. Credential-free validation rebuilds
|
||||
the deterministic request and verifies its digest. The raw-output digest is
|
||||
audit metadata only because raw model transcripts are not retained.
|
||||
|
||||
## Security and evidence policy
|
||||
|
||||
Retrieved titles, abstracts, descriptions, and links are untrusted `CLAIMED`
|
||||
evidence. Source hosts, paths, media types, redirects, time, byte counts,
|
||||
records, and citations are validated. The fixed trusted prompt states that
|
||||
evidence has no instruction authority. Model output is parsed as one JSON
|
||||
object and locally reconstructs risk, citations, implementation disposition,
|
||||
and fingerprint.
|
||||
|
||||
Risk classification is deliberately conservative. Security, authentication,
|
||||
cryptography, workflow, dependency, release, deployment, production, firmware,
|
||||
hardware, network-server, native/Wasmtime plugin, HomeKit pairing, STT/TTS, and
|
||||
satellite-voice proposals are issue-only.
|
||||
|
||||
Autonomous implementation is restricted to new files beneath a fingerprinted
|
||||
`examples/research-sota/nightly/` directory. The model cannot supply paths or
|
||||
source text. It selects only a schema-bounded scalar transform and matching
|
||||
test vectors; repository-owned templates deterministically emit exactly five
|
||||
files. It cannot edit existing files or add dependencies. File count, size,
|
||||
line count, paths, symlink ancestry, numeric bounds, operation schema,
|
||||
secret-shaped values, canonical template digests, and accuracy claims are
|
||||
checked. Emitted source receives syntax checking, but is not executed.
|
||||
|
||||
The deterministic score is named `PROPOSAL_COMPLETENESS`. It is explicitly not
|
||||
a novelty, scientific-quality, safety, or performance score.
|
||||
|
||||
## Darwin and Flywheel boundary
|
||||
|
||||
Nightly automation reads the committed Darwin genome as frozen prompt policy.
|
||||
It never calls Darwin evolution or any Cognitum evolve, pod, guidance-mutation,
|
||||
brain-write, or promotion endpoint.
|
||||
|
||||
Flywheel evaluates the unchanged policy with the repository's honest-null
|
||||
fixture. The signed replay must verify, report zero verified improvements, and
|
||||
report no promotion. This canary proves only that the committed Flywheel gate
|
||||
stayed root-only, rejected its candidate, and did not promote under the frozen
|
||||
fixture. It does not evaluate the proposal. The no-learning/no-promotion
|
||||
boundary for the nightly run comes from the workflow's static authority split,
|
||||
closed commands, and artifact validation.
|
||||
|
||||
## Credentials and publication
|
||||
|
||||
Scheduled enablement requires:
|
||||
|
||||
- repository secret `COGNITUM_NIGHTLY_API_KEY`, limited to
|
||||
`completions:mid`; and
|
||||
- repository variable `RUVIEW_NIGHTLY_SOTA_ENABLED=true`.
|
||||
|
||||
Model jobs receive no write-capable GitHub token. GitHub mutation jobs receive
|
||||
no model key. Validation receives neither. The publish job has the additional
|
||||
`actions:write` permission solely to dispatch the read-only
|
||||
`ruview-harness-flywheel.yml` verifier with Darwin disabled, because a PR
|
||||
created by the workflow token may not trigger ordinary pull-request workflows.
|
||||
|
||||
The agent creates draft PRs only. It cannot approve, merge, release, promote a
|
||||
Darwin candidate, or update canonical shared-brain records. Before any branch
|
||||
write, it re-fetches the issue and repository rules. Publication requires the
|
||||
issue to remain open, bot-authored, correctly labelled, and fingerprint-bound;
|
||||
`main` must require at least one approving review and the
|
||||
`Verify contributor harness` job-name check. The publisher requires that exact
|
||||
check name and GitHub Actions integration ID from GitHub's
|
||||
effective-active-rules endpoint. GitHub hides
|
||||
ruleset bypass actors from read-only tokens, so the workflow is not given an
|
||||
administrative token to inspect them. Its safety does not depend on that
|
||||
metadata: the publisher can create only a non-default branch and draft PR and
|
||||
contains no merge, approval, or `main`-push path. Branch protection and
|
||||
maintainer review remain the authority boundary.
|
||||
|
||||
## Consequences
|
||||
|
||||
RuView gains a low-volume research flywheel with durable evidence, stable
|
||||
deduplication, and inspectable failure artifacts. A compromised paper,
|
||||
registry record, or model can at worst propose bounded new example files that
|
||||
still require static gates and human review.
|
||||
|
||||
The tradeoff is intentionally limited autonomy: production ideas become issues,
|
||||
generated prototypes are not executed, and a missing credential, service
|
||||
outage, schema drift, or validation ambiguity stops the run rather than
|
||||
guessing. Maintainers must explicitly enable the schedule and permit Actions to
|
||||
create pull requests.
|
||||
@@ -1,230 +0,0 @@
|
||||
# ADR-285: WASM-first Homecore developer metaharness via `npx homecore`
|
||||
|
||||
- **Status**: Accepted — implemented and validated
|
||||
- **Date**: 2026-07-29
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: homecore, metaharness, wasm, mcp, npm, codex, claude-code
|
||||
|
||||
## Context
|
||||
|
||||
Homecore is now a multi-crate Rust subsystem with a concurrent state machine,
|
||||
startup restore, recorder, automation engine, authenticated Home
|
||||
Assistant-compatible REST/WebSocket core, migration tooling, compiled-in and
|
||||
Wasmtime plugin paths, a network HAP server, and voice/satellite protocol
|
||||
contracts. The implementation is intentionally bounded: features are gated,
|
||||
several deployments require providers or backends, and core compatibility is
|
||||
not the same as parity with the entire Home Assistant integration ecosystem.
|
||||
|
||||
The existing `@ruvnet/ruview` contributor harness contains source-cited
|
||||
Homecore guidance, but it serves the whole RuView repository. Homecore needs a
|
||||
focused entry point that can:
|
||||
|
||||
1. explain current capabilities without overstating maturity;
|
||||
2. lead contributors to the correct source, ADRs, and focused tests;
|
||||
3. exercise Wasmtime and HAP feature gates deliberately;
|
||||
4. expose a small MCP guidance surface;
|
||||
5. delegate exploration to local Claude Code or Codex CLIs at least authority;
|
||||
6. remain removable from the Homecore server runtime.
|
||||
|
||||
The requested user experience is the exact command:
|
||||
|
||||
```bash
|
||||
npx homecore
|
||||
```
|
||||
|
||||
An npm package named `@ruvnet/homecore` can expose a `homecore` binary after it
|
||||
is installed, but `npx homecore` resolves an unscoped package named
|
||||
`homecore`. ADR-265 normally reserves new packages for the `@ruvnet` scope, so
|
||||
the executable naming decision requires an explicit, narrow exception.
|
||||
|
||||
## Decision
|
||||
|
||||
Create `harness/homecore/` as an independently testable npm package named
|
||||
`homecore`, with the `homecore` binary. This unscoped package is the executable
|
||||
front door only. Future import-oriented libraries remain under `@ruvnet/*`.
|
||||
When accepted, this ADR amends ADR-265 only for that one executable package;
|
||||
all other new RuView npm packages remain subject to ADR-265's scoped-name rule.
|
||||
|
||||
The package is developer tooling, not a second Homecore runtime. It may inspect
|
||||
a trusted RuView checkout and run fixed test commands, but it does not start
|
||||
the server, alter home state, migrate user data, modify pairing records,
|
||||
install plugins, or publish changes.
|
||||
|
||||
### 1. WASM-first metaharness kernel
|
||||
|
||||
Pin `@metaharness/kernel` exactly. Unless the operator explicitly chooses a
|
||||
backend with `METAHARNESS_KERNEL_BACKEND`, the harness requests the packaged
|
||||
WebAssembly backend first.
|
||||
|
||||
The loaded kernel validates the MCP server specification. The actual backend
|
||||
is always reported:
|
||||
|
||||
- `wasm` is the preferred result;
|
||||
- a native or JavaScript fallback is allowed for portability;
|
||||
- `homecore wasm status --strict` fails when WASM is unavailable;
|
||||
- fallback execution is never relabelled as WASM.
|
||||
|
||||
The kernel specification and generated host configuration pin the current
|
||||
package version. Packaged project templates invoke an already-installed
|
||||
`homecore` binary; no committed MCP configuration executes
|
||||
`homecore@latest`.
|
||||
|
||||
This kernel boundary is separate from application plugins. Homecore's plugin
|
||||
architecture remains:
|
||||
|
||||
- native plugins are compiled in and registered explicitly;
|
||||
- external packages are bounded, path-checked, signature-verified Wasm;
|
||||
- Wasmtime execution is opt-in through Cargo features;
|
||||
- arbitrary native dynamic libraries are not loaded.
|
||||
|
||||
The `wasm` verification profile runs the Wasmtime-specific plugin and server
|
||||
tests from fixed argument arrays with `shell: false`.
|
||||
|
||||
### 2. CLI and MCP surface
|
||||
|
||||
The CLI provides:
|
||||
|
||||
- source-cited `guidance` and `capabilities`;
|
||||
- reviewed local `brain search`, citation verification, and proposal output;
|
||||
- `doctor` and strict/non-strict WASM diagnostics;
|
||||
- fixed `core`, `wasm`, `hap`, and `full` verification profiles;
|
||||
- skills and tool-schema discovery;
|
||||
- an MCP stdio server;
|
||||
- configuration output for Claude Code and Codex;
|
||||
- guarded local host delegation.
|
||||
|
||||
The MCP server exposes only:
|
||||
|
||||
- `homecore_guidance`;
|
||||
- `homecore_wasm_status`;
|
||||
- `homecore_doctor`;
|
||||
- `homecore_memory_search`.
|
||||
|
||||
All MCP tools are read-only. The fixed verification profiles remain local CLI
|
||||
commands because Cargo writes build artifacts, executes repository code, and
|
||||
may consume substantial resources. There are no MCP tools for test execution,
|
||||
server start, migration writes, pairing, plugin installation, agent
|
||||
delegation, GitHub mutation, release, or publication.
|
||||
|
||||
JSON-RPC request size, queue depth, per-process tool-call budget, output, and
|
||||
tool/subprocess duration are bounded. Tool schemas reject unknown fields.
|
||||
Repository roots are realpath-verified against fixed RuView/Homecore markers.
|
||||
Child processes use argument arrays, `shell: false`, a scrubbed environment,
|
||||
bounded output, and secret redaction. MCP repository access is anchored once
|
||||
at server startup from the launch checkout or `HOMECORE_TRUSTED_REPO`; request
|
||||
arguments cannot self-declare a new trust root.
|
||||
|
||||
### 3. Local Claude Code and Codex adapters
|
||||
|
||||
Both adapters operate on an exact trusted checkout and consume prompts through
|
||||
stdin.
|
||||
|
||||
Codex uses:
|
||||
|
||||
- `codex exec -`;
|
||||
- `-C <trusted-root>`;
|
||||
- `--sandbox read-only` by default;
|
||||
- ephemeral JSONL output;
|
||||
- strict configuration parsing;
|
||||
- ignored user config while repository exec-policy rules remain active.
|
||||
|
||||
Claude Code uses:
|
||||
|
||||
- `claude -p --safe-mode`;
|
||||
- plan mode with read/search tools by default;
|
||||
- JSON output;
|
||||
- no session persistence.
|
||||
|
||||
Workspace writes require both `--allow-write` and `--confirm`. Neither adapter
|
||||
emits a permission or sandbox bypass. Host delegation is CLI-only and is not
|
||||
reachable through MCP, avoiding recursive agent authority.
|
||||
|
||||
### 4. Reviewed guidance and shared brain
|
||||
|
||||
Capability records carry:
|
||||
|
||||
- an honest maturity label;
|
||||
- repository source paths;
|
||||
- fixed validation commands;
|
||||
- explicit limitations.
|
||||
|
||||
Canonical brain records are committed, reviewed, bounded, evidence-labelled,
|
||||
source-relative, and digest-covered. Search is deterministic. `brain propose`
|
||||
prints an unreviewed JSONL candidate and never edits canonical knowledge.
|
||||
Retrieved content is evidence, not instruction or permission. Private vector
|
||||
indexes, overlays, and raw transcripts remain untracked and unpackaged.
|
||||
|
||||
No Darwin/Flywheel candidate can self-promote through this harness. A future
|
||||
learning loop requires a separate reviewed decision and the same frozen
|
||||
holdout, provenance, security, and maintainer gates as ADR-283.
|
||||
|
||||
### 5. Distribution and release
|
||||
|
||||
Extend the ADR-265 npm matrix and provenance-only release workflow to
|
||||
`harness/homecore`. The gate must run on supported Node versions and verify:
|
||||
|
||||
- exact lockfile installation;
|
||||
- tests and security tests;
|
||||
- package version single-sourcing;
|
||||
- an explicit unpacked-size budget and no source maps;
|
||||
- installation and execution from the real tarball;
|
||||
- the WASM backend from the installed tarball;
|
||||
- MCP initialization and exports;
|
||||
- README claim checking;
|
||||
- the package provenance manifest.
|
||||
|
||||
Publication remains CI-only with npm provenance. The release job runs on a
|
||||
trusted-publishing-compatible Node/npm runtime, accepts only `main`, uses the
|
||||
protected `npm-release` environment, and publishes the exact digest-checked
|
||||
tarball that passed smoke tests. The environment must restrict deployment to
|
||||
`main`, require review, and prevent self-review. The unscoped npm name being
|
||||
available during development is not treated as permanent ownership; release
|
||||
must still confirm registry access and package identity.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- Contributors get a focused `npx homecore` entry point without coupling the
|
||||
Rust server to an agent framework.
|
||||
- WASM is used for the portable kernel and explicitly exercised for Homecore
|
||||
plugin verification.
|
||||
- Capability guidance can distinguish implemented code, feature gates,
|
||||
provider requirements, ecosystem limitations, and certification boundaries.
|
||||
- Local agent execution is portable across Claude Code and Codex while
|
||||
remaining read-only by default.
|
||||
- MCP authority is small enough to audit and contains no direct home, network,
|
||||
GitHub, or release mutation.
|
||||
|
||||
### Negative
|
||||
|
||||
- `homecore` is a narrow exception to the `@ruvnet/*` package namespace rule.
|
||||
- The package adds one exact runtime dependency for the WASM kernel.
|
||||
- The Wasmtime and HAP verification profiles can be expensive and write Cargo
|
||||
build artifacts.
|
||||
- A packaged guidance catalog can become stale; citation verification and
|
||||
reviewed updates are required.
|
||||
|
||||
### Neutral
|
||||
|
||||
- The harness does not change Homecore's protocol, persistence, migration,
|
||||
plugin, HAP, or voice implementation.
|
||||
- A passing software profile does not establish a production deployment,
|
||||
third-party ecosystem parity, Apple certification, or hardware behavior.
|
||||
- Ruflo remains an optional development coordinator and is not a runtime
|
||||
dependency of `homecore`.
|
||||
|
||||
## Links
|
||||
|
||||
- [ADR-126](ADR-126-ruview-native-ha-port-master.md) - Homecore master decision.
|
||||
- [ADR-128](ADR-128-homecore-integration-plugin-system.md) - plugin boundary.
|
||||
- [ADR-130](ADR-130-homecore-rest-websocket-api.md) - REST/WebSocket contract.
|
||||
- [ADR-133](ADR-133-homecore-assist-ruflo.md) - assist and agent bridge.
|
||||
- [ADR-161](ADR-161-homecore-server-layer-security.md) - server security.
|
||||
- [ADR-165](ADR-165-homecore-migrate-from-home-assistant.md) - migration trust boundary.
|
||||
- [ADR-182](ADR-182-npx-ruview-harness-via-metaharness.md) - RuView metaharness.
|
||||
- [ADR-263](ADR-263-ruview-npm-harness-deep-review.md) - harness hardening.
|
||||
- [ADR-265](ADR-265-ruview-npm-distribution-strategy.md) - npm distribution policy.
|
||||
- [ADR-283](ADR-283-ruview-community-metaharness-flywheel.md) - shared brain and learning gates.
|
||||
- `harness/homecore/`
|
||||
- `v2/docs/homecore-capabilities.md`
|
||||
@@ -1,45 +0,0 @@
|
||||
# ADR-286: `wifi-densepose-sar-harness` — a MetaHarness minted via `vendor/metaharness`
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Accepted — implemented, **published** |
|
||||
| **Date** | 2026-07-30 |
|
||||
| **Parent** | ADR-287 (`wifi-densepose-sar`, the crate this harness assists development on) |
|
||||
| **Relates to** | ADR-182 (`harness/ruview/`, the first MetaHarness-minted harness in this repo), ADR-285 (`harness/homecore/`, the WASM-first pattern this harness's `@metaharness/kernel` dependency follows) |
|
||||
| **Published** | [`wifi-densepose-sar-harness` v0.1.0](https://www.npmjs.com/package/wifi-densepose-sar-harness) on npm (2026-07-31) |
|
||||
|
||||
## 0. PROOF discipline
|
||||
|
||||
Every claim below about what's "real" versus "illustrative"/"SYNTHETIC" is checked by a passing test in this harness's own suite (14 tests: 5 router + 5 flywheel + 4 install-smoke). Nothing here is asserted without a corresponding `__tests__/*.test.ts` file exercising it.
|
||||
|
||||
## 1. Context
|
||||
|
||||
`wifi-densepose-sar` (ADR-287) is a new, narrowly-scoped research crate. Rather than hand-roll a bespoke development-assistance setup for it, `vendor/metaharness` (the `ruvnet/metaharness` generator, vendored as a git submodule alongside this repo's other `vendor/*` submodules) was used to scaffold one directly: `npx metaharness analyze v2/crates/wifi-densepose-sar --scaffold wifi-densepose-sar-harness --host claude-code` recommended and generated `template: vertical:coding` with four agents (architect/implementer/reviewer/test-writer) and `doctor`/`review-diff` commands — the same generator that produced `harness/ruview/` (ADR-182) and `harness/homecore/` (ADR-285).
|
||||
|
||||
The user's ask that shaped this ADR's scope was specific: wire in **darwin, router, and flywheel** — three complementary `@metaharness/*` packages the base scaffold doesn't include by default (only Darwin Mode ships built-in).
|
||||
|
||||
## 2. Decision
|
||||
|
||||
Land the scaffold at `harness/wifi-densepose-sar/`, and add real wiring for the three requested pieces, each as an actual npm dependency (not a stub, not a `try/catch` optional import):
|
||||
|
||||
1. **`@metaharness/darwin`** (devDependency) — wired by the scaffold itself. `npm run evolve` (real sandbox) / `evolve:dry` (mock sandbox) mutates the harness's own operating config and keeps only measurably-improving changes.
|
||||
2. **`@metaharness/router`** — `src/router.ts` wires a real `Router` (k-NN over labelled examples, cost-optimal selection against a quality bar) with two example model tiers (`cheap-tier` $1/MTok, `frontier-tier` $15/MTok). Exposed as a CLI command (`route <e0> <e1> <e2> <e3>`) with a matching `.claude/commands/route.md` guidance file.
|
||||
3. **`@metaharness/flywheel`** — `src/flywheel.ts` wires the real `runFlywheelGenerations` promotion loop (propose → evaluate → gate → promote, Ed25519-signed, independently replayable via `verifyReplayBundle`) with a SYNTHETIC proposer/evaluator (`dataSource: 'SYNTHETIC'`, no live model call). Exposed as `flywheel [generations]` with a matching `.claude/commands/flywheel.md` guidance file.
|
||||
|
||||
Every new CLI subcommand gets a `.claude/commands/<name>.md` file, matching the pattern the base scaffold's `doctor`/`review-diff` already establish — the MCP tool listing (`mcp__wifi-densepose-sar-harness__*`) is derived from these, so a command without one isn't fully wired into the harness's own guidance surface even if the CLI itself works.
|
||||
|
||||
## 3. What this explicitly is NOT
|
||||
|
||||
- **Not evolving the crate.** Darwin/Flywheel mutate the harness's own operating policy (agent prompts, review checklist depth) — not `wifi-densepose-sar`'s Rust code or its runtime performance. Actually optimizing the crate (the incremental-phasor-rotation work, ADR-287 §7) was done directly, not through this harness's self-improvement loop.
|
||||
- **Not a live routing/promotion system.** The router's labelled examples are illustrative seed data, not measured eval-log observations. The flywheel's proposer/evaluator are deterministic stand-ins, not a real model call or a real coding-task benchmark suite. Both are honestly labeled as such in their own source files and in this harness's `CLAUDE.md`.
|
||||
- **Not manifest-verified.** `.harness/manifest.json`/`manifest.sha256` reflect the initial scaffold output and were not regenerated after adding `router.ts`/`flywheel.ts` — this scaffold has no `manifest:update` script (unlike `harness/homecore/`). Documented as a known gap in the harness's own README.
|
||||
|
||||
## 4. A real bug the flywheel wiring found
|
||||
|
||||
The first version of the SYNTHETIC evaluator returned a constant `noopRate`. `@metaharness/flywheel`'s default promotion gate requires `noopRate` to *strictly improve* generation over generation (one of its five conjunctive clauses) — a constant value, however good, fails that clause forever, so nothing could ever be promoted. Fixed by making `noopRate` actually respond to the (synthetic) policy content; every generation promotes now. Kept as a cautionary note in `src/flywheel.ts`'s comments: a flywheel evaluator with a frozen metric is silently broken, not silently fine.
|
||||
|
||||
## 5. Consequences
|
||||
|
||||
- 14 tests (5 router + 5 flywheel + 4 install-smoke), 0 failed; `npm run build` clean under strict TypeScript.
|
||||
- Published to npm as `wifi-densepose-sar-harness` v0.1.0 — `npx wifi-densepose-sar-harness init` works from a cold install.
|
||||
- No risk to any other harness or crate in this repo — this harness only reads/assists on `wifi-densepose-sar`, and its MCP server, memory namespace, and Claude Code plugin are scoped to its own name.
|
||||
@@ -1,67 +0,0 @@
|
||||
# ADR-287: `wifi-densepose-sar` — coherent wideband RF tomography research crate
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Accepted — implemented (P1), **published** |
|
||||
| **Date** | 2026-07-30 |
|
||||
| **Parent** | ADR-278 (radar inverse rendering research program), ADR-282 (mandatory L0–L5 evidence ladder) |
|
||||
| **Relates to** | ADR-273/274 (`ruview-unified`'s `FmcwRadarCube` adapter, the eventual integration point), ADR-275 (`GaussianMap`, ditto), ADR-286 (`wifi-densepose-sar-harness`, the MetaHarness minted for this crate) |
|
||||
| **Published** | [`wifi-densepose-sar` v0.3.1](https://crates.io/crates/wifi-densepose-sar) on crates.io (2026-07-31) |
|
||||
|
||||
## 0. PROOF discipline
|
||||
|
||||
Every accuracy number this crate produces is **SYNTHETIC / evidence level L0** (ADR-282): generated by the crate's own forward simulator (`measurement::simulate_measurement`), scored against its own known ground truth (`ScatteringTarget` positions). Nothing here has been validated against real wideband RF hardware, and the crate contains no such hardware integration.
|
||||
|
||||
## 1. Context
|
||||
|
||||
A YC-backed company, Applied Electrodynamics ("WaveSight"), publicly launched a handheld "camera that can see through walls" using undisclosed radio-imaging technology. Comparing it against this repo's capabilities surfaced a real gap: `wifi-densepose-signal::ruvsense::tomography` implements *radio tomographic imaging* (Wilson & Patwari 2010) — RSS-based shadowing attenuation on a fixed-link topology, no coherent phase, no multi-frequency stepping, no synthetic aperture. It is a different technique from what a SAR-style through-wall imager needs: coherent, wideband, multi-position backprojection.
|
||||
|
||||
`ruview-unified`'s `FmcwRadarCube` adapter (ADR-274) already normalizes wideband radar cubes into range profiles per position, and ADR-278 already names a radar-cube-output extension of the ADR-276 synthetic world generator as the intended sandbox for any future radar-inverse research. Neither, before this ADR, contained an actual backprojection reconstruction kernel — the primitive every candidate technique (matched-filter SAR, GPR imaging, RISE/DiffRadar-style inversion) is built on.
|
||||
|
||||
## 2. Decision
|
||||
|
||||
Ship `wifi-densepose-sar` as a standalone leaf crate (the `nvsim` pattern: pure Rust, deterministic ChaCha20 seeding, zero coupling to `wifi-densepose-hardware` or any real ingestion path) implementing:
|
||||
|
||||
1. **Forward measurement model** (`measurement.rs`): simulates the complex, stepped-frequency returns a monostatic synthetic-aperture radar would record from known point scatterers — `y_{m,k} = Σ_j σ_j/R_{m,j}² · exp(-i·4π·f_k·R_{m,j}/c) + noise`.
|
||||
2. **Backprojection reconstruction** (`reconstruct.rs`): the matched-filter inverse of (1) onto a 3D voxel grid, parallelized over voxels (rayon).
|
||||
3. **Point-cloud extraction** (`pointcloud.rs`): threshold + local-maximum extraction from the dense voxel image.
|
||||
4. **Closed-form resolution/coherence formulas** (`resolution.rs`): `ΔR = c/2B` (range resolution), `δ_CR ≈ λR/2L` (cross-range/synthetic-aperture resolution), `Δp ≤ λ/8` (antenna-pose coherence budget, derived from a quarter-wavelength round-trip-path tolerance) — checked against the reconstruction's actual behavior in `tests/physics_validation.rs`, not merely documented.
|
||||
|
||||
This is deliberately scoped **one level below** ADR-278's RISE/DiffRadar/GeRaF reproduction program: it is the bare measurement-model + backprojection primitive, not a reproduction of any specific published system, and not a claim about Applied Electrodynamics' undisclosed product (their waveform, antenna count, bandwidth, and algorithm are unknown; this crate applies the same well-established SAR/GPR physics — see Skolnik, *Radar Handbook* — to synthetic data).
|
||||
|
||||
## 3. What this explicitly is NOT
|
||||
|
||||
- Not a hardware driver. No VNA/SDR/wideband-RF-frontend code exists anywhere in this crate or was added to `wifi-densepose-hardware`.
|
||||
- Not wired into `ruview-unified`'s `FmcwRadarCube` adapter or `GaussianMap`. That integration is real future work (§5), deliberately deferred so the reconstruction physics validates in isolation first — the same staging ADR-278 §2.3 already prescribes ("sandbox-first... before hardware").
|
||||
- Not a reproduction of RISE, DiffRadar, or GeRaF. ADR-278's gates (G1–G4) are untouched by this ADR.
|
||||
- Not a real-world through-wall imaging performance claim. The forward model is free-space propagation only — no multipath, no per-material attenuation, no antenna gain pattern, no receiver noise figure. Real-world performance depends on all of these.
|
||||
|
||||
## 4. Simplifications (honesty boundary)
|
||||
|
||||
- **Monostatic, not MIMO.** A single antenna acts as both transmitter and receiver at each synthetic-aperture position (the standard stripmap-SAR simplification), not a multi-element MIMO array. Extending to bistatic/MIMO `(m, n)` transmitter/receiver pairs is straightforward given the existing measurement-model structure but not implemented.
|
||||
- **Isotropic antenna, no gain pattern.** Every antenna position radiates/receives equally in all directions.
|
||||
- **Free-space propagation only.** No multipath, no material transmission/reflection/attenuation (contrast `ruview-unified::synth::room`'s Fresnel material model, which is narrowband-CW and not yet extended to wideband — a natural follow-up, §5).
|
||||
- **`1/R²` two-way amplitude falloff, no calibration.** Real receivers have finite dynamic range, noise figures, and require calibration against a known reference target; none of that is modeled.
|
||||
|
||||
## 5. Follow-up (not in this ADR's scope)
|
||||
|
||||
1. Extend `ruview-unified::synth::room`'s image-method ray tracer to emit wideband stepped-frequency multi-position cubes (per ADR-278 §2.3), and wire `wifi-densepose-sar::reconstruct` against that richer (multipath-aware) synthetic generator instead of the free-space-only model here.
|
||||
2. A `ruview-unified` integration adapter converting `ReflectivityImage`/`PointCloudPoint` output into `RfGaussian`/`GaussianMap` primitives (ADR-278 §2.4's stated integration contract).
|
||||
3. Bistatic/MIMO measurement model.
|
||||
4. Any of ADR-278's actual gated reproductions (RISE first), if and when that program proceeds — this crate would be a component, not a substitute.
|
||||
|
||||
## 6. Consequences
|
||||
|
||||
- The workspace gains a real (if intentionally scoped-down) coherent-imaging primitive where before there was none — useful groundwork for ADR-278 if that research program proceeds, and a direct, honest answer to "could this repo build a WaveSight-like device" (no, not without the hardware program described in the motivating comparison; yes, this is the reconstruction-algorithm groundwork such a program would need).
|
||||
- Zero risk to the existing `wifi-densepose-signal::ruvsense::tomography` (RSS-based RTI) code path or any production pipeline — this crate is not referenced by any of them.
|
||||
- 25 tests (22 unit + 3 integration physics-validation), 0 failed, clippy-clean. Criterion bench: MEASURED 512/4096/32768-voxel backprojection reconstruction throughput (see crate README for the numbers as last recorded). The incremental-phasor-rotation optimization (§7) cut reconstruction time ~4.4-4.5x, proven equivalent to the direct per-frequency computation it replaced.
|
||||
|
||||
## 7. Follow-up optimization: incremental phasor rotation (2026-07-30, MEASURED)
|
||||
|
||||
`focus_at_point` originally called `Complex64::from_polar` (one `sin`/`cos` pair) per (pose, frequency) term. Since [`FrequencySweep::frequencies`](../../v2/crates/wifi-densepose-sar/src/measurement.rs) produces evenly-spaced frequencies by construction, the per-term phase is an arithmetic progression in the frequency index — so the phasor can be evaluated once per pose and advanced by a fixed complex-multiply step per frequency, replacing K trig evaluations with 2. `focus_at_point`'s signature changed from a raw `&[f64]` frequency slice to `&FrequencySweep`, making the evenly-spaced-frequencies precondition this optimization depends on a type-level invariant rather than a caller-observed one.
|
||||
|
||||
**MEASURED (criterion regression detection, p < 0.001): ~4.4-4.5x faster** across 512/4096/32768-voxel grids. **Proven equivalent**, not just faster: `reconstruct::tests::backprojection_incremental_rotation_matches_direct_per_frequency_computation` checks the optimized path against an independently reimplemented direct per-frequency reference, across four sweep sizes (including the `n_steps=1` degenerate case) and both on-target and off-target evaluation points, to <1e-9 relative error.
|
||||
|
||||
## 8. Published (2026-07-31)
|
||||
|
||||
`wifi-densepose-sar` v0.3.1 is live on [crates.io](https://crates.io/crates/wifi-densepose-sar) — `cargo add wifi-densepose-sar` resolves it from any Rust project. A MetaHarness minted for this crate (ADR-286, `wifi-densepose-sar-harness`) is published to npm alongside it. Publishing happened after this ADR's implementation and §7 optimization landed; no code changed as part of publishing itself.
|
||||
@@ -1,231 +0,0 @@
|
||||
# ADR-288: VEIL — a compliant-waveform privacy shield against unauthorized WiFi sensing
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Proposed — implemented (P1 reference model) |
|
||||
| **Date** | 2026-08-09 |
|
||||
| **Deciders** | ruv |
|
||||
| **Codename** | **VEIL** — Verifiable Emission-shaping for Identity-Leakage prevention |
|
||||
| **Codebase target** | new leaf crate `v2/crates/wifi-densepose-privshield` |
|
||||
| **Parent** | ADR-118 (BFLD — the detection layer VEIL is the countermeasure to), ADR-282 (mandatory L0–L5 evidence ladder) |
|
||||
| **Relates to** | ADR-120/121 (BFLD privacy class + identity-risk scoring — the trigger source), ADR-141 (privacy control plane / runtime attestation — the audit consumer), ADR-280 (active sensing / governed actuation — VEIL is a defensive sensing action), ADR-185 §13 (`wifi-densepose-aether` — the pure-compute leaf pattern this crate follows) |
|
||||
| **Research bundle** | [`docs/research/privacy-shield/`](../research/privacy-shield/) (9 files) |
|
||||
| **Tracking issue** | TBD |
|
||||
|
||||
## 0. PROOF discipline
|
||||
|
||||
Every defense number this crate produces is **SYNTHETIC / evidence level L0**
|
||||
(ADR-282): generated by the crate's own model (`identity::Channel`), attacked by
|
||||
the crate's own classifier (`attacker::NearestCentroidAttacker`), and scored
|
||||
against its own known labels. Nothing here has been validated against real WiFi
|
||||
silicon, and the crate contains no radio integration and cannot emit RF. External
|
||||
attack/defense results cited from the literature (BFId, LeakyBeam, DySPAN-2026,
|
||||
IRShield, FCC statutes) are **EXTERNAL** evidence and labelled MEASURED/CLAIMED in
|
||||
the research bundle. The single measured claim about *our own behavior* is the
|
||||
pinned deterministic witness in `proof.rs`.
|
||||
|
||||
## 1. Context
|
||||
|
||||
### 1.1 The gap
|
||||
|
||||
IEEE 802.11ac/ax beamforming feedback (BFI) — the compressed Givens-rotation
|
||||
angle matrices (φ/ψ) a client sends the AP — is transmitted **unencrypted on the
|
||||
management plane**. Any device in monitor mode can capture it for every station
|
||||
at once, no network access, and the target need carry no device. The literature
|
||||
establishes the severity: **BFId** (ACM CCS 2025) re-identifies individuals from
|
||||
BFI; **LeakyBeam** (NDSS 2025) detects occupancy through walls at 20 m from BFI;
|
||||
**BeamSense** recognizes activities at up to 99.28%. IEEE Std **802.11bf-2025**
|
||||
(published 26 Sep 2025) standardizes the sensing measurement/feedback surface
|
||||
these attacks abuse — and a 2023 proposal for a BFI secure-transmission mechanism
|
||||
(802.11-23/0782) was **withdrawn**, so the standard shipped with no privacy
|
||||
protections.
|
||||
|
||||
RuView already has a *detection* layer for this: **BFLD** (ADR-118/121) measures
|
||||
the identity-leakage of each frame and gates what leaves the node. But BFLD
|
||||
protects *RuView's own outputs*; it does nothing about a **third-party sniffer**
|
||||
capturing the room's plaintext BFI off the air. There is no RuView component, and
|
||||
per our market survey no shipping product anywhere, that prevents that.
|
||||
|
||||
### 1.2 Constraint: compliant waveform controls, never jamming
|
||||
|
||||
The defense must preserve normal communications and must not interfere with any
|
||||
other station. Jamming (47 U.S.C. §333/§302a) is defined by *adding energy to
|
||||
interfere with others' transmissions*. Any acceptable control must shape only the
|
||||
node's **own** standards-conformant emission.
|
||||
|
||||
### 1.3 The separability insight
|
||||
|
||||
Identity leaks through the *fine* cross-subcarrier phase structure of a
|
||||
beamforming report; data throughput rides the *dominant* beam direction. These
|
||||
are (mostly) separable subspaces — so a transform confined to the fine subspace
|
||||
can wreck re-identification while sparing the beam the link depends on. DySPAN-2026
|
||||
independently MEASURED that shaping fine-resolution feedback is near-free in
|
||||
throughput, corroborating the insight.
|
||||
|
||||
## 2. Decision
|
||||
|
||||
Ship **`wifi-densepose-privshield`** (VEIL) as a standalone pure-compute leaf
|
||||
crate (the `wifi-densepose-aether`/`nvsim` pattern: dependency-free, deterministic,
|
||||
WASM-ready, zero coupling to any radio or ingestion path), implementing:
|
||||
|
||||
1. **A SYNTHETIC two-subspace BFI model** (`identity.rs`): each identity owns a
|
||||
stable fine-block signature; sessions add environmental nuisance; the comm
|
||||
block is identity-free and carries throughput.
|
||||
2. **The protector** (`protector.rs`): compliant waveform controls, primarily a
|
||||
**per-session keyed orthogonal rotation of the fine subspace, composed from
|
||||
extra Givens rotations** — the report's native primitive. Plus feedback
|
||||
quantization/dither, sounding-cadence randomization, and a `SensingDetector`
|
||||
that engages the shield only when sensing activity is observed.
|
||||
3. **The adversary** (`attacker.rs`): a passive nearest-centroid re-identifier
|
||||
modeling the BFId threat, with selectable Euclidean/Cosine metrics.
|
||||
4. **A throughput model** (`throughput.rs`):
|
||||
`(1 − sounding − feedback_airtime) · C(SNR·(1−ρ))/C(SNR)`, where the residual
|
||||
`ρ` falls with feedback bits and the feedback airtime rises with them — giving
|
||||
a genuine interior throughput optimum in feedback resolution.
|
||||
5. **A compliance audit** (`compliance.rs`): the rotation is orthogonal ⇒
|
||||
energy-preserving ⇒ adds no interfering energy ⇒ **not jamming**, turned into a
|
||||
checked `ComplianceReport` (energy ratio ≈ 1.0).
|
||||
6. **The experiment** (`experiment.rs`): runs the attacker against unprotected and
|
||||
protected traffic and reports both accuracies vs. chance, plus throughput and
|
||||
compliance, with a single `passed()` verdict.
|
||||
7. **The hyper-optimizer** (`optimize.rs`): derives the shipped shield config
|
||||
rather than hand-picking it — the throughput-optimal feedback resolution and
|
||||
the minimum rotation-mixing budget that collapses re-ID robustly (across both
|
||||
attacker metrics and N∈{16,32}), plus a Pareto frontier.
|
||||
8. **A deterministic proof** (`proof.rs`): a pinned FNV-1a witness over the
|
||||
reference experiment (the `nvsim`/`verify.py` discipline).
|
||||
|
||||
### 2.1 Why the keyed Givens rotation
|
||||
|
||||
It is simultaneously **orthogonal** (energy-preserving ⇒ compliant),
|
||||
**key-reversible** (the associated AP shares the session key and recovers the true
|
||||
precoder ⇒ throughput preserved), and **fresh per session** (a sniffer sees a new
|
||||
random rotation of the signature each session and cannot average it back ⇒ the
|
||||
enrollment attack collapses; over unknown rotations the signature carries no
|
||||
stable discriminative information ⇒ re-ID → chance). It is the shared-secret
|
||||
precoding idea (cf. MIMOCrypt) specialized to the identity-bearing subspace.
|
||||
|
||||
### 2.2 Measured behavior (SYNTHETIC / L0)
|
||||
|
||||
Reference experiment at the hyper-optimized operating point (§opt), default
|
||||
scene, N=16 identities, `cargo test`:
|
||||
|
||||
| Metric | Shield off | Shield on |
|
||||
|---|---|---|
|
||||
| Passive re-ID accuracy | 100.0% | **4.7%** (chance 6.25%) |
|
||||
| Link throughput ratio | 100% | **97.6%** |
|
||||
| Emission energy ratio | — | **1.000000** (compliant) |
|
||||
|
||||
All 35 unit/proof tests + doctest pass; the crate builds for
|
||||
`wasm32-unknown-unknown` and is clippy-clean.
|
||||
|
||||
### opt. Hyper-optimization (`optimize.rs`)
|
||||
|
||||
The shipped shield config is the optimizer's output, not a guess, and
|
||||
`ShieldConfig::default()` is asserted equal to it:
|
||||
|
||||
- **Feedback resolution = 5 bits.** Throughput has an interior optimum in
|
||||
feedback bits (residual falls, feedback airtime rises); the unconstrained
|
||||
optimum is 3 bits (matching DySPAN-2026), and 5 is the throughput-best value in
|
||||
the spec-allowed 802.11 {5,7,9} set.
|
||||
- **Givens passes = 96.** The proven minimum for robust collapse — across both
|
||||
attacker metrics *and* N∈{16,32} — is **48**; the shipped 96 is a free 2×
|
||||
privacy margin, since the keyed rotation is derived from the shared secret and
|
||||
never signaled (extra passes cost compute, not airtime). The original
|
||||
hand-picked 112 was 2.3× over-provisioned.
|
||||
|
||||
Net vs. the original hand-picked (112 passes / 7 bits): the optimum is strictly
|
||||
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).
|
||||
|
||||
### sota. 2025–2026 evidence update (verified)
|
||||
|
||||
A cited, adversarially-verified SOTA sweep
|
||||
(`docs/research/privacy-shield/09-sota-update-2026.md`) refines the threat and
|
||||
positioning. Load-bearing points for this ADR:
|
||||
|
||||
- **Threat is broader and cheaper than §1.1 stated.** A passive, keyless,
|
||||
single-antenna sniffer at ~20 m and *through walls* can identify people
|
||||
(BFId, 99.5%/N=197, `MEASURED`), read **breathing** from stationary occupants
|
||||
and **keystrokes/PINs** (LeakyBeam / WiKI-Eve / SThief, `MEASURED`), and —
|
||||
decisively — **reconstruct full CSI from the sniffed BFI** (BFIAttack,
|
||||
≥93% single-antenna, `MEASURED`). VEIL's obfuscation must therefore degrade
|
||||
*reconstructed-CSI* utility, not merely raw-BFI feature noise; because VEIL's
|
||||
rotation is a **secret orthogonal** transform, the attacker has no key and no
|
||||
closed-form to invert — this is now a claim to **test**, not assume.
|
||||
- **VEIL's family is independently validated.** AP-side per-packet random
|
||||
unitary on the LTF (LeakyBeam defense, 89.7%→~51%, `MEASURED`) and RIS
|
||||
obfuscation (PrivISAC, 93%→~30%, robust to a retrained multi-location
|
||||
attacker, `MEASURED`) confirm standard-permitted beamforming-surface
|
||||
obfuscation works; DP-Givens quantization (`SYNTHETIC`) offers a formal ε knob.
|
||||
- **Compliance precedent.** BeamDancer (IEEE TWC 2024, `MEASURED`) argues
|
||||
native-beamforming obfuscation is 802.11-compliant while jamming/geofencing
|
||||
are not — cite it as precedent. (Its ">96% PDR" figure was **refuted** in
|
||||
verification; do not cite it.)
|
||||
- **Security honesty.** Obfuscation shields have published counter-attacks
|
||||
("Defeating CSI obfuscation", SnoopFi), so VEIL's own shield security is
|
||||
`CLAIMED`, not proven-secure, until it withstands learned de-obfuscation.
|
||||
- **Governance gap.** No claim on 802.11bf-2025 privacy provisions survived
|
||||
verification; that pillar remains an open question, not an asserted fact.
|
||||
|
||||
The derived, prioritized improvement backlog lives in the SOTA-update file (§4).
|
||||
|
||||
## 3. What this explicitly is NOT
|
||||
|
||||
- **Not a radio driver.** No RF frontend, no transmit path, no
|
||||
`wifi-densepose-hardware` coupling. VEIL cannot emit and cannot jam.
|
||||
- **Not a defense against the associated AP.** That party holds the session key by
|
||||
construction (threat class A3); protecting against a malicious AP is BFLD's
|
||||
detection/privacy-class problem (ADR-118/141), not this shield's.
|
||||
- **Not a full motion-obfuscation claim.** A fixed per-session rotation does not
|
||||
hide coarse within-session motion; identity *re-ID* is the guaranteed target,
|
||||
motion is partial/future work.
|
||||
- **Not a real-hardware performance claim.** All defense numbers are SYNTHETIC/L0
|
||||
until a two-node capture with a boot/runtime-log witness exists (CLAUDE.md
|
||||
hardware rule; roadmap P5).
|
||||
- **Not RF denial or camera-grade anything.**
|
||||
|
||||
## 4. Simplifications (honesty boundary)
|
||||
|
||||
- The two-subspace split is an abstraction; on real radios comm and identity
|
||||
information are only *approximately* separable, so the real throughput cost of
|
||||
fully hiding identity may exceed the model's ~2%. DySPAN-2026's MEASURED curve
|
||||
bounds it as *small* at fine resolution, not zero.
|
||||
- The attacker is nearest-centroid. The collapse argument is classifier-independent
|
||||
(it is about the marginalized signal), but P2/P5 must confirm a learned attacker
|
||||
also collapses.
|
||||
- The crate's PRNG is SplitMix64 — deterministic and WASM-safe but **not
|
||||
cryptographic**; a deployment derives the rotation key from the negotiated link
|
||||
secret, never from this PRNG.
|
||||
|
||||
## 5. Consequences
|
||||
|
||||
- RuView gains the *countermeasure* half of its RF-privacy story: BFLD detects
|
||||
leakage, VEIL acts on it — a defensible, standards-anchored, gap-filling
|
||||
position (see `docs/research/privacy-shield/06-market-and-buyers.md`).
|
||||
- The compliance audit gives regulators/auditors a machine-checkable "not jamming"
|
||||
artifact that composes with ADR-141 attestation.
|
||||
- Future integration (BFLD `identity_risk` → `SensingDetector`, ADR-280 governed
|
||||
actuation, firmware feedback shaping, two-node hardware measurement) is staged in
|
||||
the research bundle roadmap and deliberately deferred so the model validates in
|
||||
isolation first.
|
||||
|
||||
## 6. Validation
|
||||
|
||||
```bash
|
||||
cargo test -p wifi-densepose-privshield --no-default-features
|
||||
cargo build -p wifi-densepose-privshield --target wasm32-unknown-unknown
|
||||
cargo clippy -p wifi-densepose-privshield --all-targets
|
||||
```
|
||||
@@ -1,95 +0,0 @@
|
||||
# ADR-289: `wifi-densepose-privshield-harness` — a MetaHarness for the VEIL privacy shield
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Proposed — implemented (P1) |
|
||||
| **Date** | 2026-08-09 |
|
||||
| **Parent** | ADR-288 (`wifi-densepose-privshield` / VEIL, the crate this harness assists development on) |
|
||||
| **Relates to** | ADR-286 (`wifi-densepose-sar-harness`, the per-crate harness scaffold this one mirrors), ADR-285 (`harness/homecore/`, the WASM-first `@metaharness/kernel` pattern), ADR-182 (`harness/ruview/`, the first minted harness), ADR-282 (L0–L5 evidence ladder) |
|
||||
| **Location** | `harness/wifi-densepose-privshield/` |
|
||||
|
||||
## 0. PROOF discipline
|
||||
|
||||
Every claim below about what is "real" versus "illustrative"/"SYNTHETIC" is
|
||||
checked by a test in this harness's own suite (router + flywheel + install-smoke
|
||||
+ guidance). The dependency-free `guidance` surface is covered by
|
||||
`__tests__/guidance.test.ts`, which runs even before `npm install`. Nothing here
|
||||
asserts a MEASURED defense result — the harness surfaces the VEIL crate's
|
||||
SYNTHETIC/L0 numbers with that label intact.
|
||||
|
||||
## 1. Context
|
||||
|
||||
`wifi-densepose-privshield` (ADR-288) is the VEIL privacy shield — a new,
|
||||
narrowly-scoped crate. Following the pattern ADR-286 set for
|
||||
`wifi-densepose-sar`, it gets a dedicated per-crate MetaHarness rather than a
|
||||
bespoke setup: the `vertical:coding` scaffold (architect/implementer/reviewer/
|
||||
test-writer, `doctor`) with `@metaharness/router`, `@metaharness/flywheel`, and
|
||||
Darwin Mode wired in, plus a VEIL-specific, dependency-free `guidance` surface.
|
||||
|
||||
## 2. Decision
|
||||
|
||||
Land the harness at `harness/wifi-densepose-privshield/`, mirroring
|
||||
`wifi-densepose-sar-harness`, with two deliberate improvements:
|
||||
|
||||
1. **Dynamic dependency imports.** `bin/cli.js` imports the `@metaharness/*`
|
||||
packages *inside* the commands that need them, not at module top. So
|
||||
`guidance`, `--help`, and the guidance test run with **zero dependencies
|
||||
installed** — useful for offline/air-gapped review and for this repo's CI
|
||||
before `npm install`. Only `init`/`doctor`/`route`/`flywheel` touch the
|
||||
kernel/host/router/flywheel packages.
|
||||
2. **A VEIL `guidance` command.** A self-contained, source-cited, read-only
|
||||
capability map (topics: `overview`, `threat`, `countermeasure`,
|
||||
`compliance`, `optimization`, `experiment`), each entry carrying a summary,
|
||||
repo-relative source citations, focused validation commands, and explicit
|
||||
limitations — the `ruview_guidance` shape, specialized to VEIL. It labels all
|
||||
defense evidence `SYNTHETIC/L0` and states plainly that guidance is
|
||||
navigation, not authority.
|
||||
|
||||
The standard three self-improvement/cost pieces are wired as real npm
|
||||
dependencies (not stubs):
|
||||
|
||||
- **`@metaharness/darwin`** (devDependency) — `npm run evolve` / `evolve:dry`
|
||||
mutates the harness's own operating config, keeping only measurable gains.
|
||||
- **`@metaharness/router`** — `src/router.ts` wires a real cost-optimal `Router`
|
||||
(`qualityBar: 0.8`, k=1) over two model tiers, with four VEIL-shaped task axes
|
||||
(threatModeling / complianceReview / optimizerTuning / docWriting). Labelled
|
||||
examples are illustrative seed data (honesty note in-file).
|
||||
- **`@metaharness/flywheel`** — `src/flywheel.ts` wires the real
|
||||
`runFlywheelGenerations` promotion loop (propose → evaluate → gate → promote,
|
||||
Ed25519-signed, independently replayable) with a SYNTHETIC proposer/evaluator
|
||||
(`dataSource: 'SYNTHETIC'`, no model call), over VEIL policy levers
|
||||
(`complianceReview`, `threatTriage`).
|
||||
|
||||
## 3. What this explicitly is NOT
|
||||
|
||||
- **Not a VEIL runtime.** The harness does not run a radio, emit RF, or jam. It
|
||||
assists *development* on the crate; it cannot execute the shield on hardware.
|
||||
- **Not evolving the crate.** Darwin/Flywheel mutate the harness's own policy
|
||||
(agent prompts, review-checklist depth), not VEIL's Rust code. The crate's
|
||||
actual hyper-optimization (ADR-288 §opt) was done directly, in the crate.
|
||||
- **Not a live routing/promotion system.** The router's examples are seed data;
|
||||
the flywheel's proposer/evaluator are deterministic stand-ins — both honestly
|
||||
labelled in-source and in `CLAUDE.md`.
|
||||
- **Not a replacement for the crate's gates.** The authoritative check for a
|
||||
VEIL change remains `cargo test -p wifi-densepose-privshield`.
|
||||
- **Not a re-labeller.** The harness must never present VEIL's SYNTHETIC results
|
||||
as MEASURED, and never scaffold interference-based ("jamming") defenses — both
|
||||
are hard rules in the harness `CLAUDE.md`.
|
||||
|
||||
## 4. Consequences
|
||||
|
||||
- The harness ships `guidance`/`doctor`/`init`/`route`/`flywheel`; `guidance`
|
||||
and `--help` work offline (validated here via `node bin/cli.js`), the rest
|
||||
after `npm install` + `npm run build` (CI).
|
||||
- `.harness/manifest.json` + `manifest.sha256` are generated with real per-file
|
||||
hashes at creation (unlike ADR-286's scaffold, whose manifest was historical).
|
||||
- Scoped to its own name: its plugin, permissions, and (future) MCP surface only
|
||||
read/assist on `wifi-densepose-privshield`. No risk to other harnesses/crates.
|
||||
|
||||
## 5. Validation
|
||||
|
||||
```bash
|
||||
cd harness/wifi-densepose-privshield
|
||||
node bin/cli.js guidance --topic overview # dependency-free
|
||||
npm ci && npm run build && npm test # full suite (CI; needs registry access)
|
||||
```
|
||||
@@ -1,94 +0,0 @@
|
||||
# ADR-290: VEIL end-to-end hardware implementation program (multi-provider firmware)
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Proposed — P4 scaffolding (build-only); portable core validated on host |
|
||||
| **Date** | 2026-08-09 |
|
||||
| **Parent** | ADR-288 (VEIL shield), ADR-289 (harness), ADR-282 (L0–L5 evidence ladder) |
|
||||
| **Location** | `firmware/privshield/` |
|
||||
| **Relates to** | `firmware/esp32-csi-node/` (the CSI sensor/attacker node), ADR-280 (governed actuation), ADR-141 (attestation) |
|
||||
|
||||
## 0. PROOF discipline
|
||||
|
||||
The **only** artifact validated here is the portable C core
|
||||
(`firmware/privshield/core/`): a host test (`make test`) checks energy
|
||||
conservation, reversibility, wrong-key failure, and — pinned — that its
|
||||
SplitMix64 key schedule is **byte-identical to the Rust crate's** PRNG. That is
|
||||
`build`/host-level evidence, not silicon. Every per-provider adapter is a
|
||||
**build-only scaffold** with `TODO(hw)` markers: `SYNTHETIC / L0`, no captured
|
||||
log, no `MEASURED` claim. Nothing in this ADR asserts VEIL works on real
|
||||
hardware; it asserts a *plan and a shared core* to get there (P5).
|
||||
|
||||
## 1. Context
|
||||
|
||||
ADR-288 shipped VEIL as a deterministic, no-radio Rust model, and the 2025–2026
|
||||
SOTA sweep (ADR-288 §sota) confirmed the mechanism's family is real and
|
||||
standard-permitted. The open question left was **"does this run on real WiFi
|
||||
hardware, and on which?"** — including the user asks: *can OpenWRT / open WiFi
|
||||
software implement it, and can ESP32 help scramble signals?* Answering requires
|
||||
committing to the platform reality rather than assuming a uniform "firmware"
|
||||
target.
|
||||
|
||||
## 2. Decision
|
||||
|
||||
Stand up `firmware/privshield/` as a **multi-provider E2E program** around one
|
||||
shared, validated core:
|
||||
|
||||
1. **A portable C shield core** (`core/veil_shield.{h,c}`) — the keyed
|
||||
Givens-rotation obfuscation, `no_std`-friendly C99 (no malloc/libc I/O), with
|
||||
a SplitMix64 key schedule matching the Rust crate so on-air behavior is
|
||||
identical everywhere and every adapter links the *same* math. Host-tested.
|
||||
2. **Per-provider adapters**, each built and graded by a hardware research
|
||||
agent, honest about what its stack can actually touch:
|
||||
- **`openwifi/`** (open PHY/MAC on SDR/FPGA) — the highest-capability path and
|
||||
the one that can host the **keyed-reversible** design end-to-end
|
||||
(protector + AP-side compensation). Carries the **P5 measurement protocol**
|
||||
(`MEASUREMENT.md`) that yields the first `MEASURED` result with a witness.
|
||||
- **`openwrt/`** (Linux `mac80211`, mt76/ath9k…) — the commodity path.
|
||||
Sounding-cadence randomization, MU-group and stream-mapping control are
|
||||
feasible from the driver/hostapd; the per-packet unitary on the LTF spatial
|
||||
mapping is firmware-deep on most parts. Partial.
|
||||
- **`nexmon/`** (Broadcom/Cypress C firmware patches) — the commodity
|
||||
C-firmware route; the read path is proven (Wi-BFI/nexmon_csi), the transmit
|
||||
report-shaping path is research-grade/partial.
|
||||
- **`esp32/`** (ESP-IDF) — **not** a feedback protector (the beamforming path
|
||||
is a closed blob): ESP32 shapes CSI *read*, not transmitted feedback. Its
|
||||
legitimate roles are a **sensing detector** (trigger the AP-side shield) and
|
||||
an **RIS controller** (drive an external reconfigurable surface to scramble
|
||||
the sensing direction — the honest way ESP32 "helps scramble", via an
|
||||
external surface, not its own PHY).
|
||||
3. **Compliance stance carried into hardware:** every control shapes the node's
|
||||
own standards-conformant emission and preserves energy; the ESP32
|
||||
decoy/cover-traffic idea is documented as *legally sensitive / not
|
||||
recommended* precisely because it edges toward the interference line.
|
||||
|
||||
Per-provider feasibility grades live in each subdir README and the top-level
|
||||
feasibility matrix; they are the answer to the "which hardware" question.
|
||||
|
||||
## 3. What this explicitly is NOT
|
||||
|
||||
- **Not validated firmware.** No adapter has run on silicon; there is no witness.
|
||||
The scaffolds compile-*shaped*, not compile-*guaranteed* on their toolchains
|
||||
(which are absent in this environment).
|
||||
- **Not a claim that ESP32 can shield beamforming feedback** — it cannot; it is a
|
||||
detector/RIS-controller only.
|
||||
- **Not jamming, on any platform.** Compliant waveform shaping only.
|
||||
- **Not a MEASURED result.** That is P5, gated on a captured log.
|
||||
|
||||
## 4. Consequences
|
||||
|
||||
- One validated core, four honest provider scaffolds, and a concrete P5
|
||||
measurement plan — a real path from model to silicon, with the effort/blocker
|
||||
reality made explicit per platform.
|
||||
- The shared core keeps every future hardware result consistent with the crate
|
||||
and with each other.
|
||||
- Scope stays inside `firmware/privshield/`; no other crate/firmware is touched
|
||||
(the existing `esp32-csi-node` remains the sensor/attacker node).
|
||||
|
||||
## 5. Validation
|
||||
|
||||
```bash
|
||||
cd firmware/privshield/core && make test # host: energy/reversibility/PRNG parity
|
||||
# per-provider builds require their toolchains (ESP-IDF, OpenWRT SDK, Nexmon,
|
||||
# Vivado) and real hardware — see each subdir's BUILD/INTEGRATION notes.
|
||||
```
|
||||
@@ -1,106 +0,0 @@
|
||||
# ADR-291: Public-benchmark evaluation harness — Widar3.0 ingest, standard split protocols, leakage guards
|
||||
|
||||
- **Status**: Accepted — initial implementation (this PR)
|
||||
- **Date**: 2026-08-10
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: training, evaluation, benchmarks, widar, mm-fi, leakage, honesty
|
||||
|
||||
## Context
|
||||
|
||||
RuView implements the field's key techniques (CSI ratio, BVP features, MAE
|
||||
pretraining, rapid adaptation) but reports results only on self-collected data
|
||||
with self-defined metrics (e.g. the README's held-out temporal-triplet
|
||||
accuracy). A 2026 deep-research sweep of the WiFi-sensing literature found:
|
||||
|
||||
1. Cross-domain generalization is the field's central unsolved problem; the
|
||||
only widely reproduced cross-domain result is Widar3.0's BVP benchmark.
|
||||
2. MM-Fi (NeurIPS 2023) is the standard WiFi-pose benchmark, with defined
|
||||
cross-subject and cross-environment protocols.
|
||||
3. The field had a documented leakage reckoning in 2024–2025: window-level
|
||||
random splits on continuous recordings inflate accuracy (one dataset's F1
|
||||
collapsed from ~90% to ~22% under subject-disjoint splits — Sensors
|
||||
24(10):3159; Signals 6(4):59).
|
||||
|
||||
`wifi-densepose-train` already has an `MmFiDataset` NPY loader and a
|
||||
deterministic `SyntheticCsiDataset`, but no Widar3.0 ingest, no standard split
|
||||
protocols, and no structural leakage guard. CLAUDE.md already requires
|
||||
mean-pose baselines and leakage-free held-out splits for pose PCK; nothing in
|
||||
the code enforces this.
|
||||
|
||||
Without leaderboard-comparable numbers, RuView's claims cannot be ranked
|
||||
against published systems, which blocks both scientific credibility and
|
||||
commercial (OEM licensing) conversations.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Do nothing; keep self-collected metrics.** Rejected: perpetuates the
|
||||
comparability gap.
|
||||
2. **Port a Python eval stack (SenseFi) alongside the Rust pipeline.**
|
||||
Rejected: violates the v2 Rust-workspace direction and adds an unreviewed
|
||||
dependency surface.
|
||||
3. **Extend `wifi-densepose-train` with native loaders + protocol machinery.**
|
||||
Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Extend `v2/crates/wifi-densepose-train` with three additions:
|
||||
|
||||
### 1. Widar3.0 ingest (`dataset::widar`)
|
||||
|
||||
- A parser for the Intel 5300 `.dat` CSI log format ("bfee" records) used by
|
||||
the Widar3.0 raw distribution: framed records with a 3-byte header
|
||||
(2-byte little-endian length + 1-byte code 0xBB), a 20-byte bfee header
|
||||
(timestamp_low, bfee_count, Nrx, Ntx, RSSI a/b/c, noise, agc, antenna_sel,
|
||||
len, rate), and a packed 10-bit-per-component complex CSI payload of
|
||||
30 subcarrier groups. Invalid records are skipped with a warning, not a
|
||||
panic — untrusted file input is validated at the boundary per CLAUDE.md.
|
||||
- A `WidarDataset` implementing the existing `CsiDataset` trait, mapping
|
||||
Widar's `Nrx × Ntx × 30` CSI into windowed `CsiSample`s via the existing
|
||||
subcarrier interpolation, with domain metadata (user, room, orientation,
|
||||
gesture) parsed from Widar's documented directory/file naming convention.
|
||||
- No network access: the loader reads a local dataset root. Dataset download
|
||||
remains a documented manual step.
|
||||
|
||||
### 2. Split protocols (`protocols`)
|
||||
|
||||
- A `SplitProtocol` type expressing the standard evaluations: cross-subject
|
||||
(MM-Fi style), cross-environment/room, cross-orientation (Widar style), and
|
||||
random-baseline (explicitly labelled as leakage-prone, for comparison only).
|
||||
- Split assignment is a pure function of sample metadata + a seed — fully
|
||||
deterministic, no RNG state.
|
||||
|
||||
### 3. Leakage guards (`protocols::leakage`)
|
||||
|
||||
- A structural `LeakageAudit` that, given a proposed train/test split,
|
||||
verifies: (a) subject-disjointness, (b) environment-disjointness where the
|
||||
protocol claims it, (c) no two windows from the same continuous recording
|
||||
span both sides of the split. A failed audit is an `Err`, not a warning.
|
||||
- PCK/accuracy reporting requires a `MeanPoseBaseline` computed from the
|
||||
training split only, and reports model-vs-baseline together, enforcing the
|
||||
CLAUDE.md rule in the type system rather than by convention.
|
||||
- Evaluation output is an evidence-tagged report (`MEASURED` requires a
|
||||
reproducer command line embedded in the report; anything else is emitted as
|
||||
`SYNTHETIC` or `CLAIMED`).
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView results become comparable to published numbers (Widar3.0 cross-domain
|
||||
gesture; MM-Fi cross-subject pose) for the first time.
|
||||
- The leakage audit will make some existing internal numbers look worse. That
|
||||
is the point.
|
||||
- Parsing a legacy binary format adds maintenance surface; mitigated by
|
||||
fixture-based tests with synthetic, deterministically generated `.dat`
|
||||
bytes (no dataset redistribution).
|
||||
- Widar's raw distribution is Intel 5300-specific; ESP32-captured data
|
||||
continues through existing loaders. The protocols/leakage machinery is
|
||||
loader-agnostic.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test -p wifi-densepose-train` — unit tests for the bfee parser
|
||||
(truncated, corrupt, and valid synthetic fixtures), split determinism,
|
||||
leakage-audit rejection cases, and mean-pose baseline math.
|
||||
- `cargo bench -p wifi-densepose-train` — criterion benchmark for parser
|
||||
throughput and split assignment on synthetic corpora.
|
||||
- No accuracy numbers are claimed by this ADR; it delivers the machinery to
|
||||
produce MEASURED ones.
|
||||
@@ -1,91 +0,0 @@
|
||||
# ADR-292: Wideband 802.11ax CSI ingest — FeitCSI/AX210 adapter and subcarrier-agnostic plumbing
|
||||
|
||||
- **Status**: Accepted — initial implementation (this PR)
|
||||
- **Date**: 2026-08-10
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: hardware, csi, 80211ax, ax210, feitcsi, ingest, mat
|
||||
|
||||
## Context
|
||||
|
||||
RuView's CSI ingest (`wifi-densepose-mat/src/integration/hardware_adapter.rs`)
|
||||
supports ESP32 serial streams, the legacy Intel 5300 tool, and Atheros/Nexmon
|
||||
paths. All of these are 802.11n-class: ≤40 MHz bandwidth, ≤114 subcarriers,
|
||||
2.4/5 GHz.
|
||||
|
||||
The 2026 research sweep found the field's center of gravity has moved to
|
||||
Intel AX200/AX210 NICs via PicoScenes (closed-source core) and FeitCSI
|
||||
(open-source, GPL): 802.11ax CSI at up to 160 MHz / 1992 subcarriers,
|
||||
including the 6 GHz band. This is both the research-grade tier today and the
|
||||
shape of the data 802.11bf silicon will deliver from ~2026 onward. RuView's
|
||||
`wifi-densepose-hardware` crate already models 802.11bf session types, but no
|
||||
ingest path can carry wideband CSI into the pipeline.
|
||||
|
||||
Without a wideband path, RuView cannot develop against the best available
|
||||
signal, cannot compare ESP32-grade results to wideband upper bounds, and will
|
||||
meet 802.11bf silicon with no tested plumbing for >114-subcarrier frames.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **PicoScenes `.csi` ingest.** Rejected for now: the format is produced by a
|
||||
closed-source core and is versioned/complex; parsing it without a
|
||||
maintained spec invites silent corruption.
|
||||
2. **Raw pcap + radiotap parsing.** Rejected: duplicates what FeitCSI already
|
||||
does on-device, and pulls a packet-capture dependency into the pipeline.
|
||||
3. **FeitCSI file/stream ingest.** Chosen: FeitCSI is open-source (its header
|
||||
layout is auditable against the source), targets AX200/AX210, covers
|
||||
20–160 MHz including 6 GHz, and emits a compact binary record per frame.
|
||||
|
||||
## Decision
|
||||
|
||||
Extend `v2/crates/wifi-densepose-mat/src/integration` with:
|
||||
|
||||
### 1. `feitcsi` record parser
|
||||
|
||||
- A validated parser for FeitCSI's binary CSI record layout (header with
|
||||
CSI buffer length, rate/bandwidth/channel metadata, antenna counts, RSSI,
|
||||
timestamp, followed by interleaved complex CSI). The parser is written
|
||||
against the documented layout, is version-checked, and rejects
|
||||
records whose declared dimensions disagree with the buffer length —
|
||||
untrusted file/stream input is validated at the boundary.
|
||||
- Bounded allocation: a hard cap on subcarrier count (4096) and antenna
|
||||
count (8) so a corrupt length field cannot cause unbounded allocation.
|
||||
|
||||
### 2. `DeviceType::FeitCsi` in the hardware adapter
|
||||
|
||||
- File-replay mode (read a recorded FeitCSI capture deterministically) and a
|
||||
streaming mode fed by an external process writing to a path/pipe. No
|
||||
privileged operations inside the crate: RuView does not configure the NIC;
|
||||
FeitCSI's own tooling owns that, per least-authority.
|
||||
|
||||
### 3. Subcarrier-agnostic plumbing
|
||||
|
||||
- Ingest carries native subcarrier dimensionality end-to-end and converts to
|
||||
pipeline width explicitly via the existing interpolation/decimation stage,
|
||||
recording the native → pipeline mapping in frame metadata so downstream
|
||||
consumers know the true spectral resolution. Bandwidth (20–160 MHz) and
|
||||
band (2.4/5/6 GHz) become first-class frame metadata.
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView gains a research-grade wideband development path and a tested
|
||||
ingest shape for future 802.11bf reporting (truncated CIR is a natural
|
||||
extension of the same plumbing).
|
||||
- GPL FeitCSI is used as an external tool, never linked: only its output
|
||||
format is parsed. No licensing contamination of the MIT workspace.
|
||||
- The parser tracks an external project's format; version checks fail loudly
|
||||
on mismatch rather than misparse.
|
||||
- ESP32 remains the deployed sensor tier; wideband is a development/
|
||||
validation tier. Accuracy claims from wideband captures must be tagged with
|
||||
the capture hardware.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test -p wifi-densepose-mat` — parser tests over synthetic fixtures:
|
||||
valid records at 20/80/160 MHz shapes, truncated buffer, dimension
|
||||
mismatch, version mismatch, allocation-cap enforcement; adapter replay
|
||||
determinism.
|
||||
- `cargo bench -p wifi-densepose-mat` — criterion benchmark for record parse
|
||||
throughput at 1992-subcarrier frames.
|
||||
- Hardware validation on real AX210 silicon is explicitly out of scope for
|
||||
this PR and remains required (per CLAUDE.md) before any capture-path
|
||||
hardware claim; the file-replay path is testable without silicon.
|
||||
@@ -1,93 +0,0 @@
|
||||
# ADR-293: Vitals ground-truth rig — reference ingest, time alignment, and agreement metrics
|
||||
|
||||
- **Status**: Accepted — initial implementation (this PR)
|
||||
- **Date**: 2026-08-10
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: vitals, validation, ground-truth, bland-altman, evidence, honesty
|
||||
|
||||
## Context
|
||||
|
||||
`wifi-densepose-vitals` (ADR-021) extracts breathing (0.1–0.5 Hz) and heart
|
||||
rate (0.8–2.0 Hz) from CSI. The 2026 research sweep found that every credible
|
||||
vitals result in the literature ships with reference-sensor ground truth
|
||||
(chest strap, pulse oximeter, ECG, or PSG), and that WiFi heart-rate numbers
|
||||
without stated scope (single person, static, line-of-sight, short range) are
|
||||
systematically misleading. RuView currently has no way to produce a MEASURED
|
||||
vitals number: there is no reference-signal ingest, no time alignment between
|
||||
CSI-derived estimates and a reference device, and no agreement statistics.
|
||||
|
||||
CLAUDE.md requires accuracy statements to be tagged MEASURED (with a
|
||||
reproducer), CLAIMED, or SYNTHETIC. For vitals, MEASURED is currently
|
||||
unreachable.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Live BLE/ANT+ integration with reference devices.** Rejected for now:
|
||||
drivers and pairing are a hardware/product concern; the blocking gap is
|
||||
the evaluation math, not the radio link.
|
||||
2. **File-based reference ingest + offline agreement analysis.** Chosen:
|
||||
every consumer reference device (Polar, Garmin, oximeters) exports
|
||||
timestamped series; a file boundary keeps the crate dependency-free and
|
||||
the pipeline deterministic.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a `groundtruth` module to `v2/crates/wifi-densepose-vitals`:
|
||||
|
||||
### 1. Reference series ingest
|
||||
|
||||
- `ReferenceSeries`: timestamped samples (unix millis + value) for one
|
||||
measurand (`HeartRateBpm` or `BreathingRateBrpm`), with device metadata
|
||||
(make/model, measurement principle). Parsed from CSV (`timestamp_ms,value`
|
||||
with a header line); malformed rows are rejected with row-numbered errors —
|
||||
untrusted file input validated at the boundary. Non-monotonic timestamps
|
||||
are an error, not silently sorted.
|
||||
|
||||
### 2. Time alignment
|
||||
|
||||
- Constant-offset estimation by maximizing normalized cross-correlation of
|
||||
the estimate series against the reference over a bounded lag window
|
||||
(default ±30 s), on a common resampled grid (nearest-sample, no
|
||||
interpolation of physiological values across gaps larger than a
|
||||
configurable limit).
|
||||
- Optional linear clock-drift fit (offset + rate) for long sessions.
|
||||
Alignment parameters are reported, never silently applied.
|
||||
|
||||
### 3. Agreement metrics
|
||||
|
||||
- `AgreementReport`: n paired samples, coverage fraction (time where both
|
||||
series had valid samples), MAE, RMSE, mean error (bias), Bland–Altman
|
||||
95% limits of agreement, and percentage-within-tolerance (configurable,
|
||||
default ±2 bpm HR / ±1 brpm breathing).
|
||||
- Session scope is mandatory metadata: subject count, motion state
|
||||
(static/moving), line-of-sight (LOS/NLOS/through-wall), distance band.
|
||||
A report without scope cannot be constructed.
|
||||
|
||||
### 4. Evidence tagging
|
||||
|
||||
- `EvidenceGrade::Measured` is only constructible when the report carries a
|
||||
reference device, non-zero paired samples, minimum coverage, and a
|
||||
reproducer command string; otherwise the report grades as `Claimed` (real
|
||||
data, no reference) or `Synthetic` (generated input). This mirrors
|
||||
ADR-291's enforcement-in-types approach and the CLAUDE.md tagging rule.
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView can convert vitals claims from CLAIMED to MEASURED with a
|
||||
reproducible offline analysis, session by session, scope by scope.
|
||||
- Honest reporting will likely show heart-rate performance below marketing
|
||||
intuition, especially NLOS/moving — that is the purpose.
|
||||
- CSV ingest means a manual export step per session; acceptable at current
|
||||
scale, and the format is the de-facto export of consumer reference gear.
|
||||
- No clinical claim is implied: agreement statistics against consumer
|
||||
reference devices are engineering evidence, not medical validation.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test -p wifi-densepose-vitals` — CSV rejection cases, alignment
|
||||
recovery of known synthetic offsets/drifts, agreement metrics against
|
||||
hand-computed fixtures, evidence-grade constructibility rules.
|
||||
- `cargo bench -p wifi-densepose-vitals` — criterion benchmark for alignment
|
||||
over hour-scale synthetic sessions.
|
||||
- Real-session validation (ESP32 capture + chest strap) remains a follow-up
|
||||
requiring hardware evidence per CLAUDE.md.
|
||||
@@ -1,83 +0,0 @@
|
||||
# ADR-294: WiFi Veil integration — emission-shaping countermeasure as an advisory BFLD dependency
|
||||
|
||||
- **Status**: Accepted — initial implementation (this PR)
|
||||
- **Date**: 2026-08-10
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: privacy, bfld, bfi, wifi-veil, countermeasure, dependency
|
||||
|
||||
## Context
|
||||
|
||||
RuView's BFLD layer (ADR-118, ADR-141) senses via beamforming feedback while
|
||||
enforcing structural privacy invariants on data entering the node. The 2026
|
||||
research sweep identified the complementary, unaddressed surface: a node's own
|
||||
*outgoing* BFI is unencrypted and enables passive third-party
|
||||
re-identification (BFId, ACM CCS 2025); IEEE 802.11bf-2025 shipped with no
|
||||
privacy mechanism; and no commercial product occupies the countermeasure
|
||||
category.
|
||||
|
||||
[`wifi-veil`](https://github.com/ruvnet/wifi-veil) (codename VEIL, extracted
|
||||
from this monorepo as a standalone crate) models a compliant emission-shaping
|
||||
defense: keyed Givens rotations over the fine subspace of compressed
|
||||
beamforming reports, energy-preserving (never jamming), reversible by a
|
||||
keyed legitimate receiver. The crate is dependency-free, deterministic,
|
||||
std-only, WASM-ready, dual MIT/Apache-2.0, and explicitly SYNTHETIC/L0: it
|
||||
models waveform controls and never drives a radio.
|
||||
|
||||
RuView should consume this capability rather than re-implement it, giving the
|
||||
sensing stack a defensive counterpart under one evidence regime.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Vendor the veil sources into a RuView crate.** Rejected: forks the
|
||||
witness-pinned upstream and duplicates maintenance.
|
||||
2. **crates.io dependency.** Not yet available (v0.1.0 unpublished at
|
||||
decision time); revisit when released.
|
||||
3. **Git dependency pinned to an exact rev, feature-gated in
|
||||
`wifi-densepose-bfld`.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
- Add `wifi-veil` to `v2/Cargo.toml` `[workspace.dependencies]` as a git
|
||||
dependency pinned to rev `018468b5d2bf41f35c552910f35659830af0eb91`
|
||||
(v0.1.0). Exact-rev pinning preserves provenance and reproducibility for a
|
||||
pre-release upstream; bumping the rev is an explicit, reviewable change.
|
||||
- Gate it in `wifi-densepose-bfld` behind a new `veil` feature
|
||||
(`veil = ["std", "dep:wifi-veil"]`), off by default — the default build
|
||||
remains dependency-light and unchanged.
|
||||
- New `bfld::veil` module (advisory-only):
|
||||
- `ShieldAssessment`: stable projection of wifi-veil's deterministic
|
||||
attacker-vs-protector `ExperimentReport` (re-ID accuracy shield-off/on,
|
||||
chance level, throughput ratio, energy-conservation audit), always
|
||||
carrying the `SYNTHETIC/L0` evidence label.
|
||||
- `assess` / `assess_default`: run the deterministic experiment.
|
||||
- `optimized_shield`: wrap `hyper_optimize` to derive the
|
||||
optimizer-shipped shield config plus its verifying assessment.
|
||||
- Boundaries, stated structurally and in docs:
|
||||
- **Advisory only.** Nothing in the integration emits RF, alters frames,
|
||||
or relaxes any BFLD gate/invariant (I1–I3 untouched).
|
||||
- **Evidence honesty.** Every veil-derived figure is labeled
|
||||
`SYNTHETIC/L0`; no MEASURED claim is possible from this path (hardware
|
||||
validation lives in wifi-veil's own P5 roadmap).
|
||||
- ESP32 nodes cannot shield their own feedback (per wifi-veil's platform
|
||||
matrix); the integration therefore informs posture and reporting, not
|
||||
on-node emission control.
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView gains a sense-and-defend posture no commercial offering has, under
|
||||
a single claim taxonomy.
|
||||
- First git dependency in the workspace: builds now fetch one pinned
|
||||
external rev. Acceptable: the crate is dependency-free, small, witness-
|
||||
pinned upstream, and license-compatible (MIT OR Apache-2.0 into MIT).
|
||||
- Feature-gated consumers (e.g. sensing-server privacy reporting, the
|
||||
desktop UI) can surface shield assessments later without new deps.
|
||||
- When wifi-veil publishes to crates.io, switch the workspace entry to a
|
||||
version requirement in a follow-up ADR amendment.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test -p wifi-densepose-bfld --features veil` — determinism,
|
||||
shield-reduces-re-ID, compliance (energy conservation), chance-band
|
||||
attainment, evidence labeling, optimizer wrapper.
|
||||
- `cargo test -p wifi-densepose-bfld` (default features) — unchanged
|
||||
behavior with the feature off.
|
||||
@@ -1,64 +0,0 @@
|
||||
# ADR-295: Source provenance state machine — synthetic can never present as live
|
||||
|
||||
- **Status**: Accepted — initial implementation (this PR)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: provenance, honesty, ui, sensing-server, security
|
||||
|
||||
## Context
|
||||
|
||||
An August 2026 external review found two provenance defects on the release
|
||||
path:
|
||||
|
||||
1. The pose-fusion simulator starts in demo mode; on any page port other than
|
||||
3000 the WebSocket target falls back to `localhost:8765`, and if the
|
||||
connection fails the simulator keeps running while the status still reads
|
||||
"ready" — producing a convincing moving visualization with no live CSI
|
||||
(issue 1557).
|
||||
2. The main sensing client labels the source **live** when the authenticated
|
||||
status endpoint returns an error for lack of authorization, until a real
|
||||
frame happens to correct it (issue 1526).
|
||||
|
||||
The common root cause: source state is a boolean (live vs not), so "unknown"
|
||||
collapses to "live". CLAUDE.md requires MEASURED/CLAIMED/SYNTHETIC labeling
|
||||
and forbids presenting synthetic output as real.
|
||||
|
||||
## Decision
|
||||
|
||||
Define one canonical, mutually exclusive `SourceState` enum shared by the
|
||||
sensing server and every UI/client that renders a source:
|
||||
|
||||
- `Synthetic` — generated data (simulator/replay of synthetic fixtures).
|
||||
- `LiveVerified` — frames from an authenticated, attested source.
|
||||
- `LiveUnverified` — frames arriving but provenance not yet confirmed.
|
||||
- `Stale` — last frame older than a configured freshness window.
|
||||
- `Disconnected` — no source.
|
||||
|
||||
Rules enforced structurally:
|
||||
|
||||
- **`Unknown` is not a state.** Any ambiguous condition resolves to
|
||||
`LiveUnverified`, `Stale`, or `Disconnected` — never `LiveVerified`.
|
||||
- A status-endpoint error resolves to `Disconnected`/`LiveUnverified`, never
|
||||
live-verified.
|
||||
- The simulator constructs `Synthetic` and cannot transition to any `Live*`
|
||||
state without a verified frame.
|
||||
- `Synthetic` is watermarked in every view and every export.
|
||||
- Transitions are a pure function of (last-frame-age, auth-status,
|
||||
source-kind) so they are unit-testable without a clock or a socket.
|
||||
|
||||
Scope of this PR: the shared `SourceState` type + transition function + tests
|
||||
in the sensing server, and wiring of the two identified surfaces (pose-fusion
|
||||
simulator status, sensing client source label). Broader UI adoption follows.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Closes the "synthetic shown as live" and "unknown shown as live" classes.
|
||||
- A small breaking change to any consumer currently reading a boolean source
|
||||
flag; mitigated by exposing a compatibility accessor during migration.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests for every transition, especially: auth-error → not-live;
|
||||
simulator → never live without a verified frame; freshness expiry → `Stale`;
|
||||
watermark present on synthetic export.
|
||||
- `cargo test -p wifi-densepose-sensing-server`.
|
||||
@@ -1,59 +0,0 @@
|
||||
# ADR-296: Sensor data-plane hardening — UDP bind control and source allowlist (step one)
|
||||
|
||||
- **Status**: Accepted — initial implementation (this PR)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: security, udp, sensor-ingest, sensing-server
|
||||
|
||||
## Context
|
||||
|
||||
The CSI UDP receiver binds `0.0.0.0:{udp_port}` unconditionally
|
||||
(`main.rs:5706`), with no equivalent of the HTTP `--bind-addr` flag (which
|
||||
correctly defaults to `127.0.0.1`), no source allowlist, no message
|
||||
authentication, no device identity, and no replay defense. Any host that can
|
||||
reach the UDP port can inject a valid-shaped frame, flip an auto-detecting
|
||||
server into a live source state, and influence presence/vital/automation
|
||||
outputs (issue 1394).
|
||||
|
||||
An IP allowlist does not stop LAN spoofing, but bind control plus an allowlist
|
||||
is the correct, shippable first step; per-device keys + authenticated
|
||||
encryption + monotonic sequence + freshness window + replay rejection is the
|
||||
full fix and is larger.
|
||||
|
||||
## Decision
|
||||
|
||||
**This PR (step one):**
|
||||
|
||||
- Add `--udp-bind` (env `RUVIEW_UDP_BIND`), **defaulting to `127.0.0.1`**.
|
||||
Binding to a routable address is now an explicit operator choice, mirroring
|
||||
the HTTP path. Desktop/appliance defaults stay loopback.
|
||||
- Add an optional source IP/CIDR allowlist (`--udp-allow`); when set, frames
|
||||
from other sources are dropped and counted. Loopback is always allowed.
|
||||
- Emit a startup security log line stating the bind scope and whether an
|
||||
allowlist is active; refuse a routable bind without an allowlist unless an
|
||||
explicit `--udp-insecure-lan` override is passed (parallel to the existing
|
||||
Docker HTTP refusal).
|
||||
- Publish a `SECURITY.md`/advisory note describing the threat model and safe
|
||||
deployment.
|
||||
|
||||
**Explicitly deferred to a follow-up ADR (step two):** per-device provisioned
|
||||
keys, MAC/AEAD, device identifiers, monotonic sequence numbers, freshness
|
||||
window, and replay rejection. This ADR documents that gap rather than
|
||||
implying the data plane is authenticated.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Removes the default open-to-LAN exposure with a one-line-safe default.
|
||||
- Not spoof-proof on a trusted LAN — the advisory says so plainly, and the
|
||||
override name (`--udp-insecure-lan`) makes the residual risk legible.
|
||||
- A behavior change for anyone relying on the old implicit `0.0.0.0` default;
|
||||
called out in the changelog and the startup log.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests: default bind is loopback; routable bind without allowlist is
|
||||
refused unless overridden; allowlist accept/drop with counting; loopback
|
||||
always allowed.
|
||||
- `cargo test -p wifi-densepose-sensing-server`.
|
||||
- Real-silicon validation of the LAN path remains required before any
|
||||
deployment claim.
|
||||
@@ -1,58 +0,0 @@
|
||||
# ADR-297: Multi-node semantic correctness — per-node inference, node-keyed rate limiting, stale state
|
||||
|
||||
- **Status**: Accepted — initial implementation (this PR)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: multi-node, mqtt, home-assistant, correctness, sensing-server
|
||||
|
||||
## Context
|
||||
|
||||
The external review confirmed three defects on the multi-node path — the core
|
||||
mechanism RuView uses to reduce blind spots and room dependence:
|
||||
|
||||
1. The active `NodeInfo` payload carries RSSI/position/subcarrier/sync but **no
|
||||
per-node classification**; the MQTT mapper reads `node.classification` and
|
||||
falls back to the room aggregate when absent, so every node can publish the
|
||||
same aggregate presence value (issues 1540, 1554).
|
||||
2. The MQTT `RateLimiter` is keyed by `EntityKind` only
|
||||
(`mqtt/state.rs:65`), so one node consumes the numeric publish slot and the
|
||||
others are suppressed until the interval expires, while availability still
|
||||
says online (issue 1541).
|
||||
3. In the UDP vital path, top-level classification is taken from the
|
||||
latest-arriving node while other features are fused, so with disagreeing
|
||||
nodes room presence can flip at packet frequency (issue 1555).
|
||||
|
||||
## Decision
|
||||
|
||||
- **Separate the types.** Introduce `NodeInference` (per-node classification +
|
||||
confidence + freshness) distinct from `RoomInference` (the fused room
|
||||
aggregate). `NodeInfo` carries a `NodeInference`; the room aggregate is
|
||||
computed explicitly and never overwrites node state. No silent fallback from
|
||||
node to room.
|
||||
- **Key the rate limiter by (node, entity).** `RateLimiter` becomes keyed on
|
||||
`(NodeId, EntityKind)` so nodes no longer starve each other; per-entity
|
||||
behavior per node is preserved.
|
||||
- **Deterministic fusion.** Room classification is a pure function of the set
|
||||
of current per-node inferences (e.g. freshness-weighted vote), not
|
||||
last-writer-wins; identical inputs yield identical room state.
|
||||
- **Stale entities cannot stay online.** An entity whose backing node has not
|
||||
reported within N expected publish intervals transitions to unavailable/
|
||||
stale rather than holding a frozen value while availability says online.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Multi-node HA/MQTT output becomes semantically correct; distinct nodes
|
||||
report distinct state and no longer suppress one another.
|
||||
- Schema change to `NodeInfo`/the MQTT contract; existing single-node
|
||||
deployments keep working (one node = one inference). Consumers reading the
|
||||
old aggregate-only shape need the migration accessor.
|
||||
- Aligns with ADR-295 (freshness) and the review's call for one canonical
|
||||
`NodeInference`/`RoomInference` contract.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit/integration tests: per-node classification round-trips through the MQTT
|
||||
mapper with no room fallback; two nodes with different rates both publish
|
||||
(no starvation); disagreeing nodes produce deterministic, non-flapping room
|
||||
state; a silent node's entities go stale, not frozen-online.
|
||||
- `cargo test -p wifi-densepose-sensing-server`.
|
||||
@@ -1,60 +0,0 @@
|
||||
# ADR-298: Model release sanity gates — block degenerate and mislabeled model artifacts
|
||||
|
||||
- **Status**: Accepted — initial implementation (this PR)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: models, evaluation, release-gate, honesty, presence
|
||||
|
||||
## Context
|
||||
|
||||
The external review (corroborating issue 1521) showed the published presence
|
||||
head is mathematically degenerate: with L2-normalized embeddings, a weight
|
||||
norm ≈ 3.67 against a bias ≈ 8.19 makes the smallest possible logit positive,
|
||||
so predicted presence probability is ≥ ~0.989 for every valid input — the
|
||||
decision boundary is unreachable and the head is effectively constant. The
|
||||
README then labeled a temporal-triplet accuracy (a representation-ordering
|
||||
metric) as "presence accuracy" — a category error.
|
||||
|
||||
Nothing in the release path catches a constant classifier, an unreachable
|
||||
boundary, or a metric-name mismatch. A machine check would have.
|
||||
|
||||
## Decision
|
||||
|
||||
Add a `model_gates` module (in `wifi-densepose-train`) plus a CI gate that,
|
||||
for any classifier artifact proposed for release, fails on:
|
||||
|
||||
- **Constant output** — output variance below a threshold across a diverse
|
||||
probe set (including the degenerate-embedding probe from issue 1521).
|
||||
- **Unreachable decision boundary** — for a normalized-embedding linear head,
|
||||
check whether `bias` sign dominates `‖weight‖` so the logit cannot change
|
||||
sign; fail if the boundary is analytically unreachable.
|
||||
- **Degenerate class balance** — predicted-positive rate at/above a ceiling
|
||||
(e.g. > 99%) on a balanced probe set.
|
||||
- **Missing/blank baseline** — a report without a paired mean-pose/majority
|
||||
baseline (ties into ADR-291 `EvaluationReport`).
|
||||
- **Metric-name provenance** — a metric may not be surfaced under a task name
|
||||
that does not match its computed kind (temporal-triplet ≠ presence);
|
||||
enforced by making the metric carry its kind and the label derive from it.
|
||||
|
||||
Each gate emits a structured, human-readable failure explaining the defect and
|
||||
the offending numbers.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The specific degenerate presence head cannot ship again, and the
|
||||
temporal-triplet-as-presence mislabel is structurally prevented.
|
||||
- Some existing artifacts will fail the gate on introduction — intended; they
|
||||
should fail.
|
||||
- The gate is heuristic, not a correctness proof; it catches the known
|
||||
failure shapes, not all bad models.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests: the issue-1521 weights fail the unreachable-boundary and
|
||||
constant-output gates; a healthy synthetic head passes; a temporal-triplet
|
||||
metric cannot be constructed with a presence label.
|
||||
- `cargo test -p wifi-densepose-train`; the CI gate runs in the model-check
|
||||
workflow.
|
||||
- This ADR does **not** withdraw the already-published artifact (an
|
||||
outward-facing action requiring maintainer sign-off) — it prevents
|
||||
recurrence and documents the model-card correction.
|
||||
@@ -1,50 +0,0 @@
|
||||
# ADR-299: Repository CSI data-incident controls — ignore rules and a pre-commit/CI policy check
|
||||
|
||||
- **Status**: Accepted — controls implemented; tree remediation gated on owner sign-off
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: privacy, data-governance, ci, security, incident
|
||||
|
||||
## Context
|
||||
|
||||
The external review found ~64.6 MB of tracked raw CSI recordings under
|
||||
`data/recordings/` and `v2/data/recordings/` (largest an ~61.8 MB overnight
|
||||
capture). CLAUDE.md explicitly prohibits committing CSI or person data. The
|
||||
`.gitignore` rule pointed only at a pre-rename path
|
||||
(`rust-port/wifi-densepose-rs/data/recordings/`) and did not cover the active
|
||||
directories, which is how the captures were committed. Raw CSI is person data
|
||||
(it encodes breathing, movement, presence), so this is a data incident, not a
|
||||
formatting nit.
|
||||
|
||||
## Decision
|
||||
|
||||
**Implemented now (mechanical, no data-ownership judgment):**
|
||||
|
||||
- Fix `.gitignore` to cover `data/recordings/`, `v2/data/recordings/`, the
|
||||
legacy path, and `*.csi.jsonl` / `*.csi.meta.json` globs (done in this PR).
|
||||
- Add a policy check (pre-commit hook + CI job) that fails when CSI-format
|
||||
files (`*.csi.jsonl`, `*.csi.meta.json`) or large JSONL captures are staged
|
||||
or present as tracked files, with a message pointing here. Tests may use
|
||||
only synthetic or expressly-consented minimal fixtures.
|
||||
|
||||
**Explicitly gated on data-owner sign-off (NOT done autonomously):**
|
||||
|
||||
- Removing the existing recordings from the tree, and any history rewrite, are
|
||||
outward-facing/destructive and require the data owner to first establish
|
||||
provenance, consent, purpose, retention authority, and redistribution
|
||||
rights. The review is correct that rewriting `origin` does not erase forks
|
||||
and clones; coordination is required. This ADR records the controls and the
|
||||
required follow-up; it does not delete the data.
|
||||
|
||||
## Consequences
|
||||
|
||||
- No new CSI captures can be committed (ignore + policy check).
|
||||
- The existing tracked recordings remain until the owner decides; the incident
|
||||
is documented and the guard prevents worsening it.
|
||||
- CI gains one fast policy job; contributors get a local pre-commit check.
|
||||
|
||||
## Validation
|
||||
|
||||
- Policy-check unit tests: a staged `*.csi.jsonl` fails; a synthetic fixture
|
||||
under an allowed test path passes; the check is deterministic and offline.
|
||||
- Manual confirmation that the new ignore globs cover both active directories.
|
||||
@@ -1,189 +0,0 @@
|
||||
# ADR-300: RuView perception substrate — a phased program for the calibration, evidence, trust, and deployment layer
|
||||
|
||||
- **Status**: Accepted — program framing; child ADRs carry their own status
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: program, architecture, calibration, evidence, provenance, fusion, fleet, epic
|
||||
|
||||
## Context
|
||||
|
||||
Three independent analyses converged on the same conclusion in 2026: a deep
|
||||
research sweep of the WiFi-sensing state of the art, an external technical and
|
||||
industry review, and an internal strategic assessment. All three found that
|
||||
RuView's gap is **not another sensing modality** but the horizontal layer that
|
||||
turns RF research into repeatable spatial infrastructure — measurement,
|
||||
calibration, out-of-distribution awareness, evidence accounting, authenticated
|
||||
identity, a canonical spatial model, and fleet deployment.
|
||||
|
||||
Several of these primitives already have foundations in the tree and should be
|
||||
**unified and made to produce signed, expiring certificates**, not rebuilt:
|
||||
|
||||
- `wifi-densepose-calibration` (enrollment, bank, anchor, runtime, specialist).
|
||||
- `frame::EvidenceLevel` L0–L5 as mandatory policy (ADR-282).
|
||||
- AetherArena benchmark infrastructure — v0 complete, CI-gated, witness ledger,
|
||||
live HF Space (ADR-149); board intentionally empty (benchmark-first).
|
||||
- RuField provenance/signature types (ADR-260/262/277/279) and BFLD
|
||||
attestation (ADR-141).
|
||||
- `worldgraph` crate; `wifi-densepose-mat/tracking` (tracker, fingerprint).
|
||||
- The in-flight ADR-295 (provenance state machine), ADR-296 (authenticated
|
||||
data plane, step one), ADR-298 (model sanity gates) — the first bricks.
|
||||
|
||||
## What RuView is optimizing for
|
||||
|
||||
Not inference capability — **epistemic reliability**:
|
||||
|
||||
```
|
||||
signal → observation → calibration → inference → uncertainty → evidence
|
||||
→ certificate → policy → governed action
|
||||
```
|
||||
|
||||
That pipeline is the product. The defensible category is not "RuView perceives
|
||||
the physical world" but "RuView determines what machines are justified in
|
||||
believing about it, proves why, and constrains what they may do with that
|
||||
belief."
|
||||
|
||||
### Four non-negotiable program rules
|
||||
|
||||
Every child ADR and implementation is bound by these:
|
||||
|
||||
1. **UNKNOWN is a first-class output, never an error condition.** A surface that
|
||||
cannot answer says UNKNOWN and stays legible; it does not throw, default to a
|
||||
confident class, or silently hold a stale value.
|
||||
2. **Capability certificates bind cryptographically.** Hardware, environment,
|
||||
model, calibration, metrics, expiry, and evidence level are bound under one
|
||||
signature (ADR-318/ADR-305). An unsigned or partially-bound certificate is
|
||||
not a certificate.
|
||||
3. **One canonical semantics downstream.** Every surface (MQTT, REST, WebSocket,
|
||||
RuField, Matter, agents, UI) consumes the same Observation → Inference →
|
||||
GovernedEvent types (ADR-306). No transport- or UI-specific reinterpretation.
|
||||
4. **Benchmarks expose worst-domain performance and confidence intervals.**
|
||||
Pooled accuracy is never sufficient for promotion (ADR-317).
|
||||
|
||||
### Certificate conditionality (the staleness guard)
|
||||
|
||||
The central architectural risk is **certificate staleness**: a room can remain
|
||||
syntactically calibrated while its RF distribution has drifted enough to
|
||||
invalidate the certificate. Therefore a capability certificate is **conditional
|
||||
on a continuously evaluated domain signature** (ADR-302), not a one-time stamp.
|
||||
Crossing the OOD threshold automatically degrades state and triggers
|
||||
recalibration rather than silently continuing:
|
||||
|
||||
```
|
||||
VALID → DEGRADED → UNKNOWN (auto-degrade on domain drift; triggers recalibration)
|
||||
```
|
||||
|
||||
This binds ADR-301 (calibration), ADR-302 (OOD), ADR-318 (certificate), and
|
||||
ADR-321 (policy): a degraded/unknown domain must invalidate the affected
|
||||
capability *before* a false confident inference reaches an actuator.
|
||||
|
||||
### Commercial framing — three primitives, not one product
|
||||
|
||||
- **RuView Runtime** — provides perception.
|
||||
- **RuView Certify** — establishes what a deployment can legitimately claim
|
||||
(calibration + evidence + capability certificate + policy).
|
||||
- **RuView Trust / Fleet** — keeps that claim valid across hardware, firmware,
|
||||
models, and environmental drift (ADR-316).
|
||||
|
||||
Certify and Trust are the parts that are hard to commoditize; presence
|
||||
detection alone is not.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a **21-primitive phased program**. Each primitive gets a child ADR
|
||||
(ADR-301…ADR-321) that owns its detailed decision, status, and validation.
|
||||
This ADR owns the framing, the dependency order, and the phase assignment.
|
||||
|
||||
### Primitive → ADR map
|
||||
|
||||
| # | Primitive | ADR | Phase |
|
||||
|---|---|---|---|
|
||||
| 1 | Automatic domain calibration | ADR-301 | 1 |
|
||||
| 2 | Out-of-distribution detection | ADR-302 | 1 |
|
||||
| 3 | Ground-truth synchronization | ADR-303 | 2 |
|
||||
| 4 | Evidence engine | ADR-304 | 1 |
|
||||
| 5 | Authenticated sensor identity | ADR-305 | 1 |
|
||||
| 6 | Canonical spatial ontology | ADR-306 | 1 |
|
||||
| 7 | Persistent identity & tracking | ADR-307 | 2 |
|
||||
| 8 | Sensor placement optimizer | ADR-308 | 3 |
|
||||
| 9 | Active sensing | ADR-309 | 3 |
|
||||
| 10 | 802.11bf-native architecture | ADR-310 | 2 |
|
||||
| 11 | Real sensor fusion | ADR-311 | 2 |
|
||||
| 12 | Long-term spatial memory | ADR-312 | 3 |
|
||||
| 13 | Counterfactual inference | ADR-313 | 3 |
|
||||
| 14 | Information-gain scheduler | ADR-314 | 3 |
|
||||
| 15 | Digital RF twin | ADR-315 | 3 |
|
||||
| 16 | Fleet control plane | ADR-316 | 2 |
|
||||
| 17 | Real benchmark service (multi-domain scorecard) | ADR-317 | 1 |
|
||||
| 18 | Capability certificates | ADR-318 | 1 |
|
||||
| 19 | Witness chain | ADR-319 | 1 |
|
||||
| 20 | RuView sensor HAL | ADR-320 | 2 |
|
||||
| 21 | Decision policy — action authorization | ADR-321 | 1 |
|
||||
|
||||
### Dependency order (why phase, not score, drives sequencing)
|
||||
|
||||
```
|
||||
ADR-306 spatial ontology ──┐
|
||||
ADR-305 auth identity ─────┼──► ADR-301 calibration cert ──► ADR-302 OOD gating
|
||||
│ │ │
|
||||
└──► ADR-319 witness chain │ (VALID→DEGRADED→UNKNOWN)
|
||||
│ ▼
|
||||
ADR-304 evidence engine ──► ADR-318 capability certificate
|
||||
│ │ (conditional on domain signature)
|
||||
│ ▼
|
||||
│ ADR-321 decision policy ──► governed action
|
||||
└──► ADR-317 benchmark scorecard (per-PR gate)
|
||||
```
|
||||
|
||||
- **Phase 1 (the certificate spine, built now):** foundational roots 303, 302,
|
||||
301, 298 (implemented first, in their own crates); then the dependent wave
|
||||
316, 299, 315, 314, 318. This set is exactly the acceptance test decomposed
|
||||
and is buildable without new hardware (types, logic, signatures, tests). The
|
||||
dependent wave adds the staleness guard (299 auto-degrades 315) and the
|
||||
action gate (318) that denies at the actuator on a degraded/unknown domain.
|
||||
- **Phase 2 (integration & operations):** 300 ground truth, 304 tracking, 307
|
||||
802.11bf-native, 308 fusion, 313 fleet, 317 HAL. Depends on the spine.
|
||||
- **Phase 3 (higher-ceiling, research-forward):** 305 placement optimizer, 306
|
||||
active sensing, 309 spatial memory, 310 counterfactual, 311 info-gain
|
||||
scheduler, 312 RF twin. Sit on top of the fused world state.
|
||||
|
||||
Phase-2 and phase-3 child ADRs are authored as **Proposed** (design intent,
|
||||
validation plan) and are not implemented by the phase-1 swarm.
|
||||
|
||||
### Acceptance test A — onboarding (from the strategic assessment)
|
||||
|
||||
> Connect a new sensor type in an unseen room. Within 30 minutes RuView should
|
||||
> identify the hardware (HAL, ADR-320), calibrate the environment (ADR-301),
|
||||
> quantify whether it can reliably sense the requested phenomenon (ADR-302),
|
||||
> generate a signed capability certificate (ADR-318), expose governed spatial
|
||||
> events (ADR-306), and return UNKNOWN whenever evidence falls outside that
|
||||
> certificate (ADR-302).
|
||||
|
||||
### Acceptance test B — drift invalidation (the staleness guard)
|
||||
|
||||
> Deliberately change the room after certification — move furniture, change the
|
||||
> AP channel, or substitute hardware. RuView should detect distribution drift
|
||||
> (ADR-302), invalidate the affected capability (ADR-318) **before** a false
|
||||
> confident inference reaches an actuator (ADR-321 denies with the specific
|
||||
> failed condition), emit UNKNOWN, preserve the complete witness chain
|
||||
> (ADR-319), and explain exactly which certificate condition failed.
|
||||
|
||||
Test B is the load-bearing one: it proves the substrate fails safe, not just
|
||||
that it perceives well. Phase 1 makes every clause except HAL testable in
|
||||
software; HAL (phase 2)
|
||||
closes the "identify the hardware" clause.
|
||||
|
||||
## Consequences
|
||||
|
||||
- One coherent substrate replaces overlapping ad-hoc schemas; every surface
|
||||
(MQTT, REST, WebSocket, RuField, Matter, agents) eventually consumes the
|
||||
ADR-306 ontology and the ADR-318 certificate.
|
||||
- Headline applications (pose/vitals/pointcloud models) are explicitly **not**
|
||||
the investment focus during this program, per the strategic direction.
|
||||
- Later ADRs may be revised as the spine lands; that is expected for a phased
|
||||
program and is why phase-2/3 ADRs ship as Proposed.
|
||||
|
||||
## Validation
|
||||
|
||||
- Each child ADR defines its own tests. The program-level exit is the
|
||||
acceptance test above, run end-to-end once phase 1 lands, and encoded as an
|
||||
AetherArena scenario (ADR-317).
|
||||
@@ -1,149 +0,0 @@
|
||||
# ADR-301: Automatic domain calibration — signed, versioned, invalidatable room fingerprint
|
||||
|
||||
- **Status**: Accepted — initial implementation planned (ADR-300 phase 1)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: calibration, provenance, drift, evidence, honesty, substrate
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is primitive 1 of the perception-substrate program (ADR-300) and the
|
||||
first brick of that program's "certificate spine" (ADR-300 phase 1). It depends
|
||||
on the canonical spatial ontology (ADR-306) to name *which space* it
|
||||
characterizes, on authenticated sensor identity (ADR-305) to bind a fingerprint
|
||||
to *which signed device* produced it, and on the witness chain (ADR-319) to
|
||||
anchor the resulting artifact. Its output is consumed directly by
|
||||
out-of-distribution detection (ADR-302).
|
||||
|
||||
WiFi sensing is only reproducible inside the environment it was tuned for.
|
||||
Multipath, furniture geometry, transceiver placement, and AP channel all shape
|
||||
the CSI distribution, so a model that reads a room correctly one week can drift
|
||||
silently the next. RuView already has the raw ingredients for room-aware
|
||||
sensing but not a single portable, signed, expiring artifact that says "this is
|
||||
the room, here is when it was measured, and here is the evidence that it is
|
||||
still the same room."
|
||||
|
||||
Existing scaffolding to build on, not rebuild (`v2/crates/wifi-densepose-calibration`):
|
||||
|
||||
- `enrollment` / `anchor` — guided human anchors with an adaptive quality gate.
|
||||
- `bank` / `specialist` / `runtime` — a versioned bank of small specialist
|
||||
models and a confidence-gated mixture runtime (`RoomState`), including the
|
||||
crate's existing honest `STALE` degradation when the ADR-135 empty-room
|
||||
baseline drifts.
|
||||
- `geometry` / `geometry_embedding` — transceiver-geometry record and its
|
||||
fixed-length conditioning featurization (ADR-152).
|
||||
|
||||
What is missing is (a) an *automatic* observe-only characterization phase that
|
||||
does not require a human enrollment ritual, (b) empty-vs-occupied baseline
|
||||
separation as a first-class pair, (c) a signed, versioned, comparable
|
||||
`CalibrationCertificate` artifact, and (d) explicit invalidation on drift rather
|
||||
than a soft `STALE` flag buried in the runtime.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Keep calibration internal to the runtime (status quo).** Rejected: the
|
||||
room characterization exists only as in-process state; it cannot be signed,
|
||||
shipped, compared across time, or presented as evidence to ADR-302/ADR-318.
|
||||
2. **Build a new calibration crate.** Rejected: `wifi-densepose-calibration`
|
||||
already owns enrollment, the specialist bank, geometry embedding, and the
|
||||
baseline-drift concept. A parallel crate would fork the room model.
|
||||
3. **Extend `wifi-densepose-calibration` with an automatic characterization
|
||||
phase and a signed certificate artifact.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Extend `v2/crates/wifi-densepose-calibration` with an `autocal` characterization
|
||||
phase and a `certificate` artifact module. The target UX is:
|
||||
|
||||
> install → observe (~10 min) → room fingerprint → calibration certificate →
|
||||
> sensing.
|
||||
|
||||
### 1. Automatic characterization (`autocal`)
|
||||
|
||||
- An observe-only pass (default ~10 minutes, configurable) that collects CSI
|
||||
without requiring guided human anchors, reusing the `anchor` quality gate to
|
||||
reject frames it cannot trust. It layers on the existing ADR-135 empty-room
|
||||
baseline rather than replacing it.
|
||||
- Produces a `RoomFingerprint`: a bounded, fixed-length statistical summary of
|
||||
the room's CSI distribution (subcarrier amplitude/phase moments, multipath
|
||||
structure, occupancy-band energy), plus the `geometry_embedding` when a
|
||||
geometry record is present. The fingerprint is the distance-comparable object
|
||||
ADR-302 measures against; its schema is versioned.
|
||||
|
||||
### 2. Empty / occupied baseline pair
|
||||
|
||||
- Characterization establishes a paired baseline: an **empty** distribution
|
||||
(no occupant motion) and an **occupied** distribution (motion present),
|
||||
separated by the existing occupancy signal rather than a manual label. Both
|
||||
are stored on the fingerprint so downstream OOD gating can distinguish "the
|
||||
empty room changed" (furniture/geometry drift) from "occupancy statistics
|
||||
changed" (different subject dynamics).
|
||||
|
||||
### 3. `CalibrationCertificate` artifact
|
||||
|
||||
- A serializable `CalibrationCertificate` binding: the `RoomFingerprint`; a
|
||||
space identifier from the ADR-306 ontology; the signing sensor identity from
|
||||
ADR-305; `captured_at_unix_s`; a monotonic `version`; a schema version; the
|
||||
calibration `tier`; and an `EvidenceLevel` (L0–L5, ADR-282) — an automatic
|
||||
characterization on real captured CSI is at most L1/L2 and is labelled as
|
||||
such, never L3+.
|
||||
- The certificate is **signed** using RuField provenance/signature types
|
||||
(ADR-260/262/277/279) and anchored in the witness chain (ADR-319). Signature
|
||||
and witness anchoring are mandatory: an unsigned certificate is not a valid
|
||||
certificate.
|
||||
- Two certificates for the same space are **comparable**: `distance(a, b)`
|
||||
returns a bounded fingerprint distance, which is the primitive ADR-302 uses
|
||||
to gate KNOWN → DEGRADED → UNKNOWN.
|
||||
|
||||
### 4. Invalidation and continuous drift compensation
|
||||
|
||||
- A certificate carries an explicit validity policy: it is invalidated when
|
||||
fingerprint distance against live traffic exceeds a threshold, when the AP
|
||||
channel or transceiver geometry changes, when the signing device identity
|
||||
changes, or on age expiry. Invalidation is an explicit state transition that
|
||||
emits a witness record (ADR-319), not a silent `STALE` flag.
|
||||
- Continuous drift compensation runs as a bounded online update of the
|
||||
fingerprint within a **compatibility envelope**: small drift is absorbed and
|
||||
logged; drift beyond the envelope invalidates the certificate and forces
|
||||
re-characterization. Compensation never silently rewrites a signed
|
||||
certificate — it produces a new version, preserving the append-only history.
|
||||
|
||||
### Provenance and honesty discipline
|
||||
|
||||
- No accuracy number is claimed by this ADR; it delivers the artifact and the
|
||||
distance/invalidation machinery. Any certificate produced from generated CSI
|
||||
is L0/`Synthetic` by construction; the constructor rejects labelling
|
||||
synthetic characterization as measured (ADR-279 invariant 6, ADR-282 ladder).
|
||||
- Certificates never leave the edge except through the governed control plane
|
||||
(ADR-277); a room fingerprint is treated as potentially sensitive spatial
|
||||
data, not free telemetry.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Room characterization becomes a portable, signed, versioned artifact that
|
||||
ADR-302 (OOD), ADR-318 (capability certificates), and ADR-317 (benchmark)
|
||||
can consume without re-deriving room state.
|
||||
- The automatic observe-only path lowers deployment friction (no mandatory
|
||||
enrollment ritual) but yields a weaker evidence level than guided enrollment;
|
||||
the certificate states which path produced it so consumers can weight it.
|
||||
- Explicit invalidation means RuView will sometimes refuse to sense a changed
|
||||
room until re-characterization. That refusal is the intended honest behavior,
|
||||
surfaced by ADR-302, not a regression.
|
||||
- The existing enrollment/bank/runtime path is preserved; `autocal` is an
|
||||
additional entry point that produces the same `RoomFingerprint` object the
|
||||
guided path can also emit.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test -p wifi-densepose-calibration` — fingerprint determinism from
|
||||
fixed synthetic CSI; empty/occupied separation on synthetic occupancy;
|
||||
certificate signing/verification round-trip and tamper rejection;
|
||||
`distance()` monotonicity on progressively perturbed fixtures; invalidation
|
||||
transitions (channel change, geometry change, age, drift-envelope breach)
|
||||
each emit the expected witness record; constructor rejects synthetic→measured
|
||||
mislabeling.
|
||||
- Cross-ADR: an ADR-302 test consumes a certificate and asserts the gating
|
||||
state transitions on a drifted fingerprint.
|
||||
- Real-silicon characterization (ESP32 capture over a real 10-minute window)
|
||||
remains a follow-up requiring hardware evidence per CLAUDE.md; a successful
|
||||
build or synthetic run is not hardware evidence.
|
||||
@@ -1,135 +0,0 @@
|
||||
# ADR-302: Out-of-distribution detection — KNOWN / DEGRADED / UNKNOWN gating
|
||||
|
||||
- **Status**: Accepted — initial implementation planned (ADR-300 phase 1)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: ood, calibration, uncertainty, quality, evidence, honesty, substrate
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is primitive 2 of the perception-substrate program (ADR-300) and part
|
||||
of the phase-1 certificate spine. It sits directly downstream of automatic
|
||||
domain calibration (ADR-301): the `CalibrationCertificate` and its
|
||||
`RoomFingerprint` are the reference distribution this ADR measures against. It
|
||||
reuses fusion-layer quality scoring (ADR-137) as one of its inputs and feeds
|
||||
its state into the evidence engine (ADR-304) and capability certificates
|
||||
(ADR-318).
|
||||
|
||||
The central unsolved problem of WiFi sensing is cross-domain generalization: a
|
||||
model trained (or calibrated) in one room degrades unpredictably in another, or
|
||||
in the same room after furniture moves, the AP changes channel, or the radio
|
||||
hardware is swapped. A model that keeps returning confident classifications
|
||||
under these conditions is the single most misleading failure mode in the field,
|
||||
and it is the failure the strategic assessment (ADR-300) named explicitly.
|
||||
Confidence alone is insufficient: a softmax head is perfectly capable of being
|
||||
confidently wrong on out-of-distribution input. RuView must be able to say
|
||||
"I do not recognize this situation" instead of guessing.
|
||||
|
||||
Today RuView has partial signals but no unified gate:
|
||||
|
||||
- ADR-301 produces a comparable `RoomFingerprint` and a `distance()` metric.
|
||||
- ADR-137 `QualityScore` carries fusion coherence, evidence references, and
|
||||
contradiction flags per fused frame.
|
||||
- Model heads emit confidence/uncertainty, but nothing combines domain
|
||||
distance, signal quality, calibration compatibility, and uncertainty into a
|
||||
single decision, and nothing forces a model to stop emitting confident labels
|
||||
when it leaves its calibrated domain.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Threshold on model confidence alone.** Rejected: confidently-wrong OOD
|
||||
predictions are exactly the failure mode; confidence is necessary but not
|
||||
sufficient.
|
||||
2. **A per-model bespoke OOD check inside each task head.** Rejected:
|
||||
duplicates logic, cannot be audited uniformly, and does not compose with the
|
||||
calibration certificate or the evidence engine.
|
||||
3. **A shared OOD gate that every inference passes through, fusing four signals
|
||||
against the ADR-301 certificate.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Add an out-of-distribution gate — implemented in a shared crate consumed by the
|
||||
task-head runtime (`wifi-densepose-calibration::runtime` and the model serving
|
||||
path) — that attaches a `DomainState` to **every** inference.
|
||||
|
||||
### 1. Four inputs, one decision
|
||||
|
||||
Each inference carries four measured quantities:
|
||||
|
||||
1. **Domain distance** — fingerprint distance (ADR-301 `distance()`) between
|
||||
live traffic and the active `CalibrationCertificate`, split into the
|
||||
empty-baseline and occupied-baseline components so geometry drift and
|
||||
occupancy-statistics drift are distinguishable.
|
||||
2. **Signal quality** — reuse the ADR-137 quality scoring signals (fusion
|
||||
coherence, contradiction flags) plus per-frame SNR/validity.
|
||||
3. **Calibration compatibility** — is a valid, non-invalidated certificate
|
||||
present for this space (ADR-306) and this signed device (ADR-305)? An
|
||||
expired, invalidated, or device-mismatched certificate is itself a
|
||||
compatibility failure.
|
||||
4. **Uncertainty** — the model head's own predictive uncertainty.
|
||||
|
||||
### 2. State machine: KNOWN → DEGRADED → UNKNOWN
|
||||
|
||||
- **KNOWN** — domain distance within the certificate's compatibility envelope,
|
||||
quality above threshold, certificate valid and compatible, uncertainty low.
|
||||
Confident classifications are returned.
|
||||
- **DEGRADED** — one or more signals crossed a soft threshold (e.g. moderate
|
||||
fingerprint drift within the envelope, elevated uncertainty, a tolerated
|
||||
ADR-137 contradiction flag). Classifications are returned but flagged
|
||||
degraded with the specific reason; downstream consumers must treat them as
|
||||
lower-evidence.
|
||||
- **UNKNOWN** — the room changed materially (empty-baseline drift beyond the
|
||||
envelope, AP channel change, transceiver-geometry change, hardware/device
|
||||
change, or an invalidated/absent certificate). RuView **stops returning
|
||||
confident classifications** and returns UNKNOWN with the triggering cause.
|
||||
This is the required behavior, not an error.
|
||||
|
||||
State transitions are hysteretic (separate enter/exit thresholds) so the gate
|
||||
does not flap on noise. The state, the four input values, and the triggering
|
||||
cause are all reported — never a bare label.
|
||||
|
||||
### 3. Certificate-bound, honest by construction
|
||||
|
||||
- The gate is meaningless without a certificate: with no valid ADR-301
|
||||
certificate for the current space/device, the default state is UNKNOWN, not
|
||||
KNOWN. Absence of evidence is treated as absence of capability.
|
||||
- The `DomainState` and its inputs are emitted to the evidence engine
|
||||
(ADR-304) as part of every inference record, and are an input to the ADR-318
|
||||
capability certificate (a model's capability is bounded by the domain it can
|
||||
hold KNOWN in).
|
||||
- No accuracy number is claimed here; the ADR delivers the gating machinery.
|
||||
The gate's own thresholds are calibration parameters, reported with each
|
||||
decision.
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView gains a uniform, auditable answer to "should I trust this inference?"
|
||||
that combines domain, quality, calibration, and uncertainty rather than
|
||||
confidence alone.
|
||||
- Deployments will see more DEGRADED/UNKNOWN results than a
|
||||
confidence-only system, especially right after a room changes. That increase
|
||||
is the product working: it is the difference between honest RF perception and
|
||||
confidently-wrong output.
|
||||
- Every task head that opts into the substrate must route through the gate;
|
||||
heads that bypass it cannot claim a KNOWN state or earn an ADR-318
|
||||
certificate.
|
||||
- The gate couples model serving to the presence of a live calibration
|
||||
certificate, making ADR-301 a hard dependency of confident inference — the
|
||||
intended coupling.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test` on the OOD crate — state-machine transitions on synthetic
|
||||
fixtures: in-envelope drift stays KNOWN; soft-threshold breach → DEGRADED;
|
||||
empty-baseline drift beyond envelope, channel change, geometry change,
|
||||
device mismatch, and invalidated/absent certificate each → UNKNOWN;
|
||||
hysteresis prevents flapping under injected noise; missing certificate
|
||||
defaults to UNKNOWN.
|
||||
- Cross-ADR: consumes an ADR-301 certificate and asserts a drifted fingerprint
|
||||
drives the expected transition; asserts the `DomainState` is present on every
|
||||
emitted inference record consumed by ADR-304.
|
||||
- No confident classification is emitted in the UNKNOWN state in any test —
|
||||
enforced as an assertion, not a convention.
|
||||
- Real-silicon OOD behavior (moving furniture / changing AP channel on a live
|
||||
ESP32 capture and observing the transition) remains a follow-up requiring
|
||||
hardware evidence per CLAUDE.md.
|
||||
@@ -1,125 +0,0 @@
|
||||
# ADR-303: Ground-truth synchronization — reference sensors as a formal validation plane
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 2)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: ground-truth, validation, fusion, evidence, benchmark, honesty, substrate
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is primitive 3 of the perception-substrate program (ADR-300), authored
|
||||
as **Proposed** in phase 2: it is design intent and a validation plan, not
|
||||
implemented by the phase-1 swarm. It sits on top of the phase-1 certificate
|
||||
spine and feeds the evidence engine (ADR-304) and the real benchmark service
|
||||
(ADR-317). It generalizes the vitals ground-truth rig (ADR-293) from a single
|
||||
measurand to a modality-agnostic plane.
|
||||
|
||||
RuView's evidence discipline (CLAUDE.md; ADR-282 ladder) requires MEASURED
|
||||
accuracy claims to be backed by an independent reference. ADR-293 built exactly
|
||||
this for vitals: reference-series ingest, time alignment (cross-correlation
|
||||
lag + optional clock-drift fit), and agreement statistics (MAE/RMSE/bias/
|
||||
Bland–Altman/within-tolerance), with an `EvidenceGrade` that is only
|
||||
constructible as `Measured` when a real reference, non-zero paired samples,
|
||||
minimum coverage, and a reproducer are present. That machinery is measurand- and
|
||||
device-shaped: it knows about heart rate and breathing rate.
|
||||
|
||||
The substrate needs the same discipline for *every* phenomenon RuView senses —
|
||||
presence, count, localization, pose, posture, activity — and for reference
|
||||
sources of many modalities (cameras, mmWave, pressure mats, wearables, pulse
|
||||
oximeters, microphones, manual labels). The critical design decision is that
|
||||
these reference sensors form a **validation plane**, not additional inference
|
||||
inputs.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Fuse reference sensors as extra inference inputs.** Rejected on principle:
|
||||
folding cameras/mmWave into the estimator would make RuView's RF claims
|
||||
unfalsifiable — the reference would be training the thing it is meant to
|
||||
check, and a camera-fed result is no longer a camera-free RF result. It
|
||||
would also violate the ADR-282 layering (RuView is probabilistic
|
||||
exteroception, never ground truth) and the honesty rule against presenting
|
||||
fused-with-camera output as WiFi sensing.
|
||||
2. **One-off rigs per measurand (extend ADR-293 ad hoc each time).** Rejected:
|
||||
duplicates alignment/agreement code per phenomenon and never yields a shared
|
||||
validation surface for the benchmark.
|
||||
3. **A first-class, modality-agnostic `GroundTruth` API that is strictly a
|
||||
validation plane.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce a `GroundTruth` API — a modality-agnostic validation plane that
|
||||
compares RF inference against independent observation and never feeds it.
|
||||
|
||||
### 1. Modality-agnostic reference ingest
|
||||
|
||||
- A `ReferenceObservation` generalizing ADR-293's `ReferenceSeries`: a
|
||||
timestamped, typed observation of a `Phenomenon` (presence, count,
|
||||
localization, pose keypoints, posture, activity, heart rate, breathing rate)
|
||||
from a `ReferenceModality` (camera, mmWave, pressure, wearable, pulse
|
||||
oximeter, microphone, manual label), with device/source metadata and the
|
||||
measurement principle recorded.
|
||||
- Untrusted reference files are validated at the boundary (row-numbered
|
||||
rejections, non-monotonic timestamps are errors), reusing ADR-293's ingest
|
||||
discipline. Camera/mmWave references arrive as exported label/keypoint
|
||||
streams, not live model feeds.
|
||||
|
||||
### 2. Synchronization
|
||||
|
||||
- Generalize ADR-293's time alignment (bounded-lag normalized cross-correlation
|
||||
+ optional linear clock-drift fit) to arbitrary measurands on a common
|
||||
resampled grid, with no interpolation across gaps beyond a configurable
|
||||
limit. Alignment parameters are always reported, never silently applied.
|
||||
- Spatial synchronization where relevant: reference observations are expressed
|
||||
in the ADR-306 spatial ontology so an RF localization/pose result and a
|
||||
camera/mmWave observation are compared in one coordinate frame.
|
||||
|
||||
### 3. Agreement as validation, not fusion
|
||||
|
||||
- A modality-appropriate `AgreementReport` per phenomenon: continuous
|
||||
measurands reuse ADR-293's MAE/RMSE/bias/Bland–Altman/within-tolerance;
|
||||
categorical/detection phenomena (presence, activity) report confusion-matrix
|
||||
metrics; spatial phenomena report localization error percentiles and pose
|
||||
PCK **with the mandatory mean-pose baseline and leakage-free split**
|
||||
(CLAUDE.md; ADR-291).
|
||||
- Session scope is mandatory metadata (subject count, motion state, LOS/NLOS/
|
||||
through-wall, distance band) — a report without scope cannot be constructed,
|
||||
as in ADR-293.
|
||||
|
||||
### 4. Evidence and isolation guarantees
|
||||
|
||||
- The plane is one-directional by type: the inference path has no read access
|
||||
to `GroundTruth` at runtime. A build/test-time isolation check (and the type
|
||||
boundary) prevents a reference observation from becoming an estimator input.
|
||||
- Reports carry an `EvidenceLevel` (ADR-282) and an `EvidenceGrade`
|
||||
constructible as `Measured` only with a real reference, paired samples,
|
||||
coverage, and a reproducer (ADR-293 rule). Reports feed the ADR-304 evidence
|
||||
engine and are the substrate ADR-317 scores against.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Every phenomenon RuView senses gets the same MEASURED-vs-independent-observer
|
||||
discipline vitals already has, in one shared surface.
|
||||
- Keeping references strictly as validation preserves the falsifiability and
|
||||
the camera-free identity of RF results; it costs the (tempting) accuracy a
|
||||
camera-fused estimator would show, which is the correct trade.
|
||||
- Reference capture is an operational burden (a camera/mmWave rig per validated
|
||||
session); acceptable because it is a validation activity, not a runtime
|
||||
requirement, and it is what turns CLAIMED into MEASURED.
|
||||
- Because this is Proposed (phase 2), the API shape may be revised once the
|
||||
phase-1 spine (ADR-301/299/301/303) lands and the benchmark (ADR-317)
|
||||
exercises it.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests (planned): modality-agnostic ingest rejection cases; alignment
|
||||
recovery of known synthetic offsets/drifts across measurands; agreement math
|
||||
per phenomenon against hand-computed fixtures; pose PCK path requires a
|
||||
mean-pose baseline and rejects leaky splits; evidence-grade constructibility;
|
||||
the isolation check fails a build that wires a reference into the inference
|
||||
path.
|
||||
- Cross-ADR: an ADR-317 benchmark scenario consumes `GroundTruth` reports as
|
||||
its scored reference; ADR-304 ingests the agreement reports as evidence
|
||||
records.
|
||||
- Real-session validation (RF capture synchronized with a real camera/mmWave/
|
||||
pressure/wearable reference) is the phase-2 exit and requires hardware
|
||||
evidence per CLAUDE.md; a synthetic run is not hardware evidence.
|
||||
@@ -1,117 +0,0 @@
|
||||
# ADR-304: Evidence engine — MLflow for physical sensing
|
||||
|
||||
- **Status**: Accepted — initial implementation planned (ADR-300 phase 1)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: evidence, provenance, ledger, accuracy, drift, benchmark, honesty, substrate
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is primitive 4 of the perception-substrate program (ADR-300) and a
|
||||
central pillar of the phase-1 certificate spine. It consumes the domain state
|
||||
from out-of-distribution detection (ADR-302) and the calibration age from the
|
||||
calibration certificate (ADR-301), it is the store that capability certificates
|
||||
(ADR-318) are minted from, and it is the accuracy source the real benchmark
|
||||
service (ADR-317) reads. In phase 2 it ingests agreement reports from the
|
||||
ground-truth plane (ADR-303).
|
||||
|
||||
The strategic assessment (ADR-300) judged this primitive **more commercially
|
||||
important than another pose architecture**: what unblocks OEM and integrator
|
||||
conversations is not a higher headline number but a defensible, auditable record
|
||||
of how a model actually performs, per room, per device, per subject, over time.
|
||||
MLflow made ML experiments trackable; physical sensing needs the equivalent for
|
||||
deployed accuracy, drift, and evidence level — an append-only ledger, not a
|
||||
dashboard that overwrites yesterday's number.
|
||||
|
||||
RuView already has the constituent evidence types; what is missing is the ledger
|
||||
that unifies them per deployment context:
|
||||
|
||||
- RuField provenance/signature types (ADR-260/262/277/279) — the signed,
|
||||
provenance-bearing record types to reuse rather than reinvent.
|
||||
- The AetherArena witness-ledger pattern (ADR-149) — an append-only,
|
||||
witness-anchored ledger of scored results, the structural template here.
|
||||
- `frame::EvidenceLevel` L0–L5 (ADR-282) — the mandatory evidence tag every
|
||||
record carries.
|
||||
- ADR-302 `DomainState`, ADR-137 `QualityScore`, ADR-301 certificate version
|
||||
and age — the per-inference signals to accumulate.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Log accuracy to flat files / metrics dashboards.** Rejected: mutable,
|
||||
un-signed, un-scoped, and not comparable over time — the exact gap.
|
||||
2. **Reuse a general experiment tracker (MLflow itself).** Rejected: it is
|
||||
experiment-time, not deployment-time; it has no notion of room/device/
|
||||
subject context, calibration age, evidence level, or signed provenance, and
|
||||
it would add an external service dependency contrary to the substrate's
|
||||
edge-first, dependency-light direction.
|
||||
3. **A native append-only evidence ledger reusing RuField record types and the
|
||||
AetherArena ledger pattern.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Build an **evidence engine**: a per-`(room, device, subject)` append-only
|
||||
accuracy ledger that every model automatically writes to.
|
||||
|
||||
### 1. The evidence record
|
||||
|
||||
- An `EvidenceRecord` keyed by context — space id (ADR-306), signed device id
|
||||
(ADR-305), and subject id where consented and available — carrying: model
|
||||
version; calibration certificate version and **age** (ADR-301); the ADR-302
|
||||
`DomainState` (KNOWN/DEGRADED/UNKNOWN) and its four inputs; the ADR-137
|
||||
quality signals; predictive uncertainty; and, when a reference is present
|
||||
(ADR-303), the agreement result (accuracy, false-positive rate). Each record
|
||||
carries exactly one `EvidenceLevel` (L0–L5, ADR-282).
|
||||
- Records are **append-only** and signed with RuField signature types
|
||||
(ADR-260/262/277/279); the ledger is anchored in the witness chain (ADR-319),
|
||||
following the AetherArena witness-ledger pattern (ADR-149). No record is ever
|
||||
mutated in place — a correction is a new record.
|
||||
|
||||
### 2. Per-context accuracy accounting
|
||||
|
||||
- The engine maintains, per `(room, device, subject)` context: measured
|
||||
accuracy (only where an ADR-303 reference backs it — otherwise the record is
|
||||
CLAIMED/SYNTHETIC, never MEASURED), false-positive rate, drift trajectory
|
||||
(fingerprint distance over time from ADR-301), the fraction of inferences in
|
||||
each domain state, calibration age distribution, and model-version history.
|
||||
- Aggregation is a pure function over the append-only log at a queried time —
|
||||
the ledger is the source of truth; summaries are derived, never authoritative
|
||||
(mirroring CLAUDE.md's "source over summaries" rule).
|
||||
|
||||
### 3. Honesty enforced in the record
|
||||
|
||||
- The engine cannot upgrade an evidence level; a level is set by the record's
|
||||
provenance at write time (synthetic input → L0/`Synthetic`; no reference →
|
||||
CLAIMED; reference + reproducer → MEASURED), reusing the ADR-282/ADR-291/
|
||||
ADR-293 constructor discipline. A benchmark or certificate reading the ledger
|
||||
gets the honest level, not an optimistic rollup.
|
||||
- No benchmark numbers are invented by this ADR; it delivers the ledger and the
|
||||
accounting. Empty contexts report "no evidence," which downstream (ADR-318)
|
||||
must treat as no capability.
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView gains a single auditable answer to "how well does this model actually
|
||||
work, here, on this device, for this subject, and how fresh is the
|
||||
calibration?" — the artifact OEM/integrator diligence actually asks for.
|
||||
- ADR-318 capability certificates become derivable (a certificate is a signed
|
||||
attestation over a slice of the ledger) and ADR-317 gains a real accuracy
|
||||
source per PR instead of self-reported numbers.
|
||||
- The append-only, signed design has storage and key-management cost; bounded
|
||||
by per-context retention policy and by reusing the existing RuField/witness
|
||||
infrastructure rather than a new store.
|
||||
- Some contexts will show sparse or unflattering evidence. Surfacing that is the
|
||||
point; the engine must never paper over a thin context with a global average.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test` on the evidence-engine crate — append-only invariant (no
|
||||
in-place mutation; corrections are new records); per-context aggregation math
|
||||
against fixtures; evidence-level is set by provenance and cannot be upgraded;
|
||||
signature round-trip and tamper rejection; witness anchoring; empty-context
|
||||
queries return "no evidence" not a fabricated number.
|
||||
- Cross-ADR: ingests ADR-302 `DomainState` and (phase 2) ADR-303 agreement
|
||||
reports; an ADR-318 test mints a certificate from a ledger slice and an
|
||||
ADR-317 test reads accuracy from the ledger.
|
||||
- Real-deployment evidence (a populated ledger from live ESP32 captures with
|
||||
ADR-303 references) is the maturity milestone and requires hardware evidence
|
||||
per CLAUDE.md; a synthetic ledger is L0 by construction.
|
||||
@@ -1,147 +0,0 @@
|
||||
# ADR-305: Authenticated sensor identity — RF chain of custody
|
||||
|
||||
- **Status**: Accepted — initial implementation planned (ADR-300 phase 1)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: security, identity, provenance, sensor-ingest, attestation, phase-1
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** (perception substrate program) and owns
|
||||
primitive #5, *authenticated sensor identity*. In the ADR-300 dependency DAG it
|
||||
is a spine root that, together with **ADR-306** (canonical spatial ontology),
|
||||
feeds **ADR-301** (calibration certificate) and **ADR-319** (witness chain).
|
||||
|
||||
RuView's inference outputs are only as trustworthy as the measurements that
|
||||
produced them, yet today a measurement's origin is essentially assertional. The
|
||||
UDP data plane accepts frames from any reachable host: **ADR-296** shipped step
|
||||
one — a loopback-default bind (`--udp-bind`) and an optional source
|
||||
IP/CIDR allowlist — and explicitly deferred to a follow-up ADR "per-device
|
||||
provisioned keys, MAC/AEAD, device identifiers, monotonic sequence numbers,
|
||||
freshness window, and replay rejection." **This ADR is that step two.** ADR-296
|
||||
correctly documented that an IP allowlist does not stop LAN spoofing; a
|
||||
cryptographic device identity is what closes that gap.
|
||||
|
||||
Foundations already exist in the tree and must be reused rather than rebuilt:
|
||||
|
||||
- `wifi-densepose-rufield` provides `DeviceId`, `Signature`, `SignatureBlock`,
|
||||
`FrameProvenance`, `ProvenanceClass`, and `SignatureVerifyError` — the type
|
||||
vocabulary for a signed frame.
|
||||
- `wifi-densepose-bfld` provides `CapabilityAttestation` and
|
||||
`PrivacyAttestationProof` (BFLD attestation, ADR-141) — the device-side
|
||||
attestation surface.
|
||||
- **ADR-295** defines the source-provenance state machine and freshness
|
||||
(`SpatialStateFreshness`); a monotonic sequence and freshness window slot
|
||||
into that machine rather than duplicating it.
|
||||
|
||||
The gap is not new primitives but an **end-to-end chain of custody**: a frame
|
||||
must be traceable as `device → signed measurement → sequence → timestamp →
|
||||
calibration → inference → signed event`, with every link verified at the
|
||||
ingest boundary per CLAUDE.md ("validate untrusted input at every network,
|
||||
hardware, and FFI boundary; default to least authority").
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Stop at ADR-296 (bind + IP allowlist).** Rejected: ADR-296 itself names
|
||||
this insufficient on a trusted LAN; any on-subnet host can still spoof a
|
||||
device.
|
||||
2. **TLS/DTLS transport authentication only.** Rejected: authenticates the
|
||||
*channel*, not the *measurement*. It does not survive store-and-forward,
|
||||
does not bind a sequence number into the signed object, and gives the
|
||||
downstream evidence/witness layers nothing to re-verify offline.
|
||||
3. **Per-device signing keys with a signed measurement envelope, monotonic
|
||||
sequence, and freshness window, reusing the RuField/BFLD types.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce an **authenticated frame envelope** carried through the sensing
|
||||
server, built from existing RuField/BFLD types.
|
||||
|
||||
### 1. Per-device provisioned identity
|
||||
|
||||
- Each radio (ESP32-S3/C6 node or adapter) is provisioned with a keypair; the
|
||||
device holds the private key, the server holds the enrolled public key bound
|
||||
to a `DeviceId`. Provisioning is an explicit, authorized enrollment step — a
|
||||
device is untrusted until an operator enrolls its public key. Private keys are
|
||||
never logged or committed (CLAUDE.md credential rule); the ESP32 side follows
|
||||
`firmware/esp32-csi-node` key-handling notes.
|
||||
- The enrollment record binds `DeviceId → public key → capabilities`
|
||||
(via `CapabilityAttestation`, ADR-141), so a device can only assert
|
||||
measurements for phenomena it is attested to sense. This is what **ADR-318**
|
||||
(capability certificate) later consumes.
|
||||
|
||||
### 2. Signed measurement envelope
|
||||
|
||||
- A frame on the wire becomes a `SignatureBlock` over the canonical
|
||||
serialization of `{DeviceId, sequence, timestamp, measurement-hash}`. The
|
||||
measurement itself (CSI/CIR payload) is covered by the hash so tampering is
|
||||
detectable without embedding the whole payload twice.
|
||||
- Verification uses `Signature`/`SignatureVerifyError` from
|
||||
`wifi-densepose-rufield`. A frame that fails signature verification is
|
||||
dropped and counted, exactly as ADR-296 drops disallowed sources — an `Err`
|
||||
at the boundary, never a warning that proceeds.
|
||||
|
||||
### 3. Monotonic sequence + freshness (replay defense)
|
||||
|
||||
- Each device maintains a strictly monotonic per-device sequence number. The
|
||||
server tracks the last accepted sequence per `DeviceId`; a non-increasing
|
||||
sequence is rejected as a replay.
|
||||
- A freshness window bounds `timestamp` against the server clock skew budget;
|
||||
stale frames are rejected. This reuses ADR-295's `SpatialStateFreshness`
|
||||
rather than inventing a parallel notion of staleness, and composes with
|
||||
ADR-297's stale-node handling.
|
||||
|
||||
### 4. Chain of custody into the event
|
||||
|
||||
- On successful verification the frame's `FrameProvenance` records the verified
|
||||
`DeviceId`, sequence, and timestamp. Calibration (ADR-301) and inference
|
||||
annotate their transforms, and the emitted spatial event (ADR-306 ontology)
|
||||
carries a signed provenance lineage. `ProvenanceClass` still enforces the
|
||||
synthetic/measured invariant from ADR-282/ADR-279 (invariant 6): a measured
|
||||
chain of custody can never be aliased to synthetic and vice-versa.
|
||||
- This end-to-end signed lineage is the substrate the **ADR-319** witness chain
|
||||
serializes and the **ADR-318** capability certificate points at as evidence.
|
||||
|
||||
### Compatibility
|
||||
|
||||
- The envelope is **opt-in per deployment** and negotiated at enrollment. An
|
||||
un-enrolled single-node desktop deployment keeps working unauthenticated
|
||||
behind ADR-296's loopback default; a routable, multi-node, or fleet
|
||||
deployment (ADR-316) requires enrolled identities. The startup security log
|
||||
(ADR-296) is extended to state whether frame authentication is active.
|
||||
|
||||
## Consequences
|
||||
|
||||
- LAN spoofing and replay — the residual risks ADR-296 named plainly — are
|
||||
closed for enrolled deployments. The measurement, not merely the channel, is
|
||||
authenticated, so the guarantee survives store-and-forward into the witness
|
||||
chain.
|
||||
- Enrollment/key-management is now an operational responsibility (provisioning,
|
||||
rotation, revocation). This is documented as a deployment step; key rotation
|
||||
and revocation lists are specified here but their fleet distribution is
|
||||
owned by ADR-316.
|
||||
- Signature verification adds per-frame CPU cost at ingest; bounded and
|
||||
measured in validation below. It is a deliberate cost for a verifiable chain
|
||||
of custody.
|
||||
- A schema addition to the frame contract; un-enrolled deployments are
|
||||
unaffected, and the migration accessor mirrors ADR-297's approach.
|
||||
- **No spoof-resistance claim is MEASURED until validated on real silicon**
|
||||
(CLAUDE.md hardware rule): a passing unit/integration suite demonstrates the
|
||||
logic, not the fielded device path.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests (`cargo test -p wifi-densepose-sensing-server`,
|
||||
`-p wifi-densepose-rufield`): valid envelope accepted; bad signature
|
||||
rejected and counted; non-monotonic sequence rejected as replay; out-of-
|
||||
window timestamp rejected; un-enrolled `DeviceId` rejected; measured/synthetic
|
||||
provenance aliasing rejected (ADR-279 invariant 6).
|
||||
- Integration test: a captured/synthesized multi-frame stream produces a
|
||||
verifiable `device → … → signed event` lineage that ADR-319 can serialize and
|
||||
re-verify offline.
|
||||
- Benchmark (`cargo bench`): per-frame verification cost, to bound ingest
|
||||
overhead.
|
||||
- **Real-silicon evidence required** before any deployment-grade
|
||||
authentication claim: a captured boot/runtime log from an enrolled ESP32 node
|
||||
signing frames end-to-end. A successful build or simulator run is not
|
||||
hardware evidence.
|
||||
@@ -1,142 +0,0 @@
|
||||
# ADR-306: Canonical spatial ontology — one Site→…→Event model for every surface
|
||||
|
||||
- **Status**: Accepted — initial implementation planned (ADR-300 phase 1)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: ontology, worldgraph, schema, mqtt, matter, rufield, phase-1
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** and owns primitive #6, *canonical spatial
|
||||
ontology*. In the ADR-300 DAG it is a spine root alongside **ADR-305**
|
||||
(authenticated identity) and feeds every downstream primitive that must speak
|
||||
about *where* and *what*: **ADR-301** (calibration), **ADR-307** (tracking,
|
||||
consumes `Track`/`Person`), **ADR-319** (witness chain), and every external
|
||||
surface named in the ADR-300 consequences (MQTT, REST, WebSocket, RuField,
|
||||
Matter, agents).
|
||||
|
||||
RuView currently expresses "where something is" in several overlapping,
|
||||
per-surface schemas: the MQTT/Home-Assistant mapper has its own node/room
|
||||
shapes (**ADR-297** just introduced `NodeInference`/`RoomInference` to
|
||||
disambiguate node vs. room state); the `worldgraph` crate models a spatial
|
||||
graph; RuField carries `SemanticProvenance`; Matter/HomeKit has its own area
|
||||
model. The same physical fact — "a person is in the kitchen" — is re-encoded
|
||||
differently on each surface, and the review called for "one canonical
|
||||
`NodeInference`/`RoomInference` contract" (ADR-297 consequences). Without a
|
||||
single semantic model, every new surface multiplies the translation matrix and
|
||||
each translation is a place where provenance and evidence level (ADR-282) can
|
||||
be silently dropped.
|
||||
|
||||
Substantial scaffolding already exists and must be **reused/extended, not
|
||||
rebuilt**. `v2/crates/worldgraph/wifi-densepose-worldgraph` already defines:
|
||||
|
||||
- `WorldNode` variants including `Room { area_id, name, bounds_enu, floor }`,
|
||||
`Zone { parent_room, … }`, `Wall { rf_attenuation_db }`, and `Doorway`.
|
||||
- `WorldEdge` variants including `Observes { quality, last_seen_unix_ms }`,
|
||||
`LocatedIn { since_unix_ms }`, `AdjacentTo { via_doorway }`, and `Supports`.
|
||||
- `WorldGraph`, `WorldGraphSnapshot`, `WorldId`, `SemanticProvenance`,
|
||||
`PersonPosition`, and a HomeCore `area_id` linkage join key (ADR-127).
|
||||
|
||||
The `worldgraph` crate is therefore the natural home for the canonical model.
|
||||
What is missing is (a) the full `Site → Building → Floor → Space → Zone`
|
||||
containment spine above `Room`, (b) first-class `Sensor`, `Object`,
|
||||
`Observation`, `Track`, and `Event` node types, (c) one canonical serialization
|
||||
that every surface consumes, and (d) a documented migration path from the
|
||||
existing per-surface schemas.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Leave each surface with its own schema; add adapters pairwise.** Rejected:
|
||||
O(surfaces²) translations, and provenance/evidence loss at each hop.
|
||||
2. **Invent a new top-level ontology crate.** Rejected: `worldgraph` already
|
||||
models rooms, zones, walls, doorways, observation edges, and HomeCore
|
||||
linkage; a parallel crate would fork the world model.
|
||||
3. **Extend `worldgraph` into the canonical ontology and make every surface a
|
||||
projection of it.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt **one canonical spatial ontology**, hosted in the `worldgraph` crate,
|
||||
that every RuView surface reads from and writes to.
|
||||
|
||||
### 1. The containment spine and entity types
|
||||
|
||||
Define the full node taxonomy as an extension of the existing `WorldNode`:
|
||||
|
||||
```
|
||||
Site ▸ Building ▸ Floor ▸ Space ▸ Zone
|
||||
└─▸ { Sensor, Person, Object,
|
||||
Observation, Track, Event }
|
||||
```
|
||||
|
||||
- `Site`, `Building`, `Floor`, `Space` are new containment `WorldNode`
|
||||
variants above the existing `Room` (mapped to `Space`, keeping its `area_id`
|
||||
and `bounds_enu`) and `Zone`. `Wall`/`Doorway` remain as topological
|
||||
elements. Containment reuses the existing `LocatedIn`/`AdjacentTo` edge
|
||||
vocabulary; a new `PartOf` edge expresses the pure hierarchy
|
||||
(Zone `PartOf` Space `PartOf` Floor …).
|
||||
- `Sensor` is the entity **ADR-305** authenticates (`DeviceId` as its stable
|
||||
identity) and **ADR-320** (HAL, phase 2) describes the hardware of. `Person`,
|
||||
`Object`, `Observation`, `Track`, and `Event` are first-class nodes.
|
||||
`Observes`/`LocatedIn` edges already carry quality and dwell timestamps.
|
||||
- `Track` and `Person` are defined **here** as the ontology contract that
|
||||
**ADR-307** (persistent tracking) produces and updates. `Observation` is what
|
||||
an authenticated frame (ADR-305) becomes after calibration (ADR-301), and
|
||||
`Event` is the governed output that ADR-318 certifies and ADR-319 witnesses.
|
||||
|
||||
### 2. Canonical serialization
|
||||
|
||||
- A single, versioned serialization (serde-based, stable field names) is the
|
||||
one wire/at-rest representation. Every surface — MQTT/Home-Assistant, REST,
|
||||
WebSocket, RuField observations, Matter/HomeKit, agent queries — is a
|
||||
**projection** of this model, not an independent schema. `NodeInference` and
|
||||
`RoomInference` (ADR-297) become projections of `Sensor→Observes` and the
|
||||
`Space`-level fused inference respectively, so ADR-297's node/room separation
|
||||
is preserved by construction rather than re-encoded per surface.
|
||||
- Every node and edge carries `SemanticProvenance` and exactly one
|
||||
`EvidenceLevel` (L0–L5, ADR-282 policy): the evidence ladder travels *with*
|
||||
the fact across every projection, so no surface can silently upgrade or drop
|
||||
it.
|
||||
|
||||
### 3. Migration path
|
||||
|
||||
- Each existing per-surface schema gets a documented, tested bidirectional
|
||||
mapping to/from the canonical model, plus a migration accessor for consumers
|
||||
reading the old shape (mirroring ADR-297's migration accessor). Surfaces are
|
||||
cut over one at a time; a surface is "canonical" once its projection is the
|
||||
only encoder it uses. Until cutover, the mapping layer is authoritative and
|
||||
round-trip-tested so no fact is lost in translation.
|
||||
- The `worldgraph` HomeCore `area_id` linkage (ADR-127) remains the join key
|
||||
between the ontology's `Space` and external area registries.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The translation matrix collapses from O(surfaces²) to O(surfaces): each
|
||||
surface implements one projection. New surfaces (ROS 2, OpenUSD, OPC UA per
|
||||
ADR-282's roadmap) plug in as additional projections.
|
||||
- Provenance and evidence level are carried uniformly; a fact cannot cross a
|
||||
surface boundary and lose its lineage or its L-level.
|
||||
- A schema change reaching every surface; managed by the versioned
|
||||
serialization and per-surface migration accessors. Single-node deployments
|
||||
keep working (one `Sensor`, one `Space`).
|
||||
- The ontology is a *representation*, not an inference engine: it says nothing
|
||||
about *how* a `Track` or `Event` is produced — that is owned by ADR-307,
|
||||
ADR-301, ADR-302, and the model layer. This ADR does not itself make any
|
||||
accuracy claim to grade.
|
||||
- Extending `worldgraph` grows one crate's surface rather than forking a second
|
||||
world model; the geo/worldmodel sub-crates continue to build on the same node
|
||||
vocabulary.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests (`cargo test -p wifi-densepose-worldgraph`): containment-spine
|
||||
construction and invariants (a `Zone` is `PartOf` exactly one `Space`, a
|
||||
`Space` on exactly one `Floor`, etc.); round-trip serialization of every node
|
||||
and edge type; every node/edge carries exactly one `EvidenceLevel`.
|
||||
- Migration tests: each per-surface schema maps to the canonical model and back
|
||||
with no loss of provenance or evidence level; `NodeInference`/`RoomInference`
|
||||
(ADR-297) project and re-project identically.
|
||||
- Contract test: a single canonical `Event` renders correctly through the MQTT,
|
||||
REST, and WebSocket projections from one source of truth.
|
||||
- No accuracy numbers are claimed; this ADR delivers the shared representation
|
||||
the rest of the phase-1 spine writes into.
|
||||
@@ -1,135 +0,0 @@
|
||||
# ADR-307: Persistent identity & tracking — privacy-preserving probabilistic tracks
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 2)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: tracking, identity, privacy, fusion, worldgraph, phase-2
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** and owns primitive #7, *persistent identity
|
||||
& tracking*. In the ADR-300 DAG it is a phase-2 primitive sitting on the
|
||||
phase-1 spine: it **consumes the ADR-306 ontology** (producing and updating the
|
||||
`Track` and `Person` node types defined there), it relies on **ADR-305**
|
||||
authenticated identity so that the observations it associates have a verified
|
||||
origin, and its outputs are governed `Event`s that ADR-318/ADR-319 can certify
|
||||
and witness.
|
||||
|
||||
The product need is to reason about *persistent entities* — "person_7 entered
|
||||
the kitchen, then the hallway, then the bedroom" — across radios, modalities,
|
||||
rooms, and time. The hard constraint is that this must happen **without
|
||||
establishing civil identity**. RuView is camera-free (ADR-282), and a
|
||||
persistent pseudonymous track must never become, or be joinable to, a real-
|
||||
world named individual. This is a privacy property to be enforced *by
|
||||
construction*, not a policy footnote.
|
||||
|
||||
Substantial scaffolding already exists in
|
||||
`v2/crates/wifi-densepose-mat/src/tracking` and must be **reused/extended, not
|
||||
rebuilt**:
|
||||
|
||||
- `SurvivorTracker`, `TrackedSurvivor`, `TrackId`, `TrackerConfig`,
|
||||
`TrackLifecycle`, and `TrackState` — a multi-target tracker with lifecycle
|
||||
(tentative/active/lost/terminal) and a `TrackId` backed by a UUID
|
||||
(`as_uuid`).
|
||||
- `KalmanState` with `predict`/`update`, `position`, `velocity`,
|
||||
`position_uncertainty`, and `mahalanobis_distance_sq` — the motion model and
|
||||
gating distance.
|
||||
- `CsiFingerprint`, `DetectionObservation`, `AssociationResult`, and the
|
||||
`can_reidentify`/`matches`/`mark_rescued`/`rescue` re-identification surface —
|
||||
the appearance/fingerprint channel for track continuity.
|
||||
|
||||
What is missing is (a) continuity **across radios, modalities, and rooms** (the
|
||||
tracker today reasons within a node/room context), (b) a **persistent** entity
|
||||
that survives track loss and hand-off between spaces, and (c) an explicit
|
||||
**privacy boundary** that guarantees no civil-identity binding.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Per-room independent trackers, no cross-room identity.** Rejected: cannot
|
||||
express "person_7 moved kitchen → hallway → bedroom"; loses the entity at
|
||||
every room boundary.
|
||||
2. **Global identity keyed on a strong biometric fingerprint.** Rejected: a
|
||||
fingerprint strong enough to re-identify across long gaps trends toward a
|
||||
civil-identity-grade biometric — exactly what the privacy constraint
|
||||
forbids.
|
||||
3. **Probabilistic persistent tracks with bounded, decaying pseudonymous
|
||||
association, built on the existing MAT tracker.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Extend `wifi-densepose-mat/tracking` into a **cross-domain persistent track
|
||||
layer** that produces ADR-306 `Track`/`Person` nodes.
|
||||
|
||||
### 1. Persistent probabilistic entity
|
||||
|
||||
- A persistent entity is a pseudonymous `Person` node (ADR-306) with a stable
|
||||
synthetic id (e.g. `person_7`) backed by the existing `TrackId`/UUID. It
|
||||
aggregates one or more `SurvivorTracker` tracks over time and space and holds
|
||||
a **probabilistic** continuity belief — association is never asserted as
|
||||
certain, and every hand-off carries a confidence.
|
||||
- Continuity across a track-loss gap reuses the existing re-identification
|
||||
surface (`can_reidentify`, `CsiFingerprint`, `AssociationResult`), extended
|
||||
with a **time- and distance-decayed** association prior so that confidence in
|
||||
"same entity" falls with the size of the gap. Beyond a bounded horizon the
|
||||
association is dropped and a new pseudonym is minted rather than forcing a
|
||||
join — under-linking is the privacy-safe failure mode.
|
||||
|
||||
### 2. Cross-radio / cross-modality / cross-room continuity
|
||||
|
||||
- Association operates over the ADR-306 ontology graph: `Observes` edges from
|
||||
multiple `Sensor`s and `AdjacentTo`/`Doorway` topology constrain plausible
|
||||
hand-offs (a person can only move between adjacent spaces). The existing
|
||||
`mahalanobis_distance_sq` gating extends to a fused observation across
|
||||
modalities rather than a single node's detections.
|
||||
- Fusion here is track-level association; the underlying multi-modality fusion
|
||||
(radar/mmWave per ADR-063, multistatic per ADR-029, and real sensor fusion
|
||||
per ADR-311) supplies the observations. This ADR depends on those for the raw
|
||||
cross-modality evidence and does not re-implement sensor fusion.
|
||||
|
||||
### 3. Privacy boundary (by construction)
|
||||
|
||||
- **No civil-identity binding.** The persistent id is a synthetic pseudonym
|
||||
with no field, edge, or join key to any name, account, phone, MAC, or other
|
||||
civil identifier. The type carries no such field, so binding is impossible in
|
||||
the schema, not merely discouraged.
|
||||
- The `CsiFingerprint` used for re-identification is **bounded and decaying**:
|
||||
it is scoped to short-horizon continuity, is not persisted as a long-term
|
||||
biometric template, and expires. This keeps re-identification useful for
|
||||
"same person across the hallway" while structurally unable to serve "this is
|
||||
the same person who visited last month."
|
||||
- Every `Track`/`Person`/`Event` produced carries `SemanticProvenance` and an
|
||||
`EvidenceLevel` (ADR-282), and honors the ADR-277/ADR-280 edge governance and
|
||||
ADR-141 attestation — a pseudonymous track is still governed P-class data.
|
||||
Tracking accuracy is a per-domain claim to be tagged MEASURED/CLAIMED/
|
||||
SYNTHETIC with a reproducer; **this ADR claims no accuracy number.**
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView can express persistent, cross-room trajectories for automation and
|
||||
analytics while remaining camera-free and civil-identity-free.
|
||||
- The privacy-safe failure mode is **under-linking** (mint a fresh pseudonym
|
||||
when unsure), which will fragment a trajectory across long gaps or sparse
|
||||
coverage. This is a deliberate trade: a fragmented pseudonym is safe, a
|
||||
wrong civil-identity join is not.
|
||||
- Extends an existing tracker rather than forking one; single-room single-radio
|
||||
deployments keep the current behavior (one entity = one track).
|
||||
- Cross-modality quality depends on ADR-311/ADR-063/ADR-029 landing; until then
|
||||
continuity is WiFi-primary and its limits are stated, not hidden.
|
||||
- Being phase 2, this ADR is design intent; it will be revised as the ADR-306
|
||||
ontology and ADR-305 identity spine finalize.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests (`cargo test -p wifi-densepose-mat`): decayed association prior
|
||||
(confidence falls with gap; drops beyond horizon → new pseudonym);
|
||||
topology-constrained hand-off (no association across non-adjacent spaces);
|
||||
schema check that a `Person`/`Track` carries no civil-identifier field.
|
||||
- Integration test against a synthetic multi-room, multi-radio scenario:
|
||||
a scripted walk kitchen → hallway → bedroom yields one persistent pseudonym
|
||||
with per-hand-off confidence, and a deliberately ambiguous crossing produces
|
||||
two pseudonyms rather than a false join.
|
||||
- Evidence discipline: any tracking-continuity accuracy is reported only with
|
||||
the ADR-291 leakage-free protocol and an evidence tag; no number is asserted
|
||||
here.
|
||||
- Privacy review: confirm no persisted long-term biometric template and no
|
||||
civil-identity join path, as an explicit checklist item before any pilot.
|
||||
@@ -1,138 +0,0 @@
|
||||
# ADR-308: Sensor placement optimizer — floorplan + inventory → recommended positions
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 3)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: placement, planning, rf-twin, coverage, worldgraph, phase-3
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** and owns primitive #8, *sensor placement
|
||||
optimizer*. In the ADR-300 DAG it is a phase-3, research-forward primitive that
|
||||
sits on top of the fused world state and is tightly coupled to **ADR-315**
|
||||
(digital RF twin): the twin provides the propagation simulation this optimizer
|
||||
plans against. It reads the **ADR-306** canonical ontology for the physical
|
||||
scene and, after install, compares its predictions against ADR-302 observability
|
||||
and the ADR-318 capability certificate.
|
||||
|
||||
The problem it solves is the single most common cause of a bad RuView
|
||||
deployment: sensors placed by guesswork. Whether a room can be reliably sensed
|
||||
depends on AP/sensor geometry relative to walls, Fresnel-zone clearance,
|
||||
multipath structure, and where people actually move. Today an installer has no
|
||||
principled way to answer "where do I put the two nodes I have so the kitchen is
|
||||
observable?" — and no way, after install, to know whether reality matched the
|
||||
plan. This is a genuine **differentiator**: it turns RuView from "sense
|
||||
whatever the given placement happens to allow" into "recommend the placement
|
||||
that makes the requested sensing feasible."
|
||||
|
||||
Relevant existing assets to build on rather than duplicate:
|
||||
|
||||
- The `worldgraph` crate models the physical scene the optimizer plans over:
|
||||
`Room`/`Space` with `bounds_enu`, `Wall { rf_attenuation_db }` (drywall ≈ 3
|
||||
dB, brick ≈ 12 dB), `Doorway`, and `Zone` — enough geometry and coarse RF
|
||||
attenuation to seed a coverage model, plus `Sensor` nodes (ADR-306) for
|
||||
candidate positions.
|
||||
- **ADR-315** (RF twin, phase 3) is the propagation/multipath simulator; this
|
||||
optimizer is a *consumer* of the twin, not a second simulator.
|
||||
- **ADR-302** (OOD/observability) and **ADR-318** (capability certificate)
|
||||
define what "reliably sense the requested phenomenon" means, so the optimizer
|
||||
can optimize against the same observability metric the runtime later gates on.
|
||||
- **ADR-029** (multistatic) and **ADR-063** (mmWave fusion) inform which link
|
||||
geometries are useful for which phenomena.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Static placement guidelines in docs (e.g. "one node per room, opposite
|
||||
the door").** Rejected: ignores the specific floorplan, wall materials, and
|
||||
the actual hardware inventory; gives no uncertainty and no post-install
|
||||
feedback.
|
||||
2. **Full electromagnetic solver per site.** Rejected for the default path:
|
||||
too heavy for an installer workflow and overkill relative to the coarse
|
||||
`rf_attenuation_db` scene RuView actually has; reserved as an optional
|
||||
high-fidelity backend inside ADR-315.
|
||||
3. **A coverage optimizer that consumes the ADR-315 RF twin over the ADR-306
|
||||
scene, then validates predicted vs. measured observability after install.**
|
||||
Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Define a **placement optimizer** that takes a floor plan (ADR-306 scene) and a
|
||||
hardware inventory and recommends sensor positions, then closes the loop after
|
||||
install.
|
||||
|
||||
### 1. Inputs
|
||||
|
||||
- The ADR-306 canonical scene: `Space`/`Zone` bounds, `Wall` segments with
|
||||
`rf_attenuation_db`, `Doorway` topology, and any already-placed `Sensor`
|
||||
nodes.
|
||||
- A hardware inventory: the count and type of available radios (ESP32-S3/C6
|
||||
nodes, mmWave, adapters) with their capability envelopes (what each can
|
||||
sense, per ADR-318 / ADR-320 HAL descriptors).
|
||||
- A sensing objective: which phenomenon must be observable in which
|
||||
`Space`/`Zone` (presence, vitals, pose), expressed against the ADR-302
|
||||
observability metric.
|
||||
|
||||
### 2. Prediction
|
||||
|
||||
- For a candidate placement, query the **ADR-315 RF twin** for simulated RF
|
||||
coverage: path loss through `Wall` attenuation, **Fresnel-zone clearance**
|
||||
between link endpoints, and coarse **multipath** structure. From that derive
|
||||
an **expected observability** and an **uncertainty** for each objective in
|
||||
each space — reusing the same observability definition ADR-302 gates on so the
|
||||
plan and the runtime speak one language.
|
||||
- Search over candidate positions (the inventory bounds the count; the scene
|
||||
bounds the geometry) to recommend the placement that maximizes objective
|
||||
observability, reporting expected observability **and its uncertainty** per
|
||||
space — never a single confident number for a simulated result.
|
||||
|
||||
### 3. Post-install loop
|
||||
|
||||
- After install, compare **predicted vs. measured** observability using the
|
||||
ADR-302 runtime observability signal from the freshly enrolled (ADR-305),
|
||||
calibrated (ADR-301) sensors. Where measurement disagrees with prediction,
|
||||
recommend adjustments (move, re-aim, add a node) and feed the residual back
|
||||
to improve the ADR-315 twin's scene parameters (e.g. a wall's effective
|
||||
attenuation).
|
||||
|
||||
### Evidence discipline
|
||||
|
||||
- Predicted coverage is a **simulation** (evidence level L0 per ADR-282) and is
|
||||
labelled `SYNTHETIC`; it is a *recommendation*, never a sensing claim.
|
||||
- The predicted-vs-measured comparison is the only place a `MEASURED` statement
|
||||
appears, and only with a reproducer and real-silicon observability data
|
||||
(CLAUDE.md hardware rule). The optimizer never presents a simulated coverage
|
||||
map as evidence that a room *is* being sensed.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Installers get a principled, floorplan-specific placement plan and, crucially,
|
||||
a post-install check that says whether reality matched the plan — a
|
||||
differentiating capability over guess-and-check deployment.
|
||||
- Quality is bounded by the fidelity of the ADR-315 RF twin and the coarseness
|
||||
of the `worldgraph` scene (2D walls, coarse attenuation). The optimizer
|
||||
reports uncertainty rather than overstating a coarse model; higher fidelity
|
||||
is an ADR-315 concern.
|
||||
- Hard dependency on ADR-315 (twin), ADR-302 (observability metric), and
|
||||
ADR-306 (scene); this ADR does not build a simulator or an observability
|
||||
metric of its own.
|
||||
- Being phase 3, this is design intent sitting on the fused world state; it is
|
||||
expected to be revised as ADR-315 and the phase-1 spine land.
|
||||
- No claim that recommended placement *guarantees* sensing — it maximizes
|
||||
modelled observability subject to inventory and geometry, with explicit
|
||||
uncertainty.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests: coverage/observability prediction is a deterministic function of
|
||||
scene + placement + twin parameters; Fresnel-zone and wall-attenuation math
|
||||
against known analytic cases; search returns the modelled-optimal placement on
|
||||
small synthetic scenes.
|
||||
- Integration test: on a synthetic floorplan with a known-good and a
|
||||
known-bad placement, the optimizer ranks them correctly and reports higher
|
||||
uncertainty for the marginal case.
|
||||
- Post-install loop test: injected predicted-vs-measured disagreement produces a
|
||||
sensible adjustment recommendation and a twin-parameter residual.
|
||||
- Field validation (deferred, real-silicon): predicted vs. measured
|
||||
observability on an instrumented real site, reported as `MEASURED` with a
|
||||
reproducer. Until then all coverage output is `SYNTHETIC`/L0. No coverage or
|
||||
accuracy number is asserted by this ADR.
|
||||
@@ -1,152 +0,0 @@
|
||||
# ADR-309: Active sensing — closed-loop RF experiment control
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 3)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: active-sensing, control-plane, closed-loop, information-gain, actuation, phase-3
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** and owns primitive #9, *active sensing*. In
|
||||
the ADR-300 phasing it is a phase-3 primitive that sits on top of the fused
|
||||
world state produced by **ADR-311** (real sensor fusion) and is driven by the
|
||||
information budget of **ADR-314** (information-gain scheduler). It is authored
|
||||
as **Proposed**: design intent and validation plan, not a phase-1 build.
|
||||
|
||||
The default posture of every current RuView path is **passive**: RF traffic
|
||||
happens for its own reasons (a device transmits, a beacon fires), RuView
|
||||
observes whatever CSI/CIR arrives, and the pipeline extracts what it can from
|
||||
that incidental signal. The strategic assessment behind ADR-300 named the next
|
||||
step: move from *RF-happens → observe* to **RuView-controls-RF → observe the
|
||||
response → optimize the next measurement**. That turns sensing into a
|
||||
closed-loop experiment — the system chooses what to measure to resolve the
|
||||
uncertainty it currently has, rather than accepting the measurements the
|
||||
environment happens to offer.
|
||||
|
||||
Substantial control-plane scaffolding already exists and must be
|
||||
**reused/extended, not rebuilt**:
|
||||
|
||||
- **ADR-280** (active sensing / programmable perception, *implemented* in
|
||||
`ruview-unified/src/control.rs`) already defines the governed control surface
|
||||
this ADR closes the loop over: `SensingTask` (evidence-aware, fail-closed
|
||||
admission), `SensingAction` + `InformationGoal` (a deliberate act of
|
||||
evidence-gathering against a stated hypothesis, bounded by a `PrivacyClass`
|
||||
P0–P5 ceiling), `ActiveSensingPlanner` (age-of-information scheduler),
|
||||
`CoherentSensorGroup` (coherent fusion fails closed), and `request_actuation`
|
||||
→ `ActuationReceipt` for governed RIS/movable/fluid-antenna actuation.
|
||||
- ADR-280 explicitly recorded that **information-gain *estimation* is not
|
||||
implemented** — "the planner uses staleness heuristics, not mutual
|
||||
information; RIS drivers, actual multi-AP coherence measurement, and OTFS
|
||||
waveform control are hardware-dependent roadmap items." ADR-309 is the ADR
|
||||
that closes exactly those gaps, in coordination with ADR-314.
|
||||
|
||||
The missing piece is not the actuation surface — ADR-280 built that and made it
|
||||
fail closed — but the **loop**: a controller that reads the current fused-state
|
||||
uncertainty, selects a *controllable measurement configuration* expected to
|
||||
reduce it most, requests it through the ADR-280 governed surface, observes the
|
||||
response, and updates its belief before choosing the next measurement.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Stay passive; only schedule which incidental observations to keep.** This
|
||||
is roughly today's `ActiveSensingPlanner` (staleness-priority over regions).
|
||||
Rejected as the endpoint: it optimizes *attention* over uncontrolled RF, not
|
||||
the *measurement* itself. It remains the fallback when nothing is
|
||||
controllable.
|
||||
2. **Open-loop measurement scripting** (a fixed sweep of channels/bandwidths).
|
||||
Rejected: a fixed sweep spends the RF/energy/privacy budget the same way
|
||||
regardless of what is already known; it cannot concentrate measurement where
|
||||
uncertainty actually is.
|
||||
3. **Closed-loop experiment control** — read uncertainty, pick the controllable
|
||||
configuration with highest expected information gain per unit cost/privacy,
|
||||
actuate through the ADR-280 governed surface, observe, update, repeat.
|
||||
Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt **closed-loop RF experiment control** as a phase-3 controller layered on
|
||||
the ADR-280 surface. RuView selects and drives the controllable degrees of
|
||||
freedom of the RF measurement, then optimizes the next measurement from the
|
||||
observed response.
|
||||
|
||||
### 1. Controllable degrees of freedom
|
||||
|
||||
Define an `ExperimentControl` vocabulary over the configuration axes RuView can
|
||||
influence on hardware that exposes them (each axis is optional and
|
||||
capability-gated by ADR-320's HAL, so an ESP32-only deployment simply has an
|
||||
empty controllable set and degrades to the passive planner):
|
||||
|
||||
- **Channel / band** and **bandwidth** (which spectrum to probe; reuses the
|
||||
ADR-292 wideband subcarrier-agnostic metadata).
|
||||
- **Packet timing / cadence** (when to solicit a sounding, and at what rate).
|
||||
- **Antenna / chain selection** (which subset of a distributed aperture to
|
||||
activate — bounded by the ADR-280 `CoherentSensorGroup` compatibility proof).
|
||||
- **Beam / RIS configuration** (which rooms and people become observable —
|
||||
governed exactly as ADR-280 §6 requires, via `request_actuation` and an
|
||||
`ActuationReceipt`).
|
||||
- **802.11bf measurement parameters** (TB/non-TB, reporting config) once
|
||||
ADR-310 exposes standardized sensing as a native measurement type.
|
||||
|
||||
### 2. The loop
|
||||
|
||||
```
|
||||
fused-state uncertainty (ADR-311)
|
||||
│
|
||||
▼
|
||||
info-gain ranking of ExperimentControl options (ADR-314)
|
||||
│ select argmax E[ΔI] / (cost, energy, privacy ceiling)
|
||||
▼
|
||||
governed request (ADR-280 admit_task / request_actuation, fail-closed)
|
||||
│
|
||||
▼
|
||||
observe response → update belief (ADR-311) → repeat
|
||||
```
|
||||
|
||||
The controller never bypasses the ADR-280 admission and actuation gates: every
|
||||
solicited measurement is a `SensingTask`/`SensingAction`, every environment
|
||||
change is an `ActuationReceipt`, and every step composes with the ADR-277
|
||||
policy engine. Information gain is what **ADR-314** supplies (the mutual-
|
||||
information estimate ADR-280 deferred); ADR-309 owns the *control loop* that
|
||||
consumes that estimate and drives the hardware.
|
||||
|
||||
### 3. Governance and honesty boundary
|
||||
|
||||
- Actuation and solicitation stay fail-closed and privacy-ceilinged: a
|
||||
closed-loop experiment cannot widen the P0–P5 ceiling of the task it serves,
|
||||
and cannot steer a beam into a zone that does not grant the purpose (ADR-280
|
||||
`actuation_requires_policy_authorization`).
|
||||
- Any accuracy or "traffic-reduction" claim from the closed loop is tagged
|
||||
**MEASURED** only with a named reproducer over a stated scenario, **SYNTHETIC**
|
||||
for simulated apertures, and **CLAIMED** otherwise. Real multi-AP coherent
|
||||
measurement and RIS actuation remain **hardware-dependent** and require
|
||||
real-silicon evidence (a captured runtime log) before any hardware claim, per
|
||||
CLAUDE.md. No number is invented here.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Sensing becomes an experiment: RuView spends its RF/energy/privacy budget on
|
||||
the measurements that most reduce current uncertainty, instead of processing
|
||||
whatever incidental traffic arrives.
|
||||
- The loop is only as strong as its two dependencies: ADR-311 must expose a
|
||||
usable uncertainty surface and ADR-314 must produce trustworthy information-
|
||||
gain estimates. Where either is absent, the controller degrades to the
|
||||
ADR-280 staleness planner rather than acting on a fabricated gain estimate.
|
||||
- Controllability is hardware-bounded. On commodity ESP32 sensors the
|
||||
controllable set may be limited to cadence; the full loop (bandwidth, antenna,
|
||||
beam) needs NICs/RIS that expose those axes, surfaced through ADR-320.
|
||||
- This ADR adds a controller; it does not re-open ADR-280's raw-export or
|
||||
actuation-governance decisions, which remain authoritative and fail-closed.
|
||||
|
||||
## Validation
|
||||
|
||||
- Design-level acceptance (phase 3): a simulated closed loop over a synthetic
|
||||
scene reduces terminal fused-state uncertainty faster than (a) the passive
|
||||
ADR-280 staleness planner and (b) an open-loop fixed sweep, at equal
|
||||
measurement budget — reported **SYNTHETIC**, with the scenario and seed named.
|
||||
- Governance tests: every solicited measurement and actuation in the loop is
|
||||
admitted through the ADR-280 fail-closed path; a loop step that would exceed
|
||||
the task's privacy ceiling or steer into an ungranted zone is denied.
|
||||
- Degradation test: with an empty controllable set (ESP32-only), the controller
|
||||
falls back to the staleness planner with no error and no fabricated gain.
|
||||
- Hardware validation of bandwidth/antenna/beam actuation is explicitly out of
|
||||
scope until real silicon exposes those axes and produces a captured log.
|
||||
@@ -1,147 +0,0 @@
|
||||
# ADR-310: 802.11bf-native architecture — standardized WLAN sensing as native measurement types
|
||||
|
||||
- **Status**: Proposed (ADR-300 phase 2)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: 80211bf, wlan-sensing, standards, measurement-types, hal, phase-2
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** and owns primitive #10, *802.11bf-native
|
||||
architecture*. In the ADR-300 phasing it is a phase-2 integration primitive: it
|
||||
sits on the phase-1 spine (authenticated identity ADR-305, spatial ontology
|
||||
ADR-306, evidence engine ADR-304) and **feeds ADR-320** (the RuView sensor HAL),
|
||||
which is the clause of the acceptance test that "identifies the hardware." It is
|
||||
authored as **Proposed**.
|
||||
|
||||
**IEEE 802.11bf-2025 ("WLAN Sensing") was published 2025-09-26** — verified
|
||||
against the IEEE SA record in `wifi-densepose-hardware` (`ieee80211bf/mod.rs`
|
||||
header, "evidence grade MEASURED", ADR-152 §1.1). Standardization is complete
|
||||
for sub-7 GHz and >45 GHz (DMG) bands: formal sensing measurement setup,
|
||||
measurement instances, feedback/reporting, and sensing-by-proxy (SBP). This
|
||||
changes RuView's strategic frame: rather than treating every WiFi measurement as
|
||||
an *opportunistic* extraction from incidental traffic, RuView can be the **open
|
||||
reference sensing stack around the standard** — the day commodity silicon
|
||||
exposes it.
|
||||
|
||||
Substantial scaffolding already exists and must be **reused/extended, not
|
||||
rebuilt**. `v2/crates/wifi-densepose-hardware/src/ieee80211bf/` already models
|
||||
the standardized procedure surface as forward-compatible types (ADR-152/153):
|
||||
|
||||
- `types` — `SpecProfile` version gates, `SensingRole`/`TransceiverRole`,
|
||||
`MeasurementSetupParams`, `SensingCapabilities` negotiation, and required
|
||||
`ConsentMode` governance metadata on every setup.
|
||||
- `messages` — `SensingMeasurementSetupRequest/Response`,
|
||||
`SensingMeasurementInstance`, `SensingMeasurementReport`, `CsiReportPayload`,
|
||||
`SbpRequest/Response`, `SensingSessionTermination`.
|
||||
- `session` — a deterministic FSM (`Idle → SetupNegotiating → Active →
|
||||
Terminating → Idle`) with rejection paths, single-role enforcement, and SBP
|
||||
proxy mode; `table` (responder-side setup registry); `transport` (the
|
||||
`SensingTransport` seam, a `SimTransport` test double, and an
|
||||
`OpportunisticCsiBridge` that maps today's opportunistic CSI onto the
|
||||
standardized report path).
|
||||
|
||||
The module's own honesty note is authoritative and carried forward here: it is
|
||||
**not a certified 802.11bf implementation**, and **no commodity silicon — ESP32
|
||||
included — implements the standard yet**; the OTA frame binding lands when a
|
||||
chipset exposes it. Wideband ingest plumbing is already in place too: **ADR-292**
|
||||
(FeitCSI/AX210) carries native subcarrier dimensionality end-to-end and records
|
||||
the native→pipeline mapping, and noted that "truncated CIR is a natural
|
||||
extension of the same plumbing."
|
||||
|
||||
What is missing is architectural, not protocol scaffolding: normalized CSI is
|
||||
still treated as *the* WiFi input. The standardized sensing measurements
|
||||
(TB/non-TB soundings, truncated CIR / PDP reports) are modeled as protocol
|
||||
messages but are **not yet first-class native measurement types** that flow
|
||||
through calibration (ADR-301), fusion (ADR-311), and the ontology (ADR-306) on
|
||||
equal footing with normalized CSI.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Keep 802.11bf as a protocol model only; always down-convert its reports to
|
||||
normalized CSI at ingest.** Rejected: truncated CIR/PDP carry range-resolved
|
||||
multipath structure that flattening to a CSI matrix discards; it also wastes
|
||||
the standard's native report semantics.
|
||||
2. **Fork a parallel "bf pipeline" alongside the CSI pipeline.** Rejected:
|
||||
duplicates calibration, fusion, ontology, and evidence plumbing, and re-opens
|
||||
the O(surfaces²) translation problem ADR-306 exists to close.
|
||||
3. **Promote standardized sensing measurements to native measurement types
|
||||
inside the existing pipeline**, with normalized CSI as one measurement type
|
||||
among several. Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt an **802.11bf-native architecture**: standardized WLAN sensing
|
||||
measurements become **additional native measurement types**, alongside — not
|
||||
replacing — normalized CSI.
|
||||
|
||||
### 1. Native measurement types
|
||||
|
||||
- Define the standardized reports the `ieee80211bf` module already models
|
||||
(TB and non-TB soundings; truncated CIR; PDP) as first-class
|
||||
`MeasurementType` variants that the pipeline carries end-to-end, each tagged
|
||||
with its `SpecProfile` and band. Normalized CSI remains one such type; the
|
||||
`OpportunisticCsiBridge` remains the path for silicon that only offers
|
||||
incidental CSI.
|
||||
- Truncated CIR/PDP reuse the **ADR-292** subcarrier-agnostic / native-
|
||||
dimensionality plumbing (truncated CIR is the stated natural extension); the
|
||||
native→pipeline mapping is recorded in frame metadata so downstream stages
|
||||
know the true range/spectral resolution of a bf report vs. an interpolated CSI
|
||||
frame.
|
||||
|
||||
### 2. Ontology and governance binding
|
||||
|
||||
- Each standardized measurement becomes an ADR-306 `Observation` node from an
|
||||
ADR-305-authenticated `Sensor`, carrying `SemanticProvenance` and exactly one
|
||||
`EvidenceLevel` (L0–L5, ADR-282). The `ieee80211bf` `ConsentMode` metadata —
|
||||
required on every setup — composes with the ADR-277 policy engine, so a
|
||||
standardized session is admitted under the same governance as any other
|
||||
sensing task (ADR-280).
|
||||
- SBP (sensing-by-proxy) sessions attribute the report to the proxying and the
|
||||
sensing entities distinctly, so provenance is not laundered through the proxy.
|
||||
|
||||
### 3. HAL feed (ADR-320)
|
||||
|
||||
- The capability set a device advertises — which `MeasurementType`s, bands,
|
||||
bandwidths, roles, and `SpecProfile` it supports — is exactly the descriptor
|
||||
**ADR-320** (HAL) needs to "identify the hardware." ADR-310 defines that
|
||||
capability descriptor as the projection of `SensingCapabilities`; ADR-320
|
||||
consumes it. A device that implements no bf profile advertises only the
|
||||
opportunistic-CSI capability.
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView is positioned as the open reference stack *around* the standard: when a
|
||||
chipset exposes 802.11bf, its native reports flow through calibration, fusion,
|
||||
ontology, and evidence with no bespoke pipeline — the plumbing is already
|
||||
tested against `SimTransport` and synthetic fixtures.
|
||||
- Normalized CSI is demoted from "the WiFi input" to "one measurement type,"
|
||||
which is the correct framing for a multi-measurement future and prevents the
|
||||
bf path from being a second-class citizen.
|
||||
- **No hardware claim is made or implied.** No commodity silicon implements
|
||||
802.11bf yet; this ADR wires the *types and flow*, tested in simulation. Any
|
||||
OTA/native-report accuracy claim requires real silicon evidence (a captured
|
||||
log) per CLAUDE.md, and any wideband number must be tagged with the capture
|
||||
hardware (ADR-292). No benchmark number is invented here.
|
||||
- This ADR does not re-open ADR-152/153's decision to avoid OTA frame binding
|
||||
until silicon exists; it consumes that surface and adds the pipeline
|
||||
integration.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test -p wifi-densepose-hardware` — existing `ieee80211bf` FSM,
|
||||
table, and transport tests continue to pass; new tests assert that a
|
||||
`SensingMeasurementReport` (TB and non-TB) and a truncated-CIR/PDP report
|
||||
round-trip through the pipeline as native `MeasurementType`s.
|
||||
- `cargo test -p wifi-densepose-mat` — truncated CIR ingest reuses the ADR-292
|
||||
subcarrier-agnostic path and records the native→pipeline mapping; dimension/
|
||||
version validation on standardized reports mirrors the FeitCSI parser gates.
|
||||
- Ontology/governance tests: each standardized measurement becomes an ADR-306
|
||||
`Observation` from an ADR-305-authenticated `Sensor` with one `EvidenceLevel`;
|
||||
`ConsentMode` composes with ADR-277 admission; SBP attributes proxy vs. sensor
|
||||
provenance distinctly.
|
||||
- HAL contract test: the ADR-320 capability descriptor is derivable from
|
||||
`SensingCapabilities`; a bf-less device advertises only opportunistic CSI.
|
||||
- All measurement-type flows are simulation-tested (`SimTransport`, synthetic
|
||||
fixtures); OTA binding and any hardware accuracy claim remain out of scope
|
||||
until real silicon exposes the standard.
|
||||
@@ -1,140 +0,0 @@
|
||||
# ADR-311: Real sensor fusion — uncertainty-aware, multiple observations → one world state
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 2)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: fusion, uncertainty, multimodal, world-state, ontology, phase-2
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** and owns primitive #11, *real sensor fusion*.
|
||||
In the ADR-300 DAG it is a phase-2 integration primitive: it **consumes ADR-306**
|
||||
(canonical spatial ontology) and **produces the single fused world state** that
|
||||
the phase-3 primitives build on — **ADR-312** (long-term spatial memory),
|
||||
**ADR-313** (counterfactual inference), and **ADR-315** (digital RF twin). It is
|
||||
authored as **Proposed**.
|
||||
|
||||
The defining invariant is not "support more modalities" but the *shape of the
|
||||
output*: **multiple observations must resolve to one probabilistic world state,
|
||||
not many feeds into a visualization.** A dashboard that shows a WiFi layer, a
|
||||
mmWave layer, and a BLE layer side by side is not fusion; it pushes the
|
||||
reconciliation onto the human. Real fusion produces one uncertainty-aware state
|
||||
that every downstream consumer reads, with each contributing observation's
|
||||
provenance and confidence still recoverable.
|
||||
|
||||
Substantial scaffolding already exists and must be **reused/extended, not
|
||||
rebuilt**:
|
||||
|
||||
- **ADR-063** (60 GHz mmWave ↔ WiFi CSI fusion, *Proposed*) established the
|
||||
first cross-modal fusion case: pairing noisy CSI-derived vitals with clinical-
|
||||
grade mmWave FMCW radar (Seeed MR60BHA2 over UART, with a **live hardware
|
||||
capture** logged on 2026-03-15). ADR-311 generalizes that pairwise case into
|
||||
an N-modality, uncertainty-aware fusion.
|
||||
- **ADR-137** (fusion-engine quality scoring, *Accepted — partial*) already
|
||||
built the auditable-quality building block: it identified that the multistatic
|
||||
fusers (`wifi-densepose-signal/src/ruvsense/multistatic.rs`,
|
||||
`wifi-densepose-ruvector/src/viewpoint/fusion.rs`) discarded the evidence they
|
||||
used, and specified a single auditable record — "this fused output is
|
||||
trustworthy because X, Y, Z, but be aware of contradiction C" — with evidence
|
||||
references and contradiction flags. ADR-311 reuses that record as the
|
||||
provenance/quality carrier of the fused state.
|
||||
- **ADR-280** `CoherentSensorGroup` (fail-closed coherent fusion) and
|
||||
**ADR-306** `Observation`/`Track`/`Event` node types are the input and output
|
||||
vocabulary respectively.
|
||||
|
||||
What is missing is the **uncertainty-aware combiner across heterogeneous
|
||||
modalities**: a fusion stage that takes authenticated observations from WiFi,
|
||||
BLE, UWB, mmWave, acoustic, IMU, lidar, and cameras (only where policy permits),
|
||||
each with its own uncertainty, and emits one probabilistic `WorldState` — with
|
||||
per-observation contradiction flags, not a stack of independent feeds.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Per-modality feeds rendered together** (today's implicit model on some
|
||||
surfaces). Rejected: it is visualization, not fusion; contradictions are
|
||||
never reconciled and there is no single state to reason over.
|
||||
2. **Hard-switch "best modality wins"** (e.g., always prefer mmWave vitals over
|
||||
CSI vitals). Rejected: throws away corroborating evidence and cannot express
|
||||
*disagreement* — the very thing ADR-137's contradiction flags exist to
|
||||
surface — and degrades badly when the preferred modality is absent or OOD.
|
||||
3. **Uncertainty-weighted probabilistic fusion into one world state**, reusing
|
||||
ADR-137's auditable quality record and ADR-280's fail-closed coherence gate.
|
||||
Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt **uncertainty-aware multimodal fusion** whose invariant output is one
|
||||
probabilistic world state.
|
||||
|
||||
### 1. Inputs: authenticated, ontology-typed observations
|
||||
|
||||
- Inputs are ADR-306 `Observation` nodes from **ADR-305-authenticated** sensors.
|
||||
Supported modalities: WiFi (CSI / 802.11bf native reports via ADR-310), BLE,
|
||||
UWB, mmWave (ADR-063), acoustic, IMU, lidar, and cameras. Cameras and any
|
||||
higher privacy-class modality enter fusion **only where the ADR-277 policy
|
||||
engine permits** — camera-free coverage is a RuView invariant (ADR-282), so
|
||||
cameras are an opt-in, policy-gated input, never assumed present.
|
||||
- Each observation carries its own uncertainty and exactly one `EvidenceLevel`
|
||||
(ADR-282). An observation flagged out-of-distribution by **ADR-302** is
|
||||
down-weighted or excluded per its OOD verdict rather than silently averaged in.
|
||||
|
||||
### 2. Combiner: uncertainty-weighted, contradiction-aware
|
||||
|
||||
- Observations are combined by their uncertainty into one probabilistic
|
||||
`WorldState` over the ADR-306 entities (`Person`, `Object`, `Track`, and the
|
||||
per-`Space` inference). The combiner does **not** collapse disagreement: when
|
||||
modalities conflict beyond their stated uncertainty, the fused output carries
|
||||
ADR-137 **contradiction flags** and the evidence references that produced
|
||||
them, so a consumer can see *that* WiFi and mmWave disagree and *why*.
|
||||
- Coherent multi-node fusion inherits ADR-280's fail-closed
|
||||
`CoherentSensorGroup` gate: no coherent combination unless sync, phase, and
|
||||
geometry compatibility are proven; otherwise the group degrades to incoherent
|
||||
combination rather than producing confident nonsense.
|
||||
|
||||
### 3. Output: one world state, provenance preserved
|
||||
|
||||
- The output is a single `WorldState` written into the ADR-306 ontology, with
|
||||
every fused value retaining recoverable per-observation provenance and the
|
||||
ADR-137 quality record. This is the state ADR-312/310/312 consume; they read
|
||||
one probabilistic world, not a modality stack.
|
||||
- The fused state carries an aggregate uncertainty and an evidence level derived
|
||||
from its inputs (never upgraded above the weakest contributing L-level for a
|
||||
given claim).
|
||||
|
||||
## Consequences
|
||||
|
||||
- Downstream primitives (spatial memory, counterfactual, RF twin) build on one
|
||||
probabilistic world state with uniform uncertainty and provenance, instead of
|
||||
re-implementing reconciliation per consumer.
|
||||
- Contradictions become first-class signal, not noise: ADR-137's record means a
|
||||
disagreement between mmWave and CSI is surfaced and auditable, which is also
|
||||
what lets ADR-302 and the evidence engine (ADR-304) reason about reliability.
|
||||
- Fusion is uncertainty-honest: an OOD or low-evidence observation is
|
||||
down-weighted, not averaged in as if trustworthy; a fused claim never presents
|
||||
a stronger evidence level than its weakest necessary input.
|
||||
- **No accuracy or "camera-grade" claim is made.** ADR-063's mmWave path has a
|
||||
real-silicon capture; the multimodal combiner's accuracy is not asserted here.
|
||||
Any fused-accuracy number requires a named reproducer tagged MEASURED /
|
||||
SYNTHETIC / CLAIMED, and WiFi sensing is never presented as camera-grade
|
||||
(CLAUDE.md, ADR-282). No number is invented.
|
||||
- Cameras remain a governed, opt-in input; enabling them does not weaken the
|
||||
camera-free coverage guarantee for deployments that exclude them.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test -p wifi-densepose-ruvector` / `-p wifi-densepose-signal` — the
|
||||
ADR-137 quality record and contradiction flags travel with the fused output;
|
||||
the ADR-280 `CoherentSensorGroup` gate still fails closed under
|
||||
clock/phase/geometry violation.
|
||||
- Fusion invariant test: N modality observations over one scene resolve to a
|
||||
single `WorldState` node in the ADR-306 ontology (not N feeds), with
|
||||
per-observation provenance recoverable and one aggregate evidence level.
|
||||
- Uncertainty tests: a high-uncertainty or ADR-302-flagged-OOD observation is
|
||||
down-weighted/excluded; conflicting modalities produce a contradiction flag
|
||||
rather than a silently averaged value; the fused evidence level never exceeds
|
||||
the weakest necessary input.
|
||||
- Governance test: a camera or higher-privacy modality is admitted into fusion
|
||||
only when the ADR-277 policy engine permits; otherwise it is excluded and the
|
||||
fused state notes the exclusion.
|
||||
- Any accuracy comparison (e.g., fused vitals vs. mmWave-only) is reported with
|
||||
its evidence tag and reproducer; none is asserted in this ADR.
|
||||
@@ -1,146 +0,0 @@
|
||||
# ADR-312: Long-term spatial memory — learn the normal physics of a location
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 3)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: spatial-memory, ruvector, anomaly-detection, temporal, world-state, phase-3
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** and owns primitive #12, *long-term spatial
|
||||
memory*. In the ADR-300 phasing it is a phase-3 primitive that sits on the fused
|
||||
world state produced by **ADR-311** (real sensor fusion) and **ties to ADR-315**
|
||||
(digital RF twin): spatial memory is the *learned normal* that a twin can
|
||||
simulate against and that anomaly detection compares against. It is authored as
|
||||
**Proposed**.
|
||||
|
||||
The capability is to **learn the normal physics of a location** so anomalies
|
||||
surface *without training a detector for every anomaly*. Concretely, the system
|
||||
should learn statements like: "a chair is normally here"; "this bedroom is
|
||||
usually occupied between these hours"; "the RF propagation of this space
|
||||
changed"; "this machine's vibration signature changed"; "a new reflector
|
||||
appeared." None of these is a labeled anomaly class — they are *deviations from
|
||||
a learned baseline of normality*. This is the difference between supervised
|
||||
anomaly detection (which needs examples of every failure) and **baseline-relative
|
||||
anomaly detection** (which needs only a well-characterized normal).
|
||||
|
||||
Substantial substrate already exists and must be **reused/extended, not
|
||||
rebuilt**:
|
||||
|
||||
- **RuVector** (`v2/crates/wifi-densepose-ruvector`) is the designated substrate
|
||||
in the ADR-282 layer stack ("persistent objects, Gaussian fields, scene
|
||||
graphs, temporal memory"). It already provides the vector/temporal machinery
|
||||
this ADR needs — HNSW indexing (`hnsw.rs`, `hnsw_quantized.rs`), an event log
|
||||
(`event_log.rs`), coverage and estimator surfaces, and the `crv`/`mat`
|
||||
temporal sub-modules — so long-term spatial memory is a *consumer and
|
||||
organizer* of RuVector primitives, not a new store.
|
||||
- **ADR-306** supplies the entity vocabulary the memory is indexed by (`Space`,
|
||||
`Object`, `Sensor`, `Track`, `Event`); **ADR-311** supplies the fused,
|
||||
uncertainty-carrying `WorldState` snapshots that memory accumulates over time.
|
||||
- **ADR-135** (empty-room baseline calibration) and **ADR-301** (automatic
|
||||
domain calibration) already establish a *calibration-time* baseline of a
|
||||
space; ADR-312 extends that from a one-shot baseline to a **continuously
|
||||
learned, time-of-day-aware** model of normal.
|
||||
|
||||
What is missing is the **temporal normality model**: a per-`Space` learned
|
||||
distribution of fused world states over time (including periodicity — hour of
|
||||
day, day of week), plus RF-propagation and modality-signature baselines, against
|
||||
which a live fused state is scored for deviation.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Supervised anomaly classifiers per anomaly type.** Rejected: it needs
|
||||
labeled examples of every anomaly (fall, intrusion, machine fault, moved
|
||||
furniture), which do not exist for most spaces and do not transfer between
|
||||
rooms; it also cannot catch a *novel* anomaly it was never trained on.
|
||||
2. **Single static baseline** (the ADR-135 empty-room snapshot, used forever).
|
||||
Rejected as the endpoint: it cannot express *when* a space is normally
|
||||
occupied, cannot track slow legitimate drift (furniture rearranged on
|
||||
purpose), and flags every diurnal change as anomalous.
|
||||
3. **Continuously learned, time-aware normality model on the RuVector
|
||||
substrate**, scoring live fused state against learned normal. Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Adopt a **long-term spatial memory** that learns each location's normal physics
|
||||
on the RuVector substrate and scores live fused state against it.
|
||||
|
||||
### 1. What "normal" is learned over
|
||||
|
||||
Per ADR-306 `Space` (and the entities within it), accumulate the ADR-311 fused
|
||||
`WorldState` over time into a learned normality model covering:
|
||||
|
||||
- **Occupancy / activity periodicity** — the distribution of presence and
|
||||
activity by hour-of-day and day-of-week (the "bedroom usually occupied certain
|
||||
hours" case).
|
||||
- **Static scene layout** — persistent `Object` positions and the expected
|
||||
reflector set (the "chair normally here" / "new reflector appeared" cases),
|
||||
building on the ADR-135/298 baseline.
|
||||
- **RF-propagation baseline** — the space's normal multipath/propagation
|
||||
signature (the "RF propagation changed" case).
|
||||
- **Per-modality signatures** — e.g., a machine's normal vibration/acoustic/IMU
|
||||
signature (the "vibration signature changed" case).
|
||||
|
||||
Each learned baseline carries its own uncertainty and an `EvidenceLevel`
|
||||
(ADR-282); a baseline learned from replay is L1, from a field pilot L4, and is
|
||||
never presented above the evidence of the observations it was learned from.
|
||||
|
||||
### 2. Substrate: RuVector, temporally compressed
|
||||
|
||||
- The memory is stored and indexed on RuVector (HNSW for nearest-normal recall,
|
||||
the event log for the temporal stream, the temporal sub-modules for
|
||||
compression). Long-horizon history is temporally compressed — recent detail
|
||||
retained, older history summarized — so memory cost is bounded rather than
|
||||
growing linearly forever.
|
||||
- The memory is *keyed by* the ADR-306 ontology, so "normal for this `Space` at
|
||||
this hour" is a first-class query, and slow legitimate drift updates the
|
||||
baseline (with provenance) instead of accumulating as permanent anomaly.
|
||||
|
||||
### 3. Anomaly = deviation from learned normal
|
||||
|
||||
- A live fused `WorldState` is scored against the applicable learned baseline
|
||||
(matched by space and time context). A deviation beyond the baseline's
|
||||
uncertainty is surfaced as an ADR-306 `Event` — *without* a per-anomaly
|
||||
detector — carrying the baseline it deviated from, the deviation magnitude,
|
||||
and its evidence level. Whether that event is actionable is a policy/consumer
|
||||
decision (ADR-277), not this layer's.
|
||||
- The learned normal is exactly what **ADR-315** (RF twin) can simulate against:
|
||||
the twin proposes an expected state, spatial memory supplies the learned
|
||||
actual-normal, and their divergence is a physically grounded anomaly signal.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Anomaly detection generalizes: a space gets deviation detection from its own
|
||||
learned normal, so a novel anomaly (never labeled anywhere) still registers as
|
||||
a deviation, and the model transfers to a new room by *learning that room's*
|
||||
normal rather than importing a foreign detector.
|
||||
- Bounded memory: temporal compression keeps long-horizon memory finite; the
|
||||
trade-off is that fine detail of old history is summarized, which is acceptable
|
||||
for a normality baseline.
|
||||
- Legitimate change is not a permanent false positive: slow drift updates the
|
||||
baseline with provenance, distinguishing "furniture deliberately rearranged"
|
||||
(baseline shifts) from "reflector appeared unexpectedly" (deviation event).
|
||||
- **No accuracy claim is made.** Deviation-detection quality is not asserted
|
||||
here; any detection-rate or false-positive number requires a named reproducer
|
||||
tagged MEASURED / SYNTHETIC / CLAIMED, and a health/safety framing stays within
|
||||
the ADR-282 bounded-claims discipline (decision support, not diagnosis). No
|
||||
number is invented.
|
||||
- The memory is governed: learned baselines are observations of a space, subject
|
||||
to the same ADR-277 retention/privacy policy as the fused state they summarize;
|
||||
no raw P0 RF is retained to build a baseline.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test -p wifi-densepose-ruvector` — the normality model builds on the
|
||||
existing HNSW/event-log/temporal primitives; nearest-normal recall and
|
||||
temporal-compression bounds are exercised on synthetic streams.
|
||||
- Baseline/deviation tests: a synthetic scene with a known injected change (moved
|
||||
`Object`, altered propagation, altered modality signature) produces a deviation
|
||||
`Event` against the learned normal *without* a per-anomaly detector; an
|
||||
unchanged diurnal cycle produces none (no false positive on normal periodicity).
|
||||
- Drift test: a slow legitimate change updates the baseline (with provenance)
|
||||
rather than emitting a persistent anomaly; an abrupt change does emit one.
|
||||
- Evidence test: a learned baseline carries the evidence level of its source
|
||||
observations and is never presented above it; retention honors ADR-277.
|
||||
- Twin-linkage design check (with ADR-315): divergence between a twin-simulated
|
||||
expected state and the learned normal is expressible as a deviation signal.
|
||||
@@ -1,142 +0,0 @@
|
||||
# ADR-313: Counterfactual inference — generative spatial reasoning
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 3)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: inference, generative, counterfactual, rf-twin, fusion, uncertainty, phase-3
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** (perception substrate program) and owns
|
||||
primitive #13, *counterfactual inference*. In the ADR-300 DAG it is a phase-3,
|
||||
research-forward primitive that sits on top of the fused world state: it
|
||||
**consumes ADR-311** (real sensor fusion) for the current fused estimate and
|
||||
**ADR-315** (digital RF twin) for the twin's expected measurement
|
||||
distributions. It is design intent, authored as Proposed, and is expected to be
|
||||
revised as the phase-1 spine and the phase-2 fusion layer land.
|
||||
|
||||
RuView today reasons discriminatively: a task head maps measurements to a label
|
||||
or a pose. That answers "what does the classifier say?" but not the questions an
|
||||
operator actually asks — *would these RF measurements still make sense if nobody
|
||||
were present? Does one person explain the observation better than two?* Those
|
||||
are counterfactual questions, and a classifier cannot answer them because it has
|
||||
no model of what a measurement *should* look like under a hypothesized world
|
||||
state. A discriminative head asked about an empty room simply emits its
|
||||
best-effort label; it cannot say "the observation is better explained by
|
||||
absence."
|
||||
|
||||
The step this ADR proposes is toward a **generative spatial model**: given a
|
||||
hypothesized scene state (occupancy, count, coarse positions) and the ADR-315
|
||||
twin's propagation model for the deployment, predict the *expected* measurement
|
||||
distribution, then score how well each hypothesis explains the observed
|
||||
measurement. The best-explaining hypothesis — including the *nobody-present*
|
||||
null hypothesis — is the answer, and the margin between hypotheses is a
|
||||
first-class uncertainty signal.
|
||||
|
||||
Relevant existing assets to build on rather than duplicate:
|
||||
|
||||
- **ADR-311** (fusion) already produces the fused world estimate and its
|
||||
covariance; the counterfactual layer scores hypotheses *relative to* that
|
||||
estimate rather than re-fusing raw measurements.
|
||||
- **ADR-315** (RF twin) is the generative forward model — per-deployment
|
||||
geometry, radio locations, and expected measurement distributions. This ADR
|
||||
is a *consumer* of the twin's forward simulator, not a second simulator.
|
||||
- **ADR-302** (OOD/observability) already owns the `UNKNOWN` verdict; the
|
||||
null-hypothesis ("nobody present better explains this than any occupancy
|
||||
hypothesis") and the "no hypothesis explains this" case route through ADR-302,
|
||||
not a parallel gate.
|
||||
- `frame::EvidenceLevel` L0–L5 (ADR-282) and the ADR-304 evidence engine
|
||||
account for the resulting confidence.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Keep only discriminative heads.** Rejected: cannot express absence,
|
||||
cannot compare "one person vs. two" as competing explanations, and gives a
|
||||
confident label even when no world state explains the data.
|
||||
2. **A second, independently trained generative network with its own forward
|
||||
model.** Rejected for the default path: duplicates the ADR-315 twin's
|
||||
propagation model, invites the two models to disagree, and multiplies the
|
||||
surface that must be validated. Reserved only if the twin's analytic forward
|
||||
model proves insufficient for a phenomenon.
|
||||
3. **A hypothesis-scoring layer that uses the ADR-315 twin as the forward model
|
||||
and the ADR-311 fused state as the hypothesis prior, routing low-margin and
|
||||
null-dominant cases to the ADR-302 UNKNOWN verdict.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Define a **counterfactual inference layer** that scores a small set of scene
|
||||
hypotheses against observed measurements using the digital RF twin as the
|
||||
generative forward model.
|
||||
|
||||
### 1. Hypothesis set
|
||||
|
||||
- Hypotheses are drawn from the ADR-311 fused state and its neighbourhood: the
|
||||
current estimate, the **null hypothesis** (nobody present), and a bounded set
|
||||
of nearby alternatives (±1 occupant, shifted position). The fused estimate
|
||||
supplies the prior so the search stays small and grounded rather than
|
||||
enumerating an open world.
|
||||
- The hypothesis space is expressed over the **ADR-306** canonical ontology
|
||||
(`Space`/`Zone`, occupant count, coarse position), so a counterfactual result
|
||||
is a governed spatial statement, not an opaque score.
|
||||
|
||||
### 2. Forward model and scoring
|
||||
|
||||
- For each hypothesis, query the **ADR-315 twin** for the expected measurement
|
||||
distribution given that scene state and the deployment's propagation model.
|
||||
Score the observed measurement's likelihood under each hypothesis's expected
|
||||
distribution.
|
||||
- The answer is the maximum-likelihood hypothesis; the **margin** between the
|
||||
top hypotheses (and between the top hypothesis and the null) is the
|
||||
confidence signal, carried into the ADR-304 evidence engine.
|
||||
|
||||
### 3. Routing to UNKNOWN
|
||||
|
||||
- When the null hypothesis dominates, the layer reports *absence*, not a
|
||||
low-confidence occupancy label.
|
||||
- When **no** hypothesis explains the observation well (all likelihoods low, or
|
||||
the winning margin below threshold), the result routes to the **ADR-302**
|
||||
`UNKNOWN` verdict — the observation is outside what the twin can explain, and
|
||||
the honest output is "I cannot account for this," never a forced label.
|
||||
|
||||
### Evidence discipline
|
||||
|
||||
- Twin-predicted distributions are a **simulation** (evidence level L0 per
|
||||
ADR-282) labelled `SYNTHETIC`; a counterfactual verdict inherits the evidence
|
||||
level of its weakest input and is never presented as camera-grade ground
|
||||
truth (CLAUDE.md honesty rule).
|
||||
- Any accuracy statement about counterfactual discrimination (e.g. "distinguishes
|
||||
one occupant from two") requires the mean-pose-style baseline discipline of
|
||||
CLAUDE.md, a leakage-free held-out split, and a reproducer before it may be
|
||||
tagged `MEASURED`. This ADR asserts **no** such number.
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView gains the ability to answer absence and "which explanation is better"
|
||||
questions that discriminative heads structurally cannot — a step toward
|
||||
generative spatial reasoning and a differentiator for security and
|
||||
facility-monitoring applications where *absence* is the valuable signal.
|
||||
- Quality is bounded by the fidelity of the ADR-315 twin's forward model and the
|
||||
ADR-311 fused prior; the layer reports margins and defers to ADR-302 UNKNOWN
|
||||
rather than overstating a coarse model.
|
||||
- Hard dependency on ADR-311 (fused state and covariance) and ADR-315 (forward
|
||||
model); this ADR builds neither a fusion engine nor a propagation simulator of
|
||||
its own.
|
||||
- Being phase 3, this is design intent sitting on the fused world state; it is
|
||||
expected to be revised as ADR-311 and ADR-315 land, and it is not implemented
|
||||
by the phase-1 swarm.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests: hypothesis likelihood scoring is a deterministic function of
|
||||
observed measurement + hypothesis + twin parameters; the null hypothesis wins
|
||||
on a synthesized empty-room measurement; a two-occupant measurement scores the
|
||||
two-occupant hypothesis above the one-occupant hypothesis on a controlled
|
||||
synthetic case.
|
||||
- Integration test: measurements the twin cannot explain (out-of-model
|
||||
scattering) drive the layer to the ADR-302 UNKNOWN verdict rather than a
|
||||
forced occupancy label; margins propagate into the ADR-304 evidence engine.
|
||||
- Held-out discrimination (deferred, real-silicon): one-vs-two and
|
||||
presence-vs-absence discrimination on a leakage-free held-out split with a
|
||||
mean-pose baseline, reported as `MEASURED` with a reproducer. Until then all
|
||||
counterfactual output is `SYNTHETIC`/L0. No discrimination accuracy number is
|
||||
asserted by this ADR.
|
||||
@@ -1,138 +0,0 @@
|
||||
# ADR-314: Information-gain scheduler — sample the most informative radios
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 3)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: scheduling, active-sensing, information-gain, edge, energy, fusion, phase-3
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** (perception substrate program) and owns
|
||||
primitive #14, *information-gain scheduler*. In the ADR-300 DAG it is a phase-3,
|
||||
research-forward primitive that sits on top of the fused world state and
|
||||
**pairs with ADR-309** (active sensing): ADR-309 decides *what to probe*
|
||||
(waveform, sensing task); this ADR decides *which radios/modalities to spend
|
||||
budget on next*. It is authored as Proposed and is not implemented by the
|
||||
phase-1 swarm.
|
||||
|
||||
With multiple sensors, processing every stream at full rate is wasteful: many
|
||||
radios are, at any moment, contributing little to the current estimate while
|
||||
consuming compute, energy, and bandwidth — the three scarce resources on the
|
||||
edge nodes RuView targets (ESP32-S3/C6 and small gateways). Treating all sensors
|
||||
equally is precisely the design that does not survive a real deployment of
|
||||
"hundreds of sensors."
|
||||
|
||||
The scheduler assigns each candidate sensor/modality a value
|
||||
|
||||
```
|
||||
Value(sensor) ≈ expected uncertainty reduction / (compute + energy + bandwidth)
|
||||
```
|
||||
|
||||
and spends the next sampling/processing budget on the highest-value sensors.
|
||||
Expected uncertainty reduction is estimated *before* paying for the measurement,
|
||||
which is why the scheduler needs a model of what each sensor is likely to tell
|
||||
it — supplied by the fused state's covariance and the RF twin's forward model,
|
||||
not by actually sampling.
|
||||
|
||||
Relevant existing assets to build on rather than duplicate:
|
||||
|
||||
- **ADR-311** (fusion) maintains the fused state and its covariance — the
|
||||
current uncertainty the scheduler is trying to reduce. Expected uncertainty
|
||||
reduction is computed against that covariance, not a private one.
|
||||
- **ADR-315** (RF twin) provides the per-sensor forward model used to predict a
|
||||
candidate measurement's expected informativeness before sampling.
|
||||
- **ADR-320** (RuView sensor HAL, phase 2) exposes each radio's real
|
||||
compute/energy/bandwidth cost descriptors; the denominator is read from the
|
||||
HAL, not guessed per platform.
|
||||
- **ADR-309** (active sensing) is the paired actuator: the scheduler ranks
|
||||
sensors, ADR-309 chooses the probe on the chosen sensor.
|
||||
- **ADR-302** (observability) defines the phenomenon the estimate is *for*, so
|
||||
the scheduler prioritizes uncertainty reduction on the objective that matters,
|
||||
not on nuisance dimensions.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Round-robin / process-everything scheduling.** Rejected: burns edge
|
||||
compute and energy on redundant streams and does not scale to large fleets;
|
||||
the strategic and external reviews named exactly this as an edge-deployment
|
||||
blocker.
|
||||
2. **Static priority per sensor type (e.g. always prefer mmWave).** Rejected:
|
||||
ignores that a sensor's *current* informativeness depends on the scene and
|
||||
the present uncertainty — a well-placed WiFi link can dominate an occluded
|
||||
mmWave node in a given moment.
|
||||
3. **A value-of-information scheduler that ranks sensors by expected uncertainty
|
||||
reduction per unit cost, using the ADR-311 covariance and ADR-315 forward
|
||||
model, with costs from the ADR-320 HAL.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Define an **information-gain scheduler** that allocates the next
|
||||
sampling/processing budget across available radios by value of information.
|
||||
|
||||
### 1. Value function
|
||||
|
||||
- For each candidate sensor/modality, estimate **expected uncertainty
|
||||
reduction** on the ADR-302 objective by evaluating how much a predicted
|
||||
measurement (via the **ADR-315** forward model) would shrink the **ADR-311**
|
||||
fused-state covariance — a value-of-information estimate made *before* paying
|
||||
for the measurement.
|
||||
- Divide by the sensor's **cost** — compute + energy + bandwidth — read from the
|
||||
**ADR-320** HAL descriptors. The exact weighting of the three cost terms is a
|
||||
deployment policy (a battery node weights energy heavily; a wired gateway
|
||||
weights bandwidth), configured, not hardcoded.
|
||||
|
||||
### 2. Allocation
|
||||
|
||||
- Rank candidates by value and spend the budget on the top set, subject to a
|
||||
configurable floor that guarantees each sensor is sampled at least
|
||||
occasionally (so a sensor whose value is currently low is not starved into
|
||||
permanent blindness and can be re-evaluated as the scene changes).
|
||||
- The scheduler emits an allocation, not a measurement; **ADR-309** active
|
||||
sensing chooses the probe/waveform on each selected sensor, and the fusion
|
||||
layer (ADR-311) incorporates the result.
|
||||
|
||||
### 3. Governance and honesty
|
||||
|
||||
- Skipping a sensor for a cycle is a *deliberate* reduction in coverage; the
|
||||
scheduler records which sensors were sampled so downstream evidence (ADR-304)
|
||||
reflects the actual sensing that occurred, and observability (ADR-302) can
|
||||
raise `UNKNOWN` for a zone that went under-sampled rather than reporting a
|
||||
stale estimate as current.
|
||||
|
||||
### Evidence discipline
|
||||
|
||||
- Expected-uncertainty-reduction estimates are model predictions from the
|
||||
ADR-315 twin (simulation, L0 per ADR-282, `SYNTHETIC`); a scheduling decision
|
||||
is a resource choice, never a sensing claim.
|
||||
- Any energy/latency/throughput improvement figure requires real-silicon
|
||||
measurement with a reproducer before it is tagged `MEASURED` (CLAUDE.md
|
||||
hardware rule). This ADR asserts **no** efficiency number.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Edge deployments spend scarce compute, energy, and bandwidth where they buy
|
||||
the most certainty, making "hundreds of sensors" operationally tractable — a
|
||||
capability the reviews flagged as critical for edge deployment.
|
||||
- Quality is bounded by the accuracy of the ADR-315 forward model (informativeness
|
||||
prediction) and ADR-320 cost descriptors; a poor forward model degrades to
|
||||
near-round-robin, which is safe but not optimal. The sampling floor bounds the
|
||||
worst case.
|
||||
- Hard dependency on ADR-311 (covariance), ADR-315 (forward model), and ADR-320
|
||||
(cost descriptors), and paired with ADR-309; this ADR builds none of those.
|
||||
- Being phase 3, this is design intent sitting on the fused world state and is
|
||||
expected to be revised as ADR-309, ADR-311, ADR-315, and the ADR-320 HAL land.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests: the value function is a deterministic function of covariance +
|
||||
forward model + cost descriptors; a sensor predicted to reduce objective
|
||||
uncertainty more per unit cost ranks above one that reduces it less; the
|
||||
sampling floor guarantees eventual re-evaluation of a low-value sensor.
|
||||
- Integration test: on a synthetic multi-sensor scene, the scheduler reduces
|
||||
objective uncertainty faster per unit modelled cost than round-robin, and
|
||||
raises ADR-302 UNKNOWN for a deliberately starved zone rather than reporting a
|
||||
stale estimate.
|
||||
- Field validation (deferred, real-silicon): energy/latency/throughput on an
|
||||
instrumented multi-node deployment, reported as `MEASURED` with a reproducer.
|
||||
Until then all informativeness and cost figures are `SYNTHETIC`/L0. No
|
||||
efficiency number is asserted by this ADR.
|
||||
@@ -1,159 +0,0 @@
|
||||
# ADR-315: Digital RF twin — persistent per-deployment RF model
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 3)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: rf-twin, digital-twin, propagation, calibration, spatial-memory, worldgraph, phase-3
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** (perception substrate program) and owns
|
||||
primitive #15, *digital RF twin*. In the ADR-300 DAG it is a phase-3,
|
||||
research-forward primitive that underpins several other phase-3 primitives:
|
||||
**ADR-308** (placement optimizer) plans against the twin's propagation model,
|
||||
**ADR-313** (counterfactual inference) uses it as the generative forward model,
|
||||
and **ADR-314** (information-gain scheduler) uses it to predict per-sensor
|
||||
informativeness. It ties directly to **ADR-301** (calibration), **ADR-308**
|
||||
(placement), and **ADR-312** (long-term spatial memory). It is authored as
|
||||
Proposed and is not implemented by the phase-1 swarm.
|
||||
|
||||
RuView today has no persistent, per-deployment model of the RF environment.
|
||||
Calibration state, observed multipath, and radio geometry exist transiently
|
||||
inside a running session; when the process restarts or a change happens
|
||||
overnight, there is nothing that says "this is what this room's RF looked like
|
||||
yesterday." Without a persistent baseline, a physical change — furniture moved,
|
||||
a wall opened, a machine relocated, an intruder present — has nothing to be a
|
||||
*delta against*. It is just a different measurement, indistinguishable from
|
||||
noise or drift.
|
||||
|
||||
The **digital RF twin** is that persistent baseline: a per-deployment model
|
||||
holding
|
||||
|
||||
- **geometry and radio locations** (from the ADR-306 scene / worldgraph),
|
||||
- **propagation history** and **observed multipath** structure,
|
||||
- **calibration state** (from ADR-301),
|
||||
- **expected measurement distributions** for each link and phenomenon.
|
||||
|
||||
Once the twin exists, a physical change becomes a **measurable delta against the
|
||||
twin** rather than an unexplained measurement. This is what connects RuView to
|
||||
facility management (what changed in this space?), security (is there an
|
||||
unexplained presence?), robotics (has the map drifted?), and industrial
|
||||
monitoring (did the plant layout change?) — the applications the strategic
|
||||
assessment named as the value beyond a single detector.
|
||||
|
||||
Relevant existing assets to build on rather than duplicate:
|
||||
|
||||
- The `worldgraph` crate already models the physical scene — `Room`/`Space`
|
||||
with `bounds_enu`, `Wall { rf_attenuation_db }`, `Doorway`, `Zone`, and
|
||||
`Sensor` nodes (ADR-306). The twin *annotates and persists* this scene with RF
|
||||
state; it does not invent a second geometry.
|
||||
- `wifi-densepose-calibration` (enrollment, bank, anchor, runtime, specialist)
|
||||
holds the calibration state the twin persists; the twin references and
|
||||
versions calibration records, it does not reimplement calibration.
|
||||
- **ADR-312** (long-term spatial memory, phase 3) is the persistence and
|
||||
temporal-history substrate; the twin is a *structured occupant* of that
|
||||
memory, not a separate database.
|
||||
- **ADR-305** (authenticated identity) and **ADR-295** (provenance) mean the
|
||||
measurements that update the twin carry verified lineage, so a delta is
|
||||
attributable rather than anonymous.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **No persistent RF model (status quo).** Rejected: every change looks like
|
||||
noise; nothing supports "what changed since yesterday?", which is the
|
||||
question the facility/security/industrial applications actually ask.
|
||||
2. **A full electromagnetic digital twin (per-site ray-tracing / FDTD kept in
|
||||
sync in real time).** Rejected for the default path: far heavier than the
|
||||
coarse `rf_attenuation_db` scene RuView actually has and impractical on edge
|
||||
hardware. A high-fidelity solver is retained as an *optional backend* the
|
||||
twin can call, not the baseline.
|
||||
3. **A persistent, per-deployment RF model layered over the ADR-306 scene and
|
||||
ADR-312 memory: geometry + radio locations + calibration state + observed
|
||||
multipath + expected measurement distributions, updated by verified
|
||||
measurements, exposing changes as deltas.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Define the **digital RF twin** as a persistent, versioned, per-deployment model
|
||||
of the RF environment, layered over existing scene, calibration, and memory
|
||||
assets.
|
||||
|
||||
### 1. State the twin holds
|
||||
|
||||
- **Geometry and radio locations** referenced from the ADR-306 / worldgraph
|
||||
scene (not copied).
|
||||
- **Calibration state** referenced and versioned from
|
||||
`wifi-densepose-calibration` (ADR-301), so the twin knows *which* calibration
|
||||
a stored distribution was captured under.
|
||||
- **Observed multipath and propagation history** — a bounded temporal summary
|
||||
of per-link channel structure, stored in ADR-312 spatial memory.
|
||||
- **Expected measurement distributions** per link and phenomenon — the forward
|
||||
model ADR-308, ADR-313, and ADR-314 consume.
|
||||
|
||||
### 2. Update and delta
|
||||
|
||||
- Verified measurements (ADR-305 identity, ADR-295 provenance) update the twin's
|
||||
distributions online, bounded by ADR-301 calibration validity. A new
|
||||
observation is compared to the twin's expected distribution; the **delta** —
|
||||
and its statistical significance against the twin's own variance — is the
|
||||
primary output. A change large relative to the twin's modelled variance is a
|
||||
*detected physical change*, not noise.
|
||||
- The twin is **versioned**: a calibration event, a deliberate geometry edit, or
|
||||
an accepted physical change advances the twin version, so history is
|
||||
auditable and a delta is always relative to a named baseline.
|
||||
|
||||
### 3. Consumers
|
||||
|
||||
- **ADR-308** queries the twin's propagation model to plan placements.
|
||||
- **ADR-313** uses the twin's expected distributions as the generative forward
|
||||
model for hypothesis scoring.
|
||||
- **ADR-314** uses per-sensor expected informativeness from the twin.
|
||||
- Facility/security/robotics/industrial integrations read the twin's change
|
||||
deltas as governed ADR-306 spatial events.
|
||||
|
||||
### Evidence discipline
|
||||
|
||||
- The twin's expected distributions and any propagation simulation are
|
||||
**simulation** (evidence level L0 per ADR-282), labelled `SYNTHETIC`. A delta
|
||||
computed against them is a model-relative statement.
|
||||
- A change/anomaly detection *claim* (e.g. "detects furniture-scale changes")
|
||||
requires real-silicon measurement against a leakage-free protocol with a
|
||||
reproducer before it is tagged `MEASURED` (CLAUDE.md hardware rule). The twin
|
||||
never presents a modelled expected distribution as evidence that a physical
|
||||
state *is* the case; it presents a *delta and its significance*. This ADR
|
||||
asserts **no** detection-accuracy number.
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView gains a persistent per-deployment baseline, turning "a different
|
||||
measurement" into "a measurable, attributable, versioned change" — the bridge
|
||||
from a sensing runtime to facility management, security, robotics, and
|
||||
industrial monitoring.
|
||||
- The twin is the shared forward model for ADR-308/310/311, so those primitives
|
||||
speak one propagation model rather than three inconsistent ones — a
|
||||
deliberate reason to build the twin before its consumers mature.
|
||||
- Quality is bounded by the coarseness of the worldgraph scene and the fidelity
|
||||
of the forward model; the twin reports deltas *with significance against its
|
||||
own variance* rather than asserting confident change detection on a coarse
|
||||
model. The optional high-fidelity backend is where higher accuracy lives.
|
||||
- Hard dependency on ADR-306 (scene), ADR-301 (calibration state), and ADR-312
|
||||
(persistence); it reuses `worldgraph` and `wifi-densepose-calibration` rather
|
||||
than rebuilding geometry or calibration.
|
||||
- Being phase 3, this is design intent; it is expected to be revised as the
|
||||
phase-1 spine, ADR-311 fusion, and ADR-312 memory land.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests: the twin's expected distribution is a deterministic function of
|
||||
scene + calibration + propagation history; delta computation and its
|
||||
significance against stored variance are correct on synthetic distributions;
|
||||
versioning advances on calibration/geometry/accepted-change events and history
|
||||
is retained.
|
||||
- Integration test: on a synthetic deployment, an injected physical change (a
|
||||
wall attenuation shift) produces a significant delta against the twin while
|
||||
ordinary noise does not; the delta surfaces as a governed ADR-306 event with
|
||||
provenance (ADR-305/292).
|
||||
- Field validation (deferred, real-silicon): change detection on an instrumented
|
||||
real deployment with a controlled physical-change protocol, reported as
|
||||
`MEASURED` with a reproducer. Until then all twin distributions and deltas are
|
||||
`SYNTHETIC`/L0. No detection-accuracy number is asserted by this ADR.
|
||||
@@ -1,156 +0,0 @@
|
||||
# ADR-316: Fleet control plane — provisioning to audit trails
|
||||
|
||||
- **Status**: Proposed (ADR-300 phase 2)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: fleet, operations, provisioning, firmware, updates, audit, identity, phase-2
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is a child of **ADR-300** (perception substrate program) and owns
|
||||
primitive #16, *fleet control plane*. In the ADR-300 DAG it is a phase-2
|
||||
integration-and-operations primitive that sits on the phase-1 spine: it
|
||||
**consumes ADR-305** (authenticated sensor identity) for per-device identity and
|
||||
enrollment, and **ADR-318** (capability certificate) for the signed models,
|
||||
calibration validity, and capability envelopes a device is allowed to run. It is
|
||||
authored as Proposed and is not implemented by the phase-1 swarm.
|
||||
|
||||
The external and internal reviews both named the same operational gap: RuView
|
||||
has strong per-device primitives but no **release identity** and no **bill of
|
||||
materials** binding a fielded sensor to the exact firmware, model, and
|
||||
calibration it is running — and no plane to manage that across many devices.
|
||||
Without this, a handful of nodes is fine but *hundreds* of sensors become an
|
||||
operational nightmare: no coherent way to provision, roll certificates, verify
|
||||
firmware compatibility, distribute signed models, track calibration lifecycle,
|
||||
watch health, stage updates, roll back, diagnose remotely, enforce data
|
||||
retention, or produce an audit trail. This ADR addresses that release-identity /
|
||||
BOM gap directly.
|
||||
|
||||
The scope is deliberately the **control plane**, not the data plane. The
|
||||
authenticated measurement path is **ADR-296** (bind + allowlist) plus **ADR-305**
|
||||
(signed envelope); this ADR governs the *devices and artifacts*, not the
|
||||
per-frame stream.
|
||||
|
||||
Relevant existing assets to build on rather than duplicate:
|
||||
|
||||
- **ADR-305** already defines per-device keypairs, the `DeviceId → public key →
|
||||
capabilities` enrollment record, key rotation and revocation *semantics* — and
|
||||
explicitly deferred their **fleet distribution** to this ADR. The control
|
||||
plane is the distribution and lifecycle layer over ADR-305 identity, not a new
|
||||
identity scheme.
|
||||
- **ADR-318** (capability certificate) defines the signed, expiring artifact a
|
||||
device is authorized to run; the fleet plane is what *distributes, stages, and
|
||||
revokes* those certificates and the signed models they point at.
|
||||
- **ADR-301** (calibration) owns calibration validity/expiry; the fleet plane
|
||||
tracks calibration *lifecycle* across the fleet (which nodes are due, which are
|
||||
stale) rather than redefining calibration.
|
||||
- **ADR-319** (witness chain) provides the append-only, re-verifiable record;
|
||||
fleet audit trails are witness-chain entries, not a parallel log format.
|
||||
- **ADR-320** (RuView sensor HAL, phase 2) provides hardware/firmware capability
|
||||
descriptors used for firmware-compatibility checks before staging an update.
|
||||
- `wifi-densepose-bfld` `CapabilityAttestation` (ADR-141) is the device-side
|
||||
attestation the plane checks against declared cohort capabilities.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Manual per-device operations (SSH/flash by hand).** Rejected: does not
|
||||
scale past a handful of nodes, produces no release identity, no audit trail,
|
||||
and no safe rollback — exactly the operational nightmare the reviews named.
|
||||
2. **Adopt a generic third-party IoT device-management platform wholesale.**
|
||||
Rejected as the core: generic platforms do not understand RuView's signed
|
||||
capability certificate, calibration validity, or witness chain, and would
|
||||
fork trust away from the phase-1 spine. A generic transport/agent *may* be a
|
||||
backend, but identity, certificates, and audit remain RuView's.
|
||||
3. **A RuView-native control plane layered on ADR-305 identity, ADR-318
|
||||
certificates, ADR-301 calibration lifecycle, and ADR-319 audit — covering
|
||||
provisioning through rollback and retention.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Define a **fleet control plane** that manages RuView sensors and their signed
|
||||
artifacts across their lifecycle, built on the phase-1 identity/certificate
|
||||
spine.
|
||||
|
||||
### 1. Release identity and bill of materials
|
||||
|
||||
- Each fielded device has a **BOM record** binding `DeviceId` (ADR-305) → exact
|
||||
firmware version → signed model set → active capability certificate (ADR-318)
|
||||
→ current calibration record (ADR-301) → HAL/hardware descriptor (ADR-320).
|
||||
This *is* the release identity the reviews found missing: given a device you
|
||||
can state precisely what it is running and prove it is signed.
|
||||
|
||||
### 2. Provisioning, certificates, firmware compatibility
|
||||
|
||||
- **Provisioning** is the authorized ADR-305 enrollment step at fleet scale:
|
||||
minting a keypair, registering the public key and capabilities, and issuing
|
||||
the initial ADR-318 certificate. A device is untrusted until provisioned.
|
||||
- **Certificate lifecycle**: issue, rotate, expire, and **revoke** ADR-318
|
||||
certificates and the ADR-305 keys behind them; revocation lists are
|
||||
distributed here (the distribution ADR-305 deferred).
|
||||
- **Firmware compatibility**: before staging a firmware or model, check the
|
||||
target's ADR-320 HAL descriptor and ADR-141 capability attestation so an
|
||||
incompatible or under-capable device is never sent an artifact it cannot
|
||||
honestly run.
|
||||
|
||||
### 3. Cohorts, staged updates, rollback
|
||||
|
||||
- Devices group into **cohorts** (by site, hardware, capability). Updates —
|
||||
signed models and firmware — roll out **staged** (canary → cohort → fleet)
|
||||
with health gates between stages, and **roll back** to the previously recorded
|
||||
BOM on a failed health check. Only signed artifacts are ever staged.
|
||||
|
||||
### 4. Health telemetry, remote diagnostics, retention, audit
|
||||
|
||||
- **Health telemetry** and **remote diagnostics** report device liveness,
|
||||
calibration staleness (ADR-301), certificate expiry (ADR-318), and error
|
||||
state — read-only diagnostics by default, mutations authorized explicitly.
|
||||
- **Data retention** policy is enforced per cohort, and P0/CSI/person data never
|
||||
leaves the edge except under the ADR-277/280 governance already in force
|
||||
(CLAUDE.md: never commit or exfiltrate CSI/person data).
|
||||
- Every lifecycle action — provision, rotate, revoke, stage, roll back — is
|
||||
written as an **ADR-319 witness-chain** entry, giving a re-verifiable **audit
|
||||
trail** rather than a mutable log.
|
||||
|
||||
### Authority and least privilege
|
||||
|
||||
- The control plane is default-deny (CLAUDE.md: default to least authority).
|
||||
Provisioning, key rotation, revocation, staging, and rollback are each
|
||||
separately authorized operations; no fleet action is implied by another.
|
||||
Credentials and private keys are never logged or committed.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Hundreds of sensors become operable: coherent release identity, signed-artifact
|
||||
distribution, staged updates with rollback, and a re-verifiable audit trail —
|
||||
closing the release-identity / BOM gap the reviews raised.
|
||||
- The plane concentrates operational authority; that is mitigated by
|
||||
default-deny, per-action authorization, signed-only artifacts, and
|
||||
witness-chained audit. A compromised plane must still forge signatures the
|
||||
phase-1 spine verifies.
|
||||
- Hard dependency on ADR-305 (identity), ADR-318 (certificate), ADR-301
|
||||
(calibration lifecycle), ADR-319 (audit), and ADR-320 (firmware/HAL
|
||||
compatibility). This ADR distributes and sequences those artifacts; it does
|
||||
not redefine identity, certificates, calibration, or the witness format.
|
||||
- Being phase 2, this is design intent depending on the spine; it is expected to
|
||||
be revised as ADR-318, ADR-319, and ADR-320 land.
|
||||
- **No fielded fleet-operation claim is MEASURED without real-silicon evidence**
|
||||
(CLAUDE.md hardware rule): staged update and rollback on real nodes require a
|
||||
captured runtime log. A passing simulation is not fleet evidence.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests: BOM records bind identity/firmware/model/certificate/calibration
|
||||
consistently and reject inconsistent bindings; certificate issue/rotate/revoke
|
||||
transitions are correct; a firmware-incompatible target is refused staging;
|
||||
every lifecycle action emits a well-formed ADR-319 witness entry.
|
||||
- Integration test: a synthetic cohort undergoes a canary→cohort→fleet staged
|
||||
update; an injected health failure triggers rollback to the prior BOM; the
|
||||
full sequence is re-verifiable from the witness chain offline; a revoked
|
||||
certificate is rejected fleet-wide.
|
||||
- Security test (`npm run test:security` analogue for the plane): default-deny
|
||||
is enforced; unauthorized provision/rotate/revoke/stage is rejected and
|
||||
counted; no credential or P0 data appears in telemetry or audit output.
|
||||
- Field validation (deferred, real-silicon): a real multi-node staged update and
|
||||
rollback with a captured boot/runtime log, reported as `MEASURED` with a
|
||||
reproducer. Until then all fleet-operation results are simulator-level. No
|
||||
fielded reliability number is asserted by this ADR.
|
||||
@@ -1,140 +0,0 @@
|
||||
# ADR-317: Multi-domain benchmark scorecard — regressions cannot hide behind pooled accuracy
|
||||
|
||||
- **Status**: Accepted — initial implementation planned (ADR-300 phase 1)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: benchmark, aetherarena, ci-gate, evidence, honesty, domain-generalization, substrate
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is primitive 17 of the perception-substrate program (ADR-300) and the
|
||||
per-PR enforcement edge of the phase-1 certificate spine. In the ADR-300
|
||||
dependency DAG it reads accuracy from the evidence engine (ADR-304), consumes
|
||||
the domain state produced by out-of-distribution detection (ADR-302), scores
|
||||
against calibration certificates (ADR-301), and is anchored in the witness chain
|
||||
(ADR-319). It is the surface that makes the rest of the spine testable on every
|
||||
change to sensing code.
|
||||
|
||||
A single pooled accuracy number is the classic way a domain-generalization
|
||||
regression hides. A model can raise mean PCK or mean presence accuracy while
|
||||
quietly collapsing on unseen rooms, unseen devices, or stationary subjects —
|
||||
exactly the conditions WiFi sensing fails in and exactly the conditions a
|
||||
pooled average washes out. The strategic assessment (ADR-300) named this: what
|
||||
distinguishes infrastructure from a demo is that a regression on *any* operating
|
||||
domain is caught before merge, not discovered in the field.
|
||||
|
||||
RuView does not need a new benchmark to do this. AetherArena is already
|
||||
**v0-complete infrastructure** (ADR-149): a deterministic scoring engine
|
||||
reusing `wifi-densepose-train` (`src/ruview_metrics.rs`, `src/ablation.rs`,
|
||||
`src/eval.rs`, `src/proof.rs`), a `PROOF_SEED=42` determinism substrate that
|
||||
SHA-256-hashes outputs against an expected hash, an append-only witness ledger,
|
||||
and a live Hugging Face Space. ADR-145's ablation harness already computes
|
||||
presence accuracy, localization error, FP/FN, latency percentiles, a
|
||||
privacy-leakage score, and **cross-room degradation**. The board is
|
||||
intentionally empty (benchmark-first). What is missing is not a scorer but a
|
||||
**scorecard format** that reports per-domain rather than pooled, and a
|
||||
**sensing-crate CI gate** that runs it on every PR.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Keep the single pooled score / `RuViewTier`.** Rejected: it is exactly the
|
||||
surface a per-domain regression hides behind; a Gold tier can coexist with a
|
||||
broken unseen-room slice.
|
||||
2. **Add a new benchmark repo/harness for domains.** Rejected: AetherArena's
|
||||
scorer, determinism binding, and witness ledger already exist and are the
|
||||
right engine; a parallel harness would fork the scoring substrate and its
|
||||
anti-gaming/leakage discipline.
|
||||
3. **Extend the AetherArena scorer with a per-domain scorecard and wire it as a
|
||||
per-PR sensing-crate gate.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Reuse the AetherArena scorer and witness ledger (ADR-149) and add two things: a
|
||||
**multi-domain scorecard** format and a **sensing-crate PR gate** that produces
|
||||
it.
|
||||
|
||||
### 1. The multi-domain scorecard
|
||||
|
||||
The scorecard reports each capability broken out by operating domain, never
|
||||
pooled into one figure. The v0 domain axes:
|
||||
|
||||
- **Presence**: `room-known`, `room-unseen`, `device-unseen`, `stationary-10m`
|
||||
(a stationary subject at range — the canonical WiFi failure case).
|
||||
- **Pose**: `matched`, `subject-unseen`, `room-unseen`.
|
||||
- **OOD rejection**: the rate at which genuinely out-of-distribution input is
|
||||
correctly returned as UNKNOWN by ADR-302 (a capability, not a failure) and
|
||||
the false-UNKNOWN rate on in-distribution input.
|
||||
- **Calibration drift**: fingerprint-distance trajectory against the ADR-301
|
||||
certificate over the scored window, and the fraction of inferences in each
|
||||
ADR-302 `DomainState` (KNOWN / DEGRADED / UNKNOWN).
|
||||
|
||||
Each cell carries exactly one `EvidenceLevel` (L0–L5, ADR-282). A slice scored
|
||||
on synthetic input is L0/`Synthetic` by construction; a slice on a leakage-free
|
||||
held-out real split is graded higher and only then may a per-domain number be
|
||||
labelled MEASURED. Pose PCK cells additionally require the mean-pose baseline
|
||||
and a leakage-free held-out split (CLAUDE.md) or they are not reported as pose
|
||||
accuracy at all.
|
||||
|
||||
### 2. Per-domain regression gate
|
||||
|
||||
- The gate compares each scorecard cell against the merged-baseline scorecard
|
||||
stored in the AetherArena witness ledger. A regression **in any single
|
||||
domain** beyond its configured threshold fails the PR, even if the pooled
|
||||
average improved. Improvement on `room-known` cannot buy a regression on
|
||||
`room-unseen`.
|
||||
- Thresholds are per-domain and per-capability; the unseen/stationary/OOD
|
||||
domains carry the strictest budgets because they are the ones a pooled score
|
||||
hides. The baseline is append-only and witness-anchored — a new baseline is a
|
||||
new signed ledger entry, never an in-place overwrite (ADR-149 ledger pattern,
|
||||
ADR-319 anchoring).
|
||||
|
||||
### 3. Sensing-crate CI wiring
|
||||
|
||||
- Every PR that touches a sensing crate runs the scorecard across all domains
|
||||
under the ADR-011/ADR-149 determinism binding (`PROOF_SEED=42`), so the run
|
||||
is reproducible and tamper-evident. The gate is added to
|
||||
`.github/workflows/` as an authoritative check.
|
||||
- The held-out real split remains private and is never accessible to synthetic
|
||||
generation, augmentation, or calibration (ADR-149 leakage constraint, ADR-282
|
||||
rule d). Submitters/PRs provide a model, not predictions on data they hold.
|
||||
|
||||
### Provenance and honesty discipline
|
||||
|
||||
- No benchmark numbers are invented by this ADR. It delivers the scorecard
|
||||
format, the per-domain gate, and the CI wiring; the numbers come from the
|
||||
ADR-304 evidence ledger and the AetherArena scorer on real data, labelled at
|
||||
the honest evidence level. Empty domains report "no evidence," which the gate
|
||||
treats as no coverage — never as a pass.
|
||||
|
||||
## Consequences
|
||||
|
||||
- A domain-generalization regression can no longer merge behind a flattering
|
||||
pooled average; the failure mode that most distinguishes fielded sensing from
|
||||
a demo is caught at PR time.
|
||||
- Every PR touching sensing pays a per-domain scoring cost. Bounded by reusing
|
||||
the existing deterministic scorer and by tiered compute (CPU smoke vs full
|
||||
score, ADR-149), but it is a deliberate cost for per-domain safety.
|
||||
- The empty AetherArena board fills with honest, per-domain, evidence-labelled
|
||||
results rather than a single headline tier — consistent with the
|
||||
benchmark-first posture and with ADR-282's ecosystem positioning.
|
||||
- Some domains will show weak or absent coverage. Surfacing that per-domain is
|
||||
the point; the scorecard must never paper over a thin domain with a pooled
|
||||
number.
|
||||
- The program-level acceptance test (ADR-300) is encoded here as an AetherArena
|
||||
scenario, closing the loop once the phase-1 spine lands.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test` on the AetherArena scorer extension — per-domain slicing math
|
||||
against fixtures; per-domain regression gate fails on a single-domain
|
||||
regression while pooled improves, and passes when all domains hold; empty
|
||||
domains report "no evidence," not a pass; every cell carries exactly one
|
||||
`EvidenceLevel`; synthetic slices are L0 by construction.
|
||||
- Determinism: a scored run reproduces its SHA-256 hash under `PROOF_SEED=42`
|
||||
(ADR-011/ADR-149 binding); the baseline scorecard is append-only and
|
||||
witness-anchored (ADR-319), never mutated in place.
|
||||
- CI: the sensing-crate gate runs on a PR touching a sensing crate and blocks a
|
||||
planted single-domain regression.
|
||||
- Real-data scorecards (a leakage-free held-out split with ADR-303 references)
|
||||
are the maturity milestone; a synthetic scorecard is L0 and no per-domain
|
||||
number is MEASURED without a reproducer per CLAUDE.md.
|
||||
@@ -1,138 +0,0 @@
|
||||
# ADR-318: Capability certificates — validated-for-this-environment claims
|
||||
|
||||
- **Status**: Accepted — initial implementation planned (ADR-300 phase 1)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: capability, certificate, evidence, provenance, signature, honesty, substrate
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is primitive 18 of the perception-substrate program (ADR-300) and,
|
||||
per the strategic assessment, among the strongest ideas in the program: it is
|
||||
where the whole certificate spine becomes a consumable contract. In the ADR-300
|
||||
dependency DAG it **consumes the evidence engine (ADR-304)** — a capability
|
||||
certificate is a signed attestation minted over a slice of that ledger — the
|
||||
**calibration certificate (ADR-301)** for the environment it is validated
|
||||
against, and the **RuField signature types (ADR-305 / ADR-260/262/277/279)** to
|
||||
sign it. It reports domain state via ADR-302 and is anchored in the witness
|
||||
chain (ADR-319).
|
||||
|
||||
RuView must stop making unconditional capability claims. "Supports presence" is
|
||||
not a true statement — presence detection works in some rooms, on some hardware,
|
||||
for some subject dynamics, and fails on a stationary subject at range in an
|
||||
uncalibrated room. A capability is only ever *validated for a specific
|
||||
environment*, and the honest unit of that claim is a signed, expiring
|
||||
certificate, not a feature flag in a README.
|
||||
|
||||
The ingredients now exist across the phase-1 spine: ADR-304 accumulates
|
||||
per-`(room, device, subject)` accuracy, false-positive rate, drift, and domain
|
||||
state; ADR-301 produces the signed room fingerprint the environment is keyed to;
|
||||
ADR-305 provides the authenticated device identity and `CapabilityAttestation`
|
||||
(BFLD, ADR-141) that bounds *what a device is even attested to sense*; ADR-282
|
||||
provides the mandatory `EvidenceLevel`. What is missing is the artifact that
|
||||
binds them into a single, verifiable "validated here, until then" claim and the
|
||||
consumer-side rule that refuses capabilities lacking one.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Static capability flags / a `supports_presence` boolean.** Rejected: it is
|
||||
the exact dishonest claim — environment-independent, unsigned, non-expiring,
|
||||
and false the moment the room, device, or subject dynamics differ.
|
||||
2. **Report raw ledger accuracy to consumers directly.** Rejected: the ledger
|
||||
(ADR-304) is the source of truth but not a portable, signed, bounded contract;
|
||||
handing consumers raw records pushes evidence-weighting and expiry logic into
|
||||
every consumer and drops the single verifiable object.
|
||||
3. **Mint a signed, expiring `CapabilityCertificate` over an ADR-304 ledger
|
||||
slice, and make consumers refuse capabilities without a valid one.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce a signed **`CapabilityCertificate`**: a bounded attestation that a
|
||||
specific capability has been validated for a specific environment, for a bounded
|
||||
time.
|
||||
|
||||
### 1. The certificate
|
||||
|
||||
A serializable `CapabilityCertificate` binding:
|
||||
|
||||
- `capability` — the phenomenon (e.g. `presence`, `pose`), which must be within
|
||||
the device's ADR-305/ADR-141 `CapabilityAttestation` (a device cannot be
|
||||
certified for something it is not even attested to sense).
|
||||
- `room` — the ADR-306 space identifier, tied to the ADR-301 calibration
|
||||
certificate version the validation was performed against.
|
||||
- `hardware` — the ADR-305 authenticated `DeviceId` (and, in phase 2, the
|
||||
ADR-320 HAL descriptor of the sensor).
|
||||
- `model` — the model version scored.
|
||||
- `calibrated_date` — the calibration certificate age at validation time.
|
||||
- `moving_recall`, `stationary_recall`, `false_presence_per_24h` — the measured
|
||||
operating metrics, sliced from the ADR-304 ledger for this exact context (not
|
||||
a global average), each honestly labelled. These are per-capability; a pose
|
||||
certificate carries pose metrics with the mean-pose baseline and a
|
||||
leakage-free split (CLAUDE.md) or it is not issued.
|
||||
- `valid_until` — an explicit expiry; a certificate is never open-ended.
|
||||
- `evidence_level` — exactly one L0–L5 (ADR-282). A certificate minted from a
|
||||
synthetic ledger slice is L0/`Synthetic`; a MEASURED metric requires an
|
||||
ADR-303 reference and a reproducer. The certificate cannot upgrade the level
|
||||
of the ledger it is minted from (ADR-304 honesty rule).
|
||||
- `signature` — a RuField `SignatureBlock` (ADR-305 / ADR-260/262/277/279) over
|
||||
the canonical serialization; an unsigned certificate is not a valid
|
||||
certificate. The certificate is anchored in the witness chain (ADR-319).
|
||||
|
||||
### 2. Minting
|
||||
|
||||
- A certificate is minted from a slice of the ADR-304 evidence ledger for one
|
||||
`(room, device, subject-class, model)` context. If the ledger reports "no
|
||||
evidence" for that context, **no certificate is issued** — absence of evidence
|
||||
is never a capability. Minting is a pure function over the append-only ledger
|
||||
at mint time; the metrics are frozen into the signed object.
|
||||
- Expiry (`valid_until`) is derived from calibration validity (ADR-301) and an
|
||||
evidence-freshness policy: a certificate cannot outlive the calibration it was
|
||||
validated against, and drift beyond the ADR-301 envelope invalidates both.
|
||||
|
||||
### 3. Consumer refusal rule
|
||||
|
||||
- Applications and surfaces **refuse to consume a capability that lacks a valid
|
||||
certificate for the current environment**. "Valid" means: signature verifies,
|
||||
`room`/`hardware`/`model` match the running context, `valid_until` is in the
|
||||
future, and the referenced calibration certificate is itself still valid
|
||||
(ADR-301 not invalidated). A failed check yields UNKNOWN via ADR-302, not a
|
||||
best-effort guess.
|
||||
- This makes the ADR-300 acceptance clause "quantify whether it can reliably
|
||||
sense the requested phenomenon → generate a signed capability certificate"
|
||||
a hard gate rather than a hope.
|
||||
|
||||
## Consequences
|
||||
|
||||
- RuView can no longer claim a capability it has not validated for the caller's
|
||||
environment; the honest failure — "not certified here" → UNKNOWN — is
|
||||
surfaced by construction rather than by discipline.
|
||||
- OEM/integrator diligence gets a single verifiable artifact ("presence,
|
||||
validated in *this* room, on *this* device, with *these* recall/false-alarm
|
||||
numbers, until *this* date, at *this* evidence level, signed") — the strongest
|
||||
commercial output of the spine.
|
||||
- Certificates expire and get refused; some environments will have no
|
||||
certificate and therefore no capability until validated. That refusal is the
|
||||
intended honest behavior, not a regression.
|
||||
- Key management and expiry policy are operational responsibilities, reusing the
|
||||
ADR-305 enrollment/rotation and ADR-301 validity machinery rather than new
|
||||
infrastructure; fleet distribution of certificates is owned by ADR-316.
|
||||
- No capability number is invented here; every metric on a certificate is sliced
|
||||
from the ADR-304 ledger at its honest evidence level.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test` on the certificate crate — mint from a ledger slice produces the
|
||||
frozen metrics; "no evidence" context yields no certificate; signature
|
||||
round-trip and tamper rejection; `valid_until` and calibration-linked expiry
|
||||
enforced; consumer refusal on room/hardware/model mismatch, expiry, or
|
||||
invalidated calibration resolves to UNKNOWN (ADR-302), not a guess; evidence
|
||||
level is inherited from the ledger and cannot be upgraded; a certificate
|
||||
cannot be issued for a capability outside the device's ADR-305/ADR-141
|
||||
attestation.
|
||||
- Cross-ADR: an ADR-304 ledger fixture mints a certificate; an ADR-302 test
|
||||
asserts an expired/mismatched certificate gates to UNKNOWN; the ADR-300
|
||||
acceptance test consumes a minted certificate end-to-end.
|
||||
- Real-deployment certificates (minted from a populated ledger with ADR-303
|
||||
references on live ESP32 captures) are the maturity milestone and require
|
||||
hardware evidence per CLAUDE.md; a certificate minted from a synthetic ledger
|
||||
is L0 by construction.
|
||||
@@ -1,146 +0,0 @@
|
||||
# ADR-319: Witness chain — epistemic infrastructure for physical AI
|
||||
|
||||
- **Status**: Accepted — initial implementation planned (ADR-300 phase 1)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: provenance, witness, evidence, signature, epistemics, ontology, substrate
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is primitive 19 of the perception-substrate program (ADR-300) and a
|
||||
spine root of its phase-1 certificate stack. In the ADR-300 dependency DAG it
|
||||
**extends the source-provenance state machine (ADR-295)** and the RuField
|
||||
provenance types, **ties to the signature machinery (ADR-305 /
|
||||
ADR-260/262/277/279)**, and anchors the artifacts produced by ADR-301
|
||||
(calibration certificates), ADR-304 (evidence records), ADR-317 (benchmark
|
||||
scorecards), and ADR-318 (capability certificates). In phase 2 it carries the
|
||||
independent-corroboration link from ADR-303.
|
||||
|
||||
The strategic assessment (ADR-300) framed RuView's real product as **epistemic
|
||||
infrastructure for physical AI**: the value is not the claim "a person is
|
||||
present" but the *auditable reasoning* behind it. A bare boolean output discards
|
||||
everything a downstream system needs to trust or contest it — which radio
|
||||
observed it, what DSP evidence supported it, which model inferred it, whether an
|
||||
independent sensor agreed, what spatial state it updated, and what policy acted
|
||||
on it. Once the answer is a boolean, "why do you believe that?" has no answer.
|
||||
|
||||
RuView already has the pieces of a chain but not the chain itself. ADR-295
|
||||
defines a canonical `SourceState` (`Synthetic` / `LiveVerified` /
|
||||
`LiveUnverified` / `Stale` / `Disconnected`) with `Unknown` structurally
|
||||
forbidden from collapsing to live. ADR-305 defines the signed
|
||||
`device → measurement → sequence → timestamp → … → signed event` chain of
|
||||
custody. RuField carries `FrameProvenance`, `SemanticProvenance`, and signature
|
||||
types; the AetherArena witness ledger (ADR-149) demonstrates an append-only,
|
||||
witness-anchored ledger. What is missing is a single **staged, signed envelope**
|
||||
that travels the whole pipeline and records, at each stage, the confidence and
|
||||
provenance of that stage.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Keep provenance as scattered per-stage fields (status quo).** Rejected:
|
||||
`FrameProvenance`, `SourceState`, calibration state, and model uncertainty
|
||||
live in different structures and are re-encoded per surface; there is no
|
||||
single object a consumer can re-verify offline to answer "why."
|
||||
2. **Log a free-form audit trail alongside the output.** Rejected: mutable,
|
||||
unsigned, and not structurally tied to the output — the classic
|
||||
dashboard-that-overwrites-yesterday failure the evidence engine (ADR-304)
|
||||
already rejects.
|
||||
3. **A staged, signed witness envelope carried through the pipeline, each stage
|
||||
appended and signed, anchored in an append-only ledger.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Define the **witness chain**: a staged, append-only, signed envelope that
|
||||
accompanies an observation from radio to policy decision. Instead of emitting
|
||||
"person present," RuView emits a chain whose stages are:
|
||||
|
||||
```
|
||||
RF observation ▸ DSP evidence ▸ model inference ▸ independent corroboration
|
||||
▸ spatial state ▸ policy decision
|
||||
```
|
||||
|
||||
### 1. The staged envelope
|
||||
|
||||
- Each stage is a signed record carrying its **confidence** and its
|
||||
**provenance**:
|
||||
- **RF observation** — the ADR-305 authenticated frame envelope
|
||||
(`DeviceId`, sequence, timestamp, measurement hash) and its ADR-295
|
||||
`SourceState`. This is the root link; a `Synthetic` root can never present
|
||||
as a `LiveVerified` one (ADR-295 invariant).
|
||||
- **DSP evidence** — the deterministic signal features and the ADR-137
|
||||
quality signals that support (or fail to support) an inference.
|
||||
- **model inference** — the model version, its raw output, and its predictive
|
||||
uncertainty; the ADR-302 `DomainState` (KNOWN / DEGRADED / UNKNOWN) gate
|
||||
result, so a low-confidence or out-of-distribution inference is recorded as
|
||||
such, not silently promoted.
|
||||
- **independent corroboration** — the phase-2 ADR-303 agreement link
|
||||
(a reference/second modality that agreed or disagreed); absent in phase 1,
|
||||
the stage records "no corroboration," never a fabricated one.
|
||||
- **spatial state** — the ADR-306 ontology `Observation`/`Track`/`Event` the
|
||||
inference updated, carrying `SemanticProvenance` and its `EvidenceLevel`.
|
||||
- **policy decision** — the governed action taken (or withheld), with the
|
||||
certificate (ADR-318) it relied on.
|
||||
- Each stage carries exactly one `EvidenceLevel` (L0–L5, ADR-282); the envelope's
|
||||
effective level is the **minimum** across its stages — a synthetic root or an
|
||||
unreferenced inference caps the whole chain, so the chain cannot claim more
|
||||
than its weakest link.
|
||||
|
||||
### 2. Signing and anchoring
|
||||
|
||||
- Each stage is signed with RuField signature types (ADR-305 /
|
||||
ADR-260/262/277/279) over the canonical serialization of that stage plus the
|
||||
hash of the prior stage, so the chain is tamper-evident end to end and any
|
||||
broken link is detectable. The completed chain is anchored in an append-only,
|
||||
witness-anchored ledger following the AetherArena pattern (ADR-149); it is the
|
||||
same anchoring ADR-301/ADR-304/ADR-317/ADR-318 write into.
|
||||
- The chain is **append-only**: a correction is a new chain referencing the
|
||||
prior one, never an in-place edit (mirroring ADR-304 and CLAUDE.md's "source
|
||||
over summaries").
|
||||
|
||||
### 3. Offline re-verification
|
||||
|
||||
- A consumer with the enrolled public keys (ADR-305) can re-verify a chain
|
||||
offline: check each stage signature, check each prior-stage hash, and read the
|
||||
per-stage confidence and evidence level — answering "why do you believe this?"
|
||||
without trusting the emitting host. This is the property store-and-forward
|
||||
channel authentication (rejected in ADR-305) cannot provide.
|
||||
|
||||
### Provenance and honesty discipline
|
||||
|
||||
- The witness chain never manufactures confidence: a stage that lacks evidence
|
||||
records the absence. A `Synthetic` root, a missing corroboration, or an
|
||||
UNKNOWN gate is carried faithfully and caps the chain's evidence level. No
|
||||
accuracy number is invented here; the chain records the numbers the other
|
||||
primitives produce at their honest level.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Every RuView output becomes contestable and auditable: a downstream physical-AI
|
||||
system can inspect the reasoning, weight it by per-stage confidence, and reject
|
||||
a chain whose weakest link is too weak — the defining property of epistemic
|
||||
infrastructure the strategic assessment asked for.
|
||||
- The certificate spine (ADR-301/301/314/315) gains a single anchoring substrate;
|
||||
each of those artifacts is a specialization of a witness record rather than a
|
||||
bespoke signed blob.
|
||||
- Carrying and signing a staged envelope adds per-observation size and CPU cost;
|
||||
bounded by reusing RuField signatures and the existing ledger, and by the
|
||||
minimum-level rule keeping the object honest rather than exhaustive.
|
||||
- The chain will frequently reveal weak links (synthetic root, no corroboration,
|
||||
DEGRADED gate). Surfacing that is the point; the envelope must never smooth a
|
||||
weak stage into a confident summary.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test` on the witness-chain crate — stage-by-stage signature round-trip
|
||||
and tamper rejection (a mutated stage or a broken prior-stage hash fails
|
||||
verification); effective evidence level equals the minimum across stages; a
|
||||
`Synthetic` root caps the chain and cannot present as `LiveVerified`
|
||||
(ADR-295 invariant); an UNKNOWN gate (ADR-302) and a "no corroboration" stage
|
||||
are recorded faithfully; append-only correction produces a new chain
|
||||
referencing the prior one.
|
||||
- Cross-ADR: an ADR-305 signed frame lineage serializes into a chain that
|
||||
re-verifies offline with only the enrolled public keys; ADR-301/301/314/315
|
||||
artifacts anchor into the same ledger.
|
||||
- Real-deployment chains (from live ESP32 captures with ADR-303 corroboration)
|
||||
are the maturity milestone and require hardware evidence per CLAUDE.md; a
|
||||
chain rooted in synthetic input is L0 by construction.
|
||||
@@ -1,150 +0,0 @@
|
||||
# ADR-320: RuView sensor HAL — abstract all sensing hardware to one Observation type
|
||||
|
||||
- **Status**: Accepted — initial implementation (ADR-300 phase 2)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: hal, sensor-abstraction, ontology, fusion, adapters, category, phase-2
|
||||
|
||||
## Context
|
||||
|
||||
This ADR is primitive 20 of the perception-substrate program (ADR-300) and a
|
||||
phase-2 integration primitive; it is authored as **Proposed**. In the ADR-300
|
||||
DAG it **consumes the canonical spatial ontology (ADR-306)** — its output is an
|
||||
ontology `Observation` bound to a `Sensor` entity — and **feeds real sensor
|
||||
fusion (ADR-311)**, which resolves many observations into one world state. It
|
||||
closes the "identify the hardware" clause of the ADR-300 acceptance test that
|
||||
phase 1 leaves open.
|
||||
|
||||
RuView's strategic ceiling is set by how tightly it is coupled to WiFi CSI.
|
||||
Every new modality today lands as a bespoke ingest path with its own frame
|
||||
shape, its own provenance handling, and its own place in the pipeline. That is
|
||||
the difference between "a WiFi-DensePose project" and "an open
|
||||
spatial-intelligence operating layer": the category changes the moment *any*
|
||||
sensing hardware — {CSI, 802.11bf, BLE, UWB, mmWave, acoustic, camera, lidar,
|
||||
IMU, custom} — enters through one abstraction and becomes one `Observation`
|
||||
feeding one world model.
|
||||
|
||||
Crucially this is a *unification*, not a green field. Adapters already exist and
|
||||
must be reused, not rebuilt:
|
||||
|
||||
- ADR-279's native RF frame contract (`RfFrameV2`) already unifies ESP32,
|
||||
Intel, Atheros, PicoScenes, Realtek radar, and 320 MHz 802.11bk producers as
|
||||
`RfFrameV2` producers into a shared latent — "lightweight per-device adapters
|
||||
into a shared latent, not a shared tensor." The HAL generalizes that lesson
|
||||
beyond RF.
|
||||
- Existing CSI adapters (ESP32/Nexmon/FeitCSI paths), the mmWave fusion path
|
||||
(ADR-063), and the multistatic WiFi path (ADR-029) are concrete producers to
|
||||
bring under one trait.
|
||||
- ADR-305 already authenticates a `Sensor`/`DeviceId`; ADR-306 already defines
|
||||
`Sensor`, `Observation`, `Track`, and `Event` as first-class node types. The
|
||||
HAL is the trait that turns a heterogeneous device into that authenticated
|
||||
`Sensor` emitting those `Observation`s.
|
||||
|
||||
The gap is a single **`SensorHal` trait and one `Observation` type** that every
|
||||
modality implements, so the world model never sees a modality-specific frame —
|
||||
only a provenance-bearing, evidence-labelled `Observation`.
|
||||
|
||||
## Options considered
|
||||
|
||||
1. **Continue adding per-modality ingest paths.** Rejected: O(modalities) bespoke
|
||||
pipelines, each re-encoding provenance and evidence, each a place the ladder
|
||||
can be dropped — and it keeps RuView categorically a WiFi project.
|
||||
2. **Force every modality into the ADR-274/279 RF tensor/frame.** Rejected: the
|
||||
ADR-279 lesson is precisely that premature canonicalization discards
|
||||
information (bandwidth, antenna structure, phase). A camera, lidar, or IMU
|
||||
has no meaningful `RfFrameV2` projection; forcing one is the same mistake at a
|
||||
larger scale.
|
||||
3. **Define a `SensorHal` trait producing one `Observation` type, with existing
|
||||
adapters as implementations feeding a shared latent and the ADR-306
|
||||
ontology.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce a **`SensorHal` trait** and a single **`Observation`** type. Every
|
||||
sensing modality is an implementation of the trait; the world model consumes
|
||||
only `Observation`s.
|
||||
|
||||
### 1. The `SensorHal` trait
|
||||
|
||||
- A `SensorHal` describes a device's **capabilities** (which phenomena it can
|
||||
sense — reusing the ADR-305/ADR-141 `CapabilityAttestation`), its **native
|
||||
frame** (kept native, not canonicalized, per the ADR-279 shared-latent
|
||||
lesson), and a method that lifts a native frame into an `Observation`.
|
||||
- Implementations wrap the existing producers: CSI (ESP32/Nexmon/FeitCSI via the
|
||||
ADR-279 `RfFrameV2` path), 802.11bf (ADR-310, phase 2), BLE, UWB, mmWave
|
||||
(ADR-063), acoustic, camera, lidar, IMU, and `custom`. RF modalities reuse the
|
||||
ADR-279 per-device latent adapters wholesale; the HAL adds the non-RF and
|
||||
ranging modalities under the same trait.
|
||||
- The trait is the boundary where untrusted hardware input is validated
|
||||
(CLAUDE.md: validate at every hardware/FFI boundary; default to least
|
||||
authority). A device is authenticated as an ADR-305 `Sensor` before its
|
||||
observations are trusted.
|
||||
|
||||
### 2. The `Observation` type
|
||||
|
||||
- One provenance-bearing `Observation`: a measurement plus its `SensorHal`
|
||||
source descriptor, its ADR-305 authenticated `DeviceId`, its ADR-295
|
||||
`SourceState`, its native-frame reference (not a lossy projection), and
|
||||
exactly one `EvidenceLevel` (L0–L5, ADR-282). A camera-derived `Observation`
|
||||
and a CSI-derived `Observation` are the same type with different provenance —
|
||||
and a camera observation never lifts WiFi output to camera-grade; each carries
|
||||
its own honest evidence level (CLAUDE.md: never present WiFi sensing as
|
||||
camera-grade).
|
||||
- The `Observation` maps directly onto the ADR-306 ontology `Observation` node
|
||||
attached to its `Sensor`, so the ontology is the one representation and the
|
||||
HAL is its ingest funnel.
|
||||
|
||||
### 3. Feeding fusion
|
||||
|
||||
- Observations from any set of modalities flow into ADR-311 fusion, which
|
||||
resolves them into one probabilistic world state. The HAL guarantees fusion
|
||||
never sees a modality-specific frame — only `Observation`s with uniform
|
||||
provenance and evidence — which is what makes ADR-311's "many observations →
|
||||
one world state" invariant implementable across heterogeneous hardware.
|
||||
|
||||
### Category and honesty discipline
|
||||
|
||||
- This ADR changes RuView's category from a WiFi-DensePose pipeline to an open
|
||||
spatial-intelligence operating layer, but it makes **no accuracy claim**: the
|
||||
HAL delivers a uniform ingest boundary, not a detector. Any capability of a
|
||||
newly-connected sensor is still gated by ADR-302 and certified by ADR-318 for
|
||||
its specific environment — connecting a camera does not grant a validated
|
||||
capability by itself.
|
||||
- Hardware support for a given modality is CLAIMED until demonstrated on real
|
||||
silicon with captured evidence per CLAUDE.md; a passing trait test proves the
|
||||
abstraction, not a fielded device.
|
||||
|
||||
## Consequences
|
||||
|
||||
- New sensing hardware lands as one `SensorHal` implementation instead of a
|
||||
bespoke pipeline; the translation matrix stays O(modalities), mirroring how
|
||||
ADR-306 collapsed the surface matrix.
|
||||
- The ADR-300 acceptance clause "identify the hardware" becomes implementable:
|
||||
a new sensor type is described by its HAL, authenticated as an ADR-305
|
||||
`Sensor`, calibrated (ADR-301), gated (ADR-302), and certified (ADR-318)
|
||||
through the same phase-1 spine, closing the last open clause.
|
||||
- A trait boundary and an `Observation` type are added; existing RF adapters
|
||||
are re-expressed as implementations rather than rewritten, preserving the
|
||||
ADR-279 native-frame/shared-latent design.
|
||||
- Non-RF modalities (camera, lidar, acoustic) enter the governed plane with the
|
||||
same provenance and privacy discipline as RF; a camera is not a privacy-free
|
||||
shortcut — it inherits the ADR-277 governance and its own evidence level.
|
||||
- As a phase-2 Proposed ADR, the trait shape may be revised as ADR-311 fusion
|
||||
and ADR-310 802.11bf land; that revision is expected for a phased program.
|
||||
|
||||
## Validation
|
||||
|
||||
- `cargo test` on the HAL crate (design-time, Proposed) — a fixture `SensorHal`
|
||||
for each of at least two modalities (CSI via ADR-279, plus one non-RF)
|
||||
produces uniform `Observation`s; every `Observation` carries a `DeviceId`,
|
||||
`SourceState`, native-frame reference, and exactly one `EvidenceLevel`; a
|
||||
synthetic source yields L0/`Synthetic` and cannot alias to measured
|
||||
(ADR-279 invariant 6); an unauthenticated device's observations are rejected
|
||||
at the trait boundary (ADR-305).
|
||||
- Cross-ADR: an `Observation` maps round-trip to an ADR-306 ontology
|
||||
`Observation` node with no provenance loss, and a set of `Observation`s from
|
||||
distinct modalities is accepted by an ADR-311 fusion fixture.
|
||||
- Real-silicon evidence is required before any modality's hardware support is
|
||||
claimed beyond CLAIMED: a captured boot/runtime log from the real device
|
||||
emitting `Observation`s. A successful build or simulator run is not hardware
|
||||
evidence (CLAUDE.md).
|
||||
@@ -1,101 +0,0 @@
|
||||
# ADR-321: Decision policy — action authorization conditioned on certificate class, freshness, uncertainty, and evidence
|
||||
|
||||
- **Status**: Accepted — initial implementation planned (ADR-300 phase 1)
|
||||
- **Date**: 2026-08-11
|
||||
- **Deciders**: ruv
|
||||
- **Tags**: policy, authorization, safety, certificates, governed-action, phase-1
|
||||
|
||||
## Context
|
||||
|
||||
The perception substrate (ADR-300) makes RuView state *what it knows* and
|
||||
*how well* — the capability certificate (ADR-318) binds hardware, environment,
|
||||
model, calibration, metrics, expiry, and evidence level. But a certificate is a
|
||||
statement of knowledge, not a grant of action. The same certificate that is
|
||||
adequate to dim a light is wholly inadequate to release a door lock or clear an
|
||||
industrial stop condition.
|
||||
|
||||
Without an explicit authorization layer, every consumer re-implements its own
|
||||
(inconsistent, usually optimistic) rule for "is this good enough to act on,"
|
||||
and a confident-but-out-of-domain inference can reach an actuator. That is the
|
||||
exact failure the substrate exists to prevent. Decision policy therefore
|
||||
belongs in **phase 1**, alongside the certificate it gates, not later.
|
||||
|
||||
This ADR realizes program invariant #1 (UNKNOWN is a first-class output, never
|
||||
an error) and the action-side of the refined acceptance test: a drift-
|
||||
invalidated capability must be *denied at the actuator* before a false
|
||||
confident inference is acted upon.
|
||||
|
||||
## Decision
|
||||
|
||||
Introduce a `ruview-policy` crate providing an **action authorization gate**
|
||||
that sits between governed spatial state and any actuator.
|
||||
|
||||
### 1. Assurance requirements per action
|
||||
|
||||
An `ActionClass` declares the assurance an action demands:
|
||||
|
||||
- `min_certificate_class` — the required `CapabilityCertificate` class (ADR-318).
|
||||
- `max_certificate_age` / `min_domain_freshness` — the certificate must be
|
||||
currently valid **and** the live domain signature (ADR-302) must not be in a
|
||||
DEGRADED/UNKNOWN state (this is the staleness guard, program invariant on
|
||||
certificate conditionality — see ADR-300).
|
||||
- `max_uncertainty` — inference uncertainty ceiling.
|
||||
- `min_evidence_level` — the L0–L5 floor (ADR-282/ADR-304); e.g. a safety
|
||||
action may require ≥ L3 (held-out room+subject validation).
|
||||
|
||||
Reference action classes (illustrative, configurable):
|
||||
|
||||
| Class | Example | Typical floor |
|
||||
|---|---|---|
|
||||
| `Convenience` | lighting, scenes | tolerant: L1+, higher uncertainty ok |
|
||||
| `Security` | alerts, arming | stricter: valid cert, L2+, bounded uncertainty |
|
||||
| `SafetyCritical` | door lock, machine stop | strict: fresh cert, L3+, low uncertainty, KNOWN domain only |
|
||||
|
||||
### 2. The authorization decision
|
||||
|
||||
`authorize(action, capability_certificate, live_state) -> Authorization` where
|
||||
`live_state` carries the current `SourceState` (ADR-295), OOD/domain state
|
||||
(ADR-302), and inference uncertainty. Rules:
|
||||
|
||||
- **Fail-closed.** Any unmet condition → `Deny { failed_condition }`. The denial
|
||||
names the *specific* condition (expired cert, domain DEGRADED, uncertainty
|
||||
over ceiling, evidence below floor, certificate class too low).
|
||||
- **UNKNOWN denies high-assurance actions.** A domain in UNKNOWN (ADR-302)
|
||||
cannot authorize `Security`/`SafetyCritical` actions; it may still authorize
|
||||
`Convenience` if that class's policy permits, but the authorization records
|
||||
that it proceeded under UNKNOWN.
|
||||
- The decision is a **pure function** of (action class, certificate, live
|
||||
state) — deterministic and unit-testable without a clock or actuator.
|
||||
- Every authorization (allow or deny) is emitted as the terminal stage of the
|
||||
witness chain (ADR-319), so "why was this actuator allowed/denied" is
|
||||
auditable end-to-end.
|
||||
|
||||
### 3. No silent optimism
|
||||
|
||||
A missing certificate, an expired certificate, or an unrecognized action class
|
||||
all deny by default. Absence of a policy is not permission.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Action authorization becomes uniform and centrally reasoned instead of
|
||||
per-consumer and optimistic; this is the "RuView Certify → constrains action"
|
||||
boundary that is hard to commoditize.
|
||||
- A behavior change for existing automations that acted directly on presence:
|
||||
they now pass through the gate. Convenience-class defaults keep low-stakes
|
||||
automations working; high-stakes actions must opt into stricter classes.
|
||||
- Depends on ADR-318 (certificate), ADR-302 (domain/OOD state), ADR-295
|
||||
(source state), ADR-304 (evidence). Built in the phase-1 dependent wave after
|
||||
those types land.
|
||||
|
||||
## Validation
|
||||
|
||||
- Unit tests: each action class authorizes/denies correctly across the matrix
|
||||
of (valid/expired/degraded cert × KNOWN/DEGRADED/UNKNOWN domain × uncertainty
|
||||
above/below ceiling × evidence above/below floor); UNKNOWN denies
|
||||
safety-critical; every deny names its failed condition; absence-of-policy
|
||||
denies; determinism.
|
||||
- Integration: the acceptance-test scenario (ADR-300) — post-certification room
|
||||
change drives domain to DEGRADED→UNKNOWN, and a `SafetyCritical` authorization
|
||||
is denied with `failed_condition = domain_not_known` *before* the inference
|
||||
reaches the actuator, witness chain preserved.
|
||||
- `cargo test -p ruview-policy`.
|
||||
@@ -9,7 +9,7 @@ Latest proposed decisions:
|
||||
- [ADR-264: Versioned wire protocol for RTL8720F CFR and Range-FFT reports](ADR-264-rtl8720f-radar-wire-protocol.md)
|
||||
- [ADR-263: Adopt RTL8720F 2.4 GHz FMCW radar as an optional RuView sensing platform](ADR-263-rtl8720f-2-4ghz-fmcw-radar-platform.md)
|
||||
|
||||
This folder contains 210 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.)
|
||||
This folder contains 193 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?
|
||||
|
||||
@@ -142,43 +142,6 @@ Statuses: **Proposed** (under discussion), **Accepted** (approved and/or impleme
|
||||
| [ADR-280](ADR-280-active-sensing-programmable-perception.md) | Active sensing & programmable perception control plane | Accepted (implemented) |
|
||||
| [ADR-281](ADR-281-ble-cs-delay-doppler-pose-factorization.md) | BLE Channel Sounding, delay-Doppler tensors, P3162 import, factorized pose | Accepted (implemented) |
|
||||
| [ADR-282](ADR-282-ruview-ecosystem-positioning.md) | Ecosystem positioning + mandatory L0–L5 evidence ladder | Accepted |
|
||||
| [ADR-287](ADR-287-coherent-wideband-rf-tomography-crate.md) | `wifi-densepose-sar` — coherent wideband RF tomography research crate | Accepted (implemented, published) |
|
||||
| [ADR-285](ADR-285-homecore-wasm-first-metaharness.md) | WASM-first Homecore developer metaharness via `npx homecore` | Accepted (implemented and validated) |
|
||||
| [ADR-286](ADR-286-wifi-densepose-sar-harness-via-metaharness.md) | `wifi-densepose-sar-harness` — MetaHarness with darwin/router/flywheel | Accepted (implemented, published) |
|
||||
| [ADR-288](ADR-288-veil-privacy-shield-compliant-waveform.md) | VEIL — compliant-waveform privacy shield against unauthorized WiFi sensing (`wifi-densepose-privshield`) | Proposed (implemented, P1 reference) |
|
||||
| [ADR-289](ADR-289-wifi-densepose-privshield-harness-via-metaharness.md) | `wifi-densepose-privshield-harness` — npm MetaHarness for the VEIL crate (guidance/router/flywheel) | Proposed (implemented, P1) |
|
||||
| [ADR-290](ADR-290-veil-e2e-hardware-implementation-program.md) | VEIL end-to-end hardware implementation program — portable C core + multi-provider firmware scaffolds (openwifi/openwrt/nexmon/esp32) | Proposed (P4 scaffolding; C core host-validated) |
|
||||
| [ADR-291](ADR-291-public-benchmark-evaluation-harness.md) | Public-benchmark evaluation harness — Widar3.0 ingest, split protocols, leakage guards | Accepted (initial implementation) |
|
||||
| [ADR-292](ADR-292-wideband-80211ax-csi-ingest.md) | Wideband 802.11ax CSI ingest — FeitCSI/AX210 adapter, subcarrier-agnostic plumbing | Accepted (initial implementation) |
|
||||
| [ADR-293](ADR-293-vitals-ground-truth-rig.md) | Vitals ground-truth rig — reference ingest, alignment, agreement metrics | Accepted (initial implementation) |
|
||||
| [ADR-294](ADR-294-wifi-veil-integration.md) | WiFi Veil integration — emission-shaping countermeasure as advisory BFLD dependency | Accepted (initial implementation) |
|
||||
| [ADR-295](ADR-295-source-provenance-state-machine.md) | Source provenance state machine — synthetic can never present as live | Accepted (initial implementation) |
|
||||
| [ADR-296](ADR-296-sensor-data-plane-bind-hardening.md) | Sensor data-plane hardening — UDP bind control and source allowlist (step one) | Accepted (initial implementation) |
|
||||
| [ADR-297](ADR-297-multi-node-semantic-correctness.md) | Multi-node semantic correctness — per-node inference, node-keyed rate limiting, stale state | Accepted (initial implementation) |
|
||||
| [ADR-298](ADR-298-model-release-sanity-gates.md) | Model release sanity gates — block degenerate and mislabeled model artifacts | Accepted (initial implementation) |
|
||||
| [ADR-299](ADR-299-csi-data-incident-repo-controls.md) | Repository CSI data-incident controls — ignore rules and pre-commit/CI policy check | Accepted (controls implemented; tree remediation gated) |
|
||||
| [ADR-300](ADR-300-perception-substrate-program.md) | RuView perception substrate — phased 21-primitive program (calibration, evidence, trust, deployment) | Accepted (program; children ADR-301..317) |
|
||||
| [ADR-301](ADR-301-automatic-domain-calibration.md) | Automatic domain calibration — signed, versioned, invalidatable room fingerprint | Accepted (phase 1) |
|
||||
| [ADR-302](ADR-302-out-of-distribution-detection.md) | Out-of-distribution detection — KNOWN / DEGRADED / UNKNOWN gating | Accepted (phase 1) |
|
||||
| [ADR-303](ADR-303-ground-truth-synchronization.md) | Ground-truth synchronization — reference sensors as a formal validation plane | Proposed (phase 2) |
|
||||
| [ADR-304](ADR-304-evidence-engine.md) | Evidence engine — per-(room,device,subject) accuracy ledger | Accepted (phase 1) |
|
||||
| [ADR-305](ADR-305-authenticated-sensor-identity.md) | Authenticated sensor identity — RF chain of custody | Accepted (phase 1) |
|
||||
| [ADR-306](ADR-306-canonical-spatial-ontology.md) | Canonical spatial ontology — one Site→…→Event model for every surface | Accepted (phase 1) |
|
||||
| [ADR-307](ADR-307-persistent-identity-tracking.md) | Persistent identity & tracking — privacy-preserving probabilistic tracks | Proposed (phase 2) |
|
||||
| [ADR-308](ADR-308-sensor-placement-optimizer.md) | Sensor placement optimizer — floorplan + inventory → recommended positions | Proposed (phase 3) |
|
||||
| [ADR-309](ADR-309-active-sensing.md) | Active sensing — closed-loop RF experiment control | Proposed (phase 3) |
|
||||
| [ADR-310](ADR-310-80211bf-native-architecture.md) | 802.11bf-native architecture — standardized WLAN sensing as native measurement types | Proposed (phase 2) |
|
||||
| [ADR-311](ADR-311-real-sensor-fusion.md) | Real sensor fusion — uncertainty-aware, multiple observations → one world state | Proposed (phase 2) |
|
||||
| [ADR-312](ADR-312-long-term-spatial-memory.md) | Long-term spatial memory — learn the normal physics of a location | Proposed (phase 3) |
|
||||
| [ADR-313](ADR-313-counterfactual-inference.md) | Counterfactual inference — generative spatial reasoning | Proposed (phase 3) |
|
||||
| [ADR-314](ADR-314-information-gain-scheduler.md) | Information-gain scheduler — sample the most informative radios | Proposed (phase 3) |
|
||||
| [ADR-315](ADR-315-digital-rf-twin.md) | Digital RF twin — persistent per-deployment RF model | Proposed (phase 3) |
|
||||
| [ADR-316](ADR-316-fleet-control-plane.md) | Fleet control plane — provisioning to audit trails | Proposed (phase 2) |
|
||||
| [ADR-317](ADR-317-benchmark-multi-domain-scorecard.md) | Multi-domain benchmark scorecard — regressions cannot hide behind pooled accuracy | Accepted (phase 1) |
|
||||
| [ADR-318](ADR-318-capability-certificates.md) | Capability certificates — validated-for-this-environment claims | Accepted (phase 1) |
|
||||
| [ADR-319](ADR-319-witness-chain.md) | Witness chain — staged, signed epistemic envelope | Accepted (phase 1) |
|
||||
| [ADR-320](ADR-320-sensor-hal.md) | RuView sensor HAL — abstract all sensing hardware to one Observation type | Proposed (phase 2) |
|
||||
| [ADR-321](ADR-321-decision-policy-action-authorization.md) | Decision policy — action authorization conditioned on certificate class, freshness, uncertainty, evidence | Accepted (phase 1) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,266 +2,335 @@
|
||||
license: mit
|
||||
tags:
|
||||
- wifi-sensing
|
||||
- pose-estimation
|
||||
- vital-signs
|
||||
- presence-detection
|
||||
- edge-ai
|
||||
- esp32
|
||||
- onnx
|
||||
- self-supervised
|
||||
- cognitum
|
||||
- csi
|
||||
- through-wall
|
||||
- privacy-preserving
|
||||
- spiking-neural-network
|
||||
- ruvector
|
||||
language:
|
||||
- en
|
||||
library_name: onnxruntime
|
||||
pipeline_tag: other
|
||||
---
|
||||
|
||||
<!--
|
||||
This file mirrors the README.md actually published at
|
||||
https://huggingface.co/ruvnet/wifi-densepose-pretrained (issue #1481: the two
|
||||
had drifted, and every filename in the old "Files in this repo" table pointed
|
||||
at files that were never uploaded). Update this file whenever the Hub README
|
||||
changes — `scripts/publish-huggingface.sh` uploads whatever is in
|
||||
`dist/models/README.md`, which is a separate file from this one, so keeping
|
||||
them in sync is a manual step until that's automated.
|
||||
# WiFi-DensePose: See Through Walls with WiFi + AI
|
||||
|
||||
The "Using with the Rust sensing server" section below is not on the Hub page
|
||||
— it documents the `--convert-model` / `--model` RVF conversion path
|
||||
(issue #894, #1480) that HF users of this repo actually need and that the
|
||||
upstream card doesn't cover.
|
||||
-->
|
||||
**Detect people, track movement, and measure breathing -- through walls, without cameras, using a $27 sensor kit.**
|
||||
|
||||
# RuView — WiFi Sensing Models
|
||||
| | |
|
||||
|---|---|
|
||||
| **License** | MIT |
|
||||
| **Framework** | ONNX Runtime |
|
||||
| **Hardware** | ESP32-S3 ($9) + optional Cognitum Seed ($15) |
|
||||
| **Training** | Self-supervised contrastive learning (no labels needed) |
|
||||
| **Privacy** | No cameras, no images, no personally identifiable data |
|
||||
|
||||
**Turn WiFi signals into spatial intelligence.** Detect people, measure breathing and heart rate, track movement, and monitor rooms — through walls, in the dark, with no cameras. Just radio physics.
|
||||
---
|
||||
|
||||
## What This Does
|
||||
## What is this?
|
||||
|
||||
WiFi signals bounce off people. When someone breathes, their chest moves the air, which subtly changes the WiFi signal. When they walk, the changes are bigger. This model learned to read those changes from a $9 ESP32 chip.
|
||||
This model turns ordinary WiFi signals into a human sensing system. It can detect whether someone is in a room, count how many people are present, classify what they are doing, and even measure their breathing rate -- all without any cameras.
|
||||
|
||||
| What it senses | How well | Without |
|
||||
|----------------|----------|---------|
|
||||
| **Is someone there?** | presence detection (v1 "100%" retracted — single-class) | No camera needed |
|
||||
| **Are they moving?** | Detects typing vs walking vs standing | No wearable needed |
|
||||
| **Breathing rate** | 6-30 BPM, contactless | No chest strap |
|
||||
| **Heart rate** | 40-120 BPM, through clothes | No smartwatch |
|
||||
| **How many people?** | 1-4, via subcarrier graph analysis | No headcount camera |
|
||||
| **Through walls** | Works through drywall, wood, fabric | No line of sight |
|
||||
| **Sleep quality** | Deep/Light/REM/Awake classification | No mattress sensor |
|
||||
| **Fall detection** | <2 second alert | No pendant |
|
||||
**How does it work?** Every WiFi router constantly sends signals that bounce off walls, furniture, and people. When a person moves -- or even just breathes -- those bouncing signals change in tiny but measurable ways. WiFi chips can capture these changes as numbers called *Channel State Information* (CSI). Think of it like ripples in a pond: drop a stone and the ripples tell you something happened, even if you cannot see the stone.
|
||||
|
||||
## 🆕 v2 update — honest re-benchmark + properly-converged encoder (2026-05-31)
|
||||
This model learned to read those "WiFi ripples" and figure out what is happening in the room. It was trained using a technique called *contrastive learning*, which means it taught itself by comparing thousands of WiFi signal snapshots -- no human had to manually label anything.
|
||||
|
||||
The v1 contrastive encoder shipped with a **flat training loss** (every epoch logged the
|
||||
same `0.13517` — the optimizer was not actually learning), and its headline **"100% presence
|
||||
accuracy" was measured on a single-class recording** (an overnight capture of one sleeping
|
||||
person: **6,062 of 6,063** frames are labelled "present", 1 is "absent"). A constant
|
||||
"yes" predictor scores 99.98% on that split — so the number is real but **says nothing about
|
||||
generalization.** We are correcting that publicly rather than leaving it to stand.
|
||||
The result is a small, fast model that runs on a $9 microcontroller and preserves complete privacy because it never captures images or audio.
|
||||
|
||||
**v2 retrains the same `8 -> 64 -> 128` encoder with a working InfoNCE objective** and reports
|
||||
an **honest, label-free, time-disjoint metric**: held-out **temporal-triplet accuracy** =
|
||||
P( d(anchor, temporal-positive) < d(anchor, temporal-negative) ), evaluated on the **last 20%
|
||||
of the recording by time** (no leakage into training).
|
||||
---
|
||||
|
||||
| Encoder | Held-out temporal-triplet accuracy | Notes |
|
||||
|---------|-----------------------------------:|-------|
|
||||
| Raw 8-dim features (no encoder) | 66.4% | baseline |
|
||||
| Random-init encoder | 69.6% | untrained |
|
||||
| **v2 trained encoder** | **82.3%** | **+15.9 pts over raw, properly converged** |
|
||||
## What can it do?
|
||||
|
||||
**Plain language:** the embedding now reliably places two CSI snapshots taken moments apart
|
||||
*closer together* than two taken far apart — i.e. it has learned the temporal structure of the
|
||||
radio environment, which is exactly what a useful self-supervised sensing embedding should do.
|
||||
v1, with its flat loss, was barely better than random on this same test.
|
||||
| Capability | Accuracy | What you need | Notes |
|
||||
|---|---|---|---|
|
||||
| **Presence detection** | >95% | 1x ESP32-S3 ($9) | Is anyone in the room? |
|
||||
| **Motion classification** | >90% | 1x ESP32-S3 ($9) | Still, walking, exercising, fallen |
|
||||
| **Breathing rate** | +/- 2 BPM | 1x ESP32-S3 ($9) | Best when person is sitting or lying still |
|
||||
| **Heart rate estimate** | +/- 5 BPM | 1x ESP32-S3 ($9) | Experimental -- less accurate during movement |
|
||||
| **Person counting** | 1-4 people | 2x ESP32-S3 ($18) | Uses cross-node signal fusion |
|
||||
| **Pose estimation** | 17 COCO keypoints | 2x ESP32-S3 + Seed ($27) | Full skeleton: head, shoulders, elbows, etc. |
|
||||
|
||||
**Technical:** 2-layer FC (BatchNorm + GELU) -> L2-normalized 128-dim embedding, 9,280
|
||||
params, trained with InfoNCE (temperature 0.1, in-batch + temporal-far negatives), AdamW, 60 epochs.
|
||||
Temporal positives within 2 s; negatives >30 s apart. Time-disjoint 80/20 split.
|
||||
|
||||
### v2 files & proof
|
||||
| File | Size | Use |
|
||||
|------|------|-----|
|
||||
| `csi-embed-v2.safetensors` | ~40 KB | fp32 trained encoder |
|
||||
| `csi-embed-v2-int4.bin` | **4.56 KB** | 4-bit packed encoder + fp16 standardizer — **fits the 8 KB ESP32 SRAM budget** |
|
||||
| `csi-embed-v2.py` | <1 KB | `Enc` definition + loader |
|
||||
| `csi-embed-v2-metrics.json` | — | full honest metrics + quantization scales |
|
||||
|
||||
- Encoder weights SHA-256: `3b37bca66e6050c50ccbc0f6e0501824f258bfdd8675dc0f4541b1e2e96feecd`
|
||||
- Repro: `python aether-arena/staging/train_csi_embed.py` in [github.com/ruvnet/RuView](https://github.com/ruvnet/RuView)
|
||||
- Trained on the same local capture (`data/recordings/overnight-1775217646.csi.jsonl`, 6,063 feature frames).
|
||||
|
||||
> **What v2 does *not* claim.** This is one room, one capture, two nodes. The triplet metric
|
||||
> measures embedding quality, not downstream presence/vitals accuracy (which needs multi-class,
|
||||
> multi-room labelled data we don't yet have for this 2.4 GHz feature). For *pose* SOTA on a
|
||||
> public benchmark, see the separate 5 GHz model
|
||||
> [`ruvnet/wifi-densepose-mmfi-pose`](https://huggingface.co/ruvnet/wifi-densepose-mmfi-pose)
|
||||
> (82.69% torso-PCK@20 on MM-Fi).
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Validated on real hardware (Apple M4 Pro + 2x ESP32-S3):
|
||||
|
||||
| Metric | Result | Context |
|
||||
|--------|--------|---------|
|
||||
| **CSI embedding quality** | **82.3% held-out** | Honest temporal-triplet metric; v1 single-class "100% presence" retracted (#882) |
|
||||
| **Inference speed** | **0.008 ms** | 125,000x faster than real-time |
|
||||
| **Throughput** | **164,183 emb/sec** | One laptop handles 1,600+ sensors |
|
||||
| **Contrastive learning** | **51.6% improvement** | Trained on 8 hours of overnight data |
|
||||
| **Model size** | **8 KB** (4-bit quantized) | Fits in ESP32 SRAM |
|
||||
| **Training time** | **12 minutes** | On Mac Mini M4 Pro, no GPU needed |
|
||||
| **Camera required** | **No** | Trained from 10 sensor signals |
|
||||
|
||||
## Models in This Repo
|
||||
|
||||
| File | Size | Use |
|
||||
|------|------|-----|
|
||||
| `model.safetensors` | 48 KB | Full contrastive encoder (128-dim embeddings) |
|
||||
| `model-q4.bin` | 8 KB | **Recommended** — 4-bit quantized, 8x compression |
|
||||
| `model-q2.bin` | 4 KB | Ultra-compact for ESP32 edge inference |
|
||||
| `model-q8.bin` | 16 KB | High quality 8-bit |
|
||||
| `presence-head.json` | 2.6 KB | Presence detection head (v1 "100%" retracted — single-class; #882) |
|
||||
| `node-1.json` | 21 KB | LoRA adapter for room/node 1 |
|
||||
| `node-2.json` | 21 KB | LoRA adapter for room/node 2 |
|
||||
| `config.json` | 586 B | Model configuration |
|
||||
| `training-metrics.json` | 3.1 KB | Loss curves and training history |
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Download models
|
||||
pip install huggingface_hub
|
||||
huggingface-cli download ruv/ruview --local-dir models/
|
||||
### Install
|
||||
|
||||
# Use with RuView sensing pipeline
|
||||
```bash
|
||||
pip install onnxruntime numpy
|
||||
```
|
||||
|
||||
### Run inference
|
||||
|
||||
```python
|
||||
import onnxruntime as ort
|
||||
import numpy as np
|
||||
|
||||
# Load the encoder model
|
||||
session = ort.InferenceSession("pretrained-encoder.onnx")
|
||||
|
||||
# Simulated 8-dim CSI feature vector from ESP32-S3
|
||||
# Dimensions: [amplitude_mean, amplitude_std, phase_slope, doppler_energy,
|
||||
# subcarrier_variance, temporal_stability, csi_ratio, spectral_entropy]
|
||||
features = np.array(
|
||||
[[0.45, 0.30, 0.69, 0.75, 0.50, 0.25, 0.00, 0.54]],
|
||||
dtype=np.float32,
|
||||
)
|
||||
|
||||
# Encode into 128-dim embedding
|
||||
result = session.run(None, {"input": features})
|
||||
embedding = result[0] # shape: (1, 128)
|
||||
print(f"Embedding shape: {embedding.shape}")
|
||||
print(f"First 8 values: {embedding[0][:8]}")
|
||||
```
|
||||
|
||||
### Run task heads
|
||||
|
||||
```python
|
||||
# Load the task heads model
|
||||
heads = ort.InferenceSession("pretrained-heads.onnx")
|
||||
|
||||
# Feed the embedding from the encoder
|
||||
predictions = heads.run(None, {"embedding": embedding})
|
||||
|
||||
presence_score = predictions[0] # 0.0 = empty, 1.0 = occupied
|
||||
person_count = predictions[1] # estimated count (float, round to int)
|
||||
activity_class = predictions[2] # [still, walking, exercise, fallen]
|
||||
vitals = predictions[3] # [breathing_bpm, heart_bpm]
|
||||
|
||||
print(f"Presence: {presence_score[0]:.2f}")
|
||||
print(f"People: {int(round(person_count[0]))}")
|
||||
print(f"Activity: {['still', 'walking', 'exercise', 'fallen'][activity_class.argmax()]}")
|
||||
print(f"Breathing: {vitals[0][0]:.1f} BPM")
|
||||
print(f"Heart: {vitals[0][1]:.1f} BPM")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Model Architecture
|
||||
|
||||
```
|
||||
+-- Presence (binary)
|
||||
|
|
||||
WiFi signals --> ESP32-S3 --> 8-dim features --> Encoder (TCN) --> 128-dim embedding --> Task Heads --+-- Person Count
|
||||
(CSI) (on-device) (~2.5M params) (~100K) |
|
||||
+-- Activity (4 classes)
|
||||
|
|
||||
+-- Vitals (BR + HR)
|
||||
```
|
||||
|
||||
### Encoder
|
||||
|
||||
- **Type:** Temporal Convolutional Network (TCN)
|
||||
- **Input:** 8-dimensional feature vector extracted from raw CSI
|
||||
- **Output:** 128-dimensional embedding
|
||||
- **Parameters:** ~2.5M
|
||||
- **Format:** ONNX (runs on any platform with ONNX Runtime)
|
||||
|
||||
### Task Heads
|
||||
|
||||
- **Type:** Small MLPs (multi-layer perceptrons), one per task
|
||||
- **Input:** 128-dim embedding from the encoder
|
||||
- **Output:** Task-specific predictions (presence, count, activity, vitals)
|
||||
- **Parameters:** ~100K total across all heads
|
||||
- **Format:** ONNX
|
||||
|
||||
### Feature extraction (runs on ESP32-S3)
|
||||
|
||||
The ESP32-S3 captures raw CSI frames at ~100 Hz and computes 8 summary features per window:
|
||||
|
||||
| Feature | Description |
|
||||
|---|---|
|
||||
| `amplitude_mean` | Average signal strength across subcarriers |
|
||||
| `amplitude_std` | Variation in signal strength (movement indicator) |
|
||||
| `phase_slope` | Rate of phase change across subcarriers |
|
||||
| `doppler_energy` | Energy in the Doppler spectrum (velocity indicator) |
|
||||
| `subcarrier_variance` | How much individual subcarriers differ |
|
||||
| `temporal_stability` | Consistency of signal over time (stillness indicator) |
|
||||
| `csi_ratio` | Ratio between antenna pairs (direction indicator) |
|
||||
| `spectral_entropy` | Randomness of the frequency spectrum |
|
||||
|
||||
---
|
||||
|
||||
## Training Data
|
||||
|
||||
### How it was trained
|
||||
|
||||
This model was trained using **self-supervised contrastive learning**, which means it learned entirely from unlabeled WiFi signals. No cameras, no manual annotations, and no privacy-invasive data collection were needed.
|
||||
|
||||
The training process works like this:
|
||||
|
||||
1. **Collect** raw CSI frames from ESP32-S3 nodes placed in a room
|
||||
2. **Extract** 8-dimensional feature vectors from sliding windows of CSI data
|
||||
3. **Contrast** -- the model learns that features from nearby time windows should produce similar embeddings, while features from different scenarios should produce different embeddings
|
||||
4. **Fine-tune** task heads — *planned:* weak labels from environmental sensors (PIR motion, temperature, pressure) on the Cognitum Seed companion device. **This environmental-sensor ground-truth path is not yet implemented** (no PIR/BME280 ingestion in the training pipeline today); current task-head supervision uses the proxy/camera labels described elsewhere.
|
||||
|
||||
### Data provenance
|
||||
|
||||
- **Source:** Live CSI from 2x ESP32-S3 nodes (802.11n, HT40, 114 subcarriers)
|
||||
- **Volume:** ~360,000 CSI frames (~3,600 feature vectors) per collection run
|
||||
- **Environment:** Residential room, ~4x5 meters
|
||||
- **Ground truth:** *Planned* — environmental sensors on the Cognitum Seed (PIR, BME280, light). Not yet wired into training; treat the PIR/BME280 references in this card as the intended design, not a current capability.
|
||||
- **Attestation:** Every collection run produces a cryptographic witness chain (`collection-witness.json`) that proves data provenance and integrity
|
||||
|
||||
### Witness chain
|
||||
|
||||
The `collection-witness.json` file contains a chain of SHA-256 hashes linking every step from raw CSI capture through feature extraction to model training. This allows anyone to verify that the published model was trained on data collected by specific hardware at a specific time.
|
||||
|
||||
---
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
### Minimum: single-node sensing ($9)
|
||||
|
||||
| Component | What it does | Cost | Where to get it |
|
||||
|---|---|---|---|
|
||||
| ESP32-S3 (8MB flash) | Captures WiFi CSI + runs feature extraction | ~$9 | Amazon, AliExpress, Adafruit |
|
||||
| USB-C cable | Power + data | ~$3 | Any electronics store |
|
||||
|
||||
This gets you: presence detection, motion classification, breathing rate.
|
||||
|
||||
### Recommended: dual-node sensing ($18)
|
||||
|
||||
Add a second ESP32-S3 to enable cross-node signal fusion for better accuracy and person counting.
|
||||
|
||||
### Full setup: sensing + ground truth ($27)
|
||||
|
||||
| Component | What it does | Cost |
|
||||
|---|---|---|
|
||||
| 2x ESP32-S3 (8MB) | WiFi CSI sensing nodes | ~$18 |
|
||||
| Cognitum Seed (Pi Zero 2W) | Runs inference + collects ground truth | ~$15 |
|
||||
| USB-C cables (x3) | Power + data | ~$9 |
|
||||
| **Total** | | **~$27** |
|
||||
|
||||
The Cognitum Seed runs the ONNX models on-device and orchestrates the ESP32 nodes over USB serial. (Using its onboard PIR/BME280 sensors as training ground truth is planned but not yet implemented — see "Data provenance" above.)
|
||||
|
||||
---
|
||||
|
||||
## Files in this repo
|
||||
|
||||
| File | Size | Description |
|
||||
|---|---|---|
|
||||
| `pretrained-encoder.onnx` | ~2 MB | Contrastive encoder (TCN backbone, 8-dim input, 128-dim output) |
|
||||
| `pretrained-heads.onnx` | ~100 KB | Task heads (presence, count, activity, vitals) |
|
||||
| `pretrained.rvf` | ~500 KB | RuVector format embeddings for advanced fusion pipelines |
|
||||
| `room-profiles.json` | ~10 KB | Environment calibration profiles (room geometry, baseline noise) |
|
||||
| `collection-witness.json` | ~5 KB | Cryptographic witness chain proving data provenance |
|
||||
| `config.json` | ~2 KB | Training configuration (hyperparameters, feature schema, versions) |
|
||||
| `README.md` | -- | This file |
|
||||
|
||||
### RuVector format (.rvf)
|
||||
|
||||
The `.rvf` file contains pre-computed embeddings in RuVector format, used by the RuView application for advanced multi-node fusion and cross-viewpoint pose estimation. You only need this if you are using the full RuView pipeline. For basic inference, the ONNX files are sufficient.
|
||||
|
||||
---
|
||||
|
||||
## How to use with RuView
|
||||
|
||||
[RuView](https://github.com/ruvnet/RuView) is the open-source application that ties everything together: firmware flashing, real-time sensing, and a browser-based dashboard.
|
||||
|
||||
### 1. Flash firmware to ESP32-S3
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ruvnet/RuView.git
|
||||
cd RuView
|
||||
|
||||
# Flash an ESP32-S3 ($9 on Amazon/AliExpress)
|
||||
python -m esptool --chip esp32s3 --port COM9 --baud 460800 \
|
||||
write_flash 0x0 bootloader.bin 0x8000 partition-table.bin \
|
||||
0xf000 ota_data_initial.bin 0x20000 esp32-csi-node.bin
|
||||
|
||||
# Provision WiFi
|
||||
python firmware/esp32-csi-node/provision.py --port COM9 \
|
||||
--ssid "YourWiFi" --password "secret" --target-ip YOUR_IP
|
||||
|
||||
# See what WiFi reveals about your room
|
||||
node scripts/deep-scan.js --bind YOUR_IP --duration 10
|
||||
# Flash firmware (requires ESP-IDF v5.4 or use pre-built binaries from Releases)
|
||||
# See the repo README for platform-specific instructions
|
||||
```
|
||||
|
||||
## Using with the Rust sensing server (RVF conversion)
|
||||
|
||||
`model.safetensors` does not carry the `RVFS` binary-container magic that
|
||||
`wifi-densepose-sensing-server`'s `--model` loader expects natively — it needs
|
||||
converting first (issue #894). As of #1480, `--model` auto-detects and
|
||||
converts `model.safetensors` / `model.rvf.jsonl` in-memory, so the one-liner
|
||||
below is enough for most uses:
|
||||
### 2. Download models
|
||||
|
||||
```bash
|
||||
cargo run -p wifi-densepose-sensing-server -- --model model.safetensors
|
||||
pip install huggingface_hub
|
||||
huggingface-cli download ruvnet/wifi-densepose-pretrained --local-dir models/
|
||||
```
|
||||
|
||||
To pre-convert once and skip re-conversion on every startup (recommended for
|
||||
repeated runs, or to inspect the converted container), use `--convert-model`:
|
||||
### 3. Run inference
|
||||
|
||||
```bash
|
||||
cargo run -p wifi-densepose-sensing-server -- \
|
||||
--convert-model model.safetensors --convert-out model.rvf
|
||||
# Start the CSI bridge (connects ESP32 serial output to the inference pipeline)
|
||||
python scripts/seed_csi_bridge.py --port COM7 --model models/pretrained-encoder.onnx
|
||||
|
||||
cargo run -p wifi-densepose-sensing-server -- \
|
||||
--model model.rvf --load-rvf model.rvf
|
||||
# Or run the full sensing server with web dashboard
|
||||
cargo run -p wifi-densepose-sensing-server
|
||||
```
|
||||
|
||||
`--model` loads the weights for inference; `--load-rvf` separately populates
|
||||
the container metadata that `/api/v1/model/info` reports — pass both if you
|
||||
want that endpoint to reflect the loaded container.
|
||||
### 4. Adapt to your room
|
||||
|
||||
Converting `model.safetensors` wires the format/load path (magic, version,
|
||||
segments, weights all valid) but the pose-decoder *architecture* published on
|
||||
HF differs from this crate's inference head, so the converted weights are not
|
||||
claimed to reproduce pose accuracy end-to-end (tracked in #894). `model-q2/q4/q8.bin`
|
||||
(quantized HF blobs) have no reader in this build yet — convert the
|
||||
full-precision `model.safetensors` instead.
|
||||
The model works best after a brief calibration period (~60 seconds of no movement) to learn the baseline signal characteristics of your specific room. The `room-profiles.json` file contains example profiles; the system will create one for your environment automatically.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
WiFi signals → ESP32-S3 ($9) → 8-dim features @ 1 Hz → Encoder → 128-dim embedding
|
||||
↓
|
||||
┌──────────────────────────┼──────────────────┐
|
||||
↓ ↓ ↓
|
||||
Presence head Activity head Vitals head
|
||||
(v1 "100%" retracted) (still/walk/talk) (BR, HR)
|
||||
```
|
||||
|
||||
The encoder converts 8 WiFi Channel State Information (CSI) features into a 128-dimensional embedding:
|
||||
|
||||
| Dim | Feature | What it captures |
|
||||
|-----|---------|-----------------|
|
||||
| 0 | Presence | How much the WiFi signal is disturbed |
|
||||
| 1 | Motion | Rate of signal change (walking > typing > still) |
|
||||
| 2 | Breathing | Chest movement modulates subcarrier phase at 6-30 BPM |
|
||||
| 3 | Heart rate | Blood pulse creates micro-Doppler at 40-120 BPM |
|
||||
| 4 | Phase variance | Signal quality — higher = more movement |
|
||||
| 5 | Person count | Independent motion clusters via min-cut graph |
|
||||
| 6 | Fall detected | Sudden phase acceleration followed by stillness |
|
||||
| 7 | RSSI | Signal strength — indicates distance from sensor |
|
||||
|
||||
## Training Details
|
||||
|
||||
**No camera was used.** Trained using self-supervised contrastive learning:
|
||||
|
||||
- **Data**: 60,630 samples from 2 ESP32-S3 nodes over 8 hours
|
||||
- **Method**: Triplet loss + InfoNCE (nearby frames = similar, distant = different)
|
||||
- **Augmentation**: 10x via temporal interpolation, noise, cross-node blending
|
||||
- **Supervision**: PIR sensor, BME280, RSSI triangulation, subcarrier asymmetry
|
||||
- **Quantization**: TurboQuant 2/4/8-bit with <0.5% quality loss
|
||||
- **Adaptation**: LoRA rank-4 per room, EWC to prevent forgetting
|
||||
|
||||
## 17 Sensing Applications
|
||||
|
||||
Built on these embeddings ([RuView](https://github.com/ruvnet/RuView)):
|
||||
|
||||
**Core:** Presence, person counting, RF scanning, SNN learning, CNN fingerprinting
|
||||
|
||||
**Health:** Sleep monitoring, apnea screening, stress detection, gait analysis
|
||||
|
||||
**Environment:** Room fingerprinting, material detection, device fingerprinting
|
||||
|
||||
**Multi-frequency:** RF tomography, passive radar, material classification, through-wall motion
|
||||
|
||||
## Hardware
|
||||
|
||||
| Component | Cost | Purpose |
|
||||
|-----------|------|---------|
|
||||
| ESP32-S3 (8MB) | ~$9 | WiFi CSI sensing |
|
||||
| [Cognitum Seed](https://cognitum.one) (optional) | $131 | Persistent storage, kNN, witness chain, AI proxy |
|
||||
---
|
||||
|
||||
## Limitations
|
||||
|
||||
- Room-specific (use LoRA adapters for new rooms)
|
||||
- Camera-free pose: 2.5% PCK@20 (camera labels improve significantly)
|
||||
- Health features are for screening only, not medical diagnosis
|
||||
- Breathing/HR less accurate during active movement
|
||||
Be honest about what this technology can and cannot do:
|
||||
|
||||
- **Room-specific.** The model needs a short calibration period in each new environment. A model calibrated in a living room will not work as well in a warehouse without re-adaptation.
|
||||
- **Single room only.** There is no cross-room tracking. Each room needs its own sensing node(s).
|
||||
- **Person count accuracy degrades above 4.** Counting works well for 1-3 people, becomes unreliable above 4 in a single room.
|
||||
- **Vitals require stillness.** Breathing and heart rate estimation work best when the person is sitting or lying down. Accuracy drops significantly during walking or exercise.
|
||||
- **Heart rate is experimental.** The +/- 5 BPM accuracy is a best-case figure. In practice, cardiac sensing via WiFi is still a research-stage capability.
|
||||
- **Wall materials matter.** Metal walls, concrete reinforced with rebar, or foil-backed insulation will significantly attenuate the signal and reduce range.
|
||||
- **WiFi interference.** Heavy WiFi traffic from other devices can add noise. The system works best on a dedicated or lightly-used WiFi channel.
|
||||
- **Not a medical device.** Vital sign estimates are for informational and research purposes only. Do not use them for medical decisions.
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Elder care:** Non-invasive fall detection and activity monitoring without cameras
|
||||
- **Smart home:** Presence-based lighting and HVAC control
|
||||
- **Security:** Occupancy detection through walls
|
||||
- **Sleep monitoring:** Breathing rate tracking overnight
|
||||
- **Research:** Low-cost human sensing for academic experiments
|
||||
- **Disaster response:** The MAT (Mass Casualty Assessment Tool) uses this model to detect survivors through rubble via WiFi signal reflections
|
||||
|
||||
---
|
||||
|
||||
## Ethical Considerations
|
||||
|
||||
WiFi sensing is a privacy-preserving alternative to cameras, but it still detects human presence and activity. Consider these points:
|
||||
|
||||
- **Consent:** Always inform people that WiFi sensing is active in a space.
|
||||
- **No biometric identification:** This model cannot identify *who* someone is -- only that someone is present and what they are doing.
|
||||
- **Data minimization:** Raw CSI data is processed on-device and only summary features or embeddings leave the sensor. No images, audio, or video are ever captured.
|
||||
- **Dual use:** Like any sensing technology, this can be misused for surveillance. We encourage transparent deployment and clear signage.
|
||||
|
||||
---
|
||||
|
||||
## Citation
|
||||
|
||||
If you use this model in your research, please cite:
|
||||
|
||||
```bibtex
|
||||
@software{ruview2026,
|
||||
title={RuView: WiFi Sensing with Self-Supervised Contrastive Learning},
|
||||
author={rUv},
|
||||
year={2026},
|
||||
url={https://github.com/ruvnet/RuView},
|
||||
note={Models: https://huggingface.co/ruv/ruview}
|
||||
@software{wifi_densepose_2026,
|
||||
title = {WiFi-DensePose: Human Pose Estimation from WiFi Channel State Information},
|
||||
author = {ruvnet},
|
||||
year = {2026},
|
||||
url = {https://github.com/ruvnet/RuView},
|
||||
license = {MIT},
|
||||
note = {Self-supervised contrastive learning on ESP32-S3 CSI data}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License. See [LICENSE](https://github.com/ruvnet/RuView/blob/main/LICENSE) for details.
|
||||
|
||||
You are free to use, modify, and distribute this model for any purpose, including commercial applications.
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
- **GitHub**: https://github.com/ruvnet/RuView
|
||||
- **Cognitum Seed**: https://cognitum.one
|
||||
- **RuVector**: https://github.com/ruvnet/ruvector
|
||||
- **License**: MIT
|
||||
- **GitHub:** [github.com/ruvnet/RuView](https://github.com/ruvnet/RuView)
|
||||
- **Hardware:** [ESP32-S3 DevKit](https://www.espressif.com/en/products/devkits) | [Cognitum Seed](https://cognitum.one)
|
||||
- **ONNX Runtime:** [onnxruntime.ai](https://onnxruntime.ai)
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
# 01 — State of the Art
|
||||
|
||||
Scope: what a passive or active adversary can extract about *who* is in a space
|
||||
and *what they are doing* from WiFi, the standard that broadens that surface, and
|
||||
the countermeasures that try to prevent it. Claims are tagged **MEASURED** (from
|
||||
a primary source, with metric), **CLAIMED** (asserted without an independent
|
||||
measurement), or analytical inference (flagged).
|
||||
|
||||
---
|
||||
|
||||
## 1. The attack surface: beamforming feedback (BFI)
|
||||
|
||||
Since WiFi 5 (802.11ac), a client (beamformee) measures the downlink channel,
|
||||
compresses the steering matrix **V** into **Givens-rotation angles φ/ψ**, and
|
||||
transmits them **in cleartext** so the AP can steer beams. Anyone in monitor
|
||||
mode can capture these frames for *every* client simultaneously — no network
|
||||
access, and the target need carry no device. Quantization is coarse (802.11ac
|
||||
angle steps of π/4…π/32 rad) yet retains rich motion and body information.
|
||||
|
||||
| Work | Venue / year | Result | Label |
|
||||
|---|---|---|---|
|
||||
| **BFId** — identity inference from BFI | ACM CCS 2025 (KIT/KASTEL) | Re-identifies individuals from BFI alone; novel 197-person dataset. Press reports **99.5%** in a controlled study (ACM full text was not openable to confirm class count/split) | MEASURED (paper); 99.5% is CLAIMED via press |
|
||||
| **LeakyBeam** — occupancy through walls | NDSS 2025 | Occupancy detection **TPR 82.7% / TNR 96.7%** at **20 m, through walls**, from plaintext BFI. Proposes a BFI-obfuscation defense | MEASURED (attack); defense overhead CLAIMED |
|
||||
| **BFIAttack** — CSI reconstruction from BFI | arXiv 2026 (USF) | Reconstructs CSI from BFI, then defeats CSI defenses. ASR: device auth 95.5% / user auth 92.6% / key-gen 94.2% (single-antenna), 1.5–6 m | MEASURED |
|
||||
| **BeamSense** — activity recognition from BFI | Computer Networks vol. 258, 2025 (Northeastern) | Human activity recognition **up to 99.28%** on commodity 802.11ac, no firmware mod, ~10% better than CSI | MEASURED |
|
||||
| **Wi-BFI** — capture tooling | arXiv 2309.04408, 2023 | Pip-installable extraction of 802.11 BFI from commercial devices | tooling |
|
||||
|
||||
**Takeaway for the defender.** BFI is the highest-leverage surface: unencrypted,
|
||||
management-plane, device-free, capturable en masse with off-the-shelf tools. It
|
||||
is also a *stepping stone* — BFIAttack shows BFI can reconstruct the CSI that all
|
||||
older attacks assume.
|
||||
|
||||
---
|
||||
|
||||
## 2. The older adjacent surface: CSI identity/gait/activity
|
||||
|
||||
CSI requires special extraction (Intel 5300 / Atheros / ESP32) but is the
|
||||
foundation the BFI attacks build on. Person-ID exploits **gait** as a biometric.
|
||||
Representative MEASURED results (commodity WiFi, CSI amplitude):
|
||||
|
||||
| System | Accuracy | N (candidates) | Note |
|
||||
|---|---|---|---|
|
||||
| WiWho (IPSN 2016) | 92%→80% | 2→6 | 2–3 m straight walk |
|
||||
| WiFi-ID (2016) | 93%→77% | 2→6 | wavelet features |
|
||||
| WiPIN (2018) | 92–100% | ≤30 | operation-free |
|
||||
| Deep-WiID (2019) | 92.5–99.7% | 6→15 | GRU |
|
||||
| WiNet / LWID (2020) | 98.5% / 98.8% | 40 / 50 | CNN |
|
||||
|
||||
**Pattern the defender must exploit and not overstate:** accuracy is high in
|
||||
small closed sets but *degrades as N grows and conditions become realistic*
|
||||
(cross-day, cross-location, cross-walking-style). Chance is **1/N**; a 99% result
|
||||
on N=5 is far weaker evidence than 99% on N=197. Open-world scale is largely
|
||||
unproven (see *SoK: Security Evaluation of Wi-Fi CSI Biometrics*, 2025).
|
||||
|
||||
---
|
||||
|
||||
## 3. The standard: IEEE 802.11bf-2025
|
||||
|
||||
IEEE Std **802.11bf-2025** (Amendment 4: *Enhancements for WLAN Sensing*) was
|
||||
published **26 September 2025**. It standardizes WLAN sensing in 1–7.125 GHz and
|
||||
above 45 GHz, defining sensing capability signaling, measurement/sounding
|
||||
setup, feedback types, and both passive (ambient-traffic) and active
|
||||
(dedicated null-packet) sensing modes.
|
||||
|
||||
- **Attack-surface implication (analytical).** 802.11bf turns CSI/measurement
|
||||
acquisition from proprietary hacks into open, vendor-agnostic, machine-readable
|
||||
MAC signaling across heterogeneous devices — institutionalizing exactly the
|
||||
measurements the BFI attacks abuse. The standard frames sensing as a feature,
|
||||
not a threat.
|
||||
- **The privacy gap (MEASURED from standards minutes).** A 2023 proposal for a
|
||||
BFI "secure transmission mechanism" (IEEE 802.11-23/0782) was **withdrawn**;
|
||||
"the group did not align on the characterization of [the] privacy problem."
|
||||
The standard shipped without privacy protections, and its own analysis admits
|
||||
passive eavesdroppers can extract location, respiration, heart rate, and
|
||||
identity.
|
||||
|
||||
---
|
||||
|
||||
## 4. Countermeasures (the defense literature)
|
||||
|
||||
All operate on the defender's *own* transmissions; none are jamming.
|
||||
|
||||
| Countermeasure | Venue / year | Mechanism | Effect | Label |
|
||||
|---|---|---|---|---|
|
||||
| **IRShield** | IEEE S&P 2022 | IRS/reconfigurable surface randomizes reflected paths | Attacker motion-detection **≤5%** | MEASURED |
|
||||
| **PhyCloak** | USENIX NSDI 2016 | Full-duplex obfuscator injects Doppler/phase distortion into sensing only | **88.69%** gesture-spoof; throughput can rise (whitelist legit sensors) | MEASURED (spoof); throughput CLAIMED |
|
||||
| **DP-Givens dithering** | IEEE DySPAN 2026 | Differentially-private stochastic quantization of BFI φ/ψ angles | Attacker speed-class error 19%→~73% (chance); **fine (3-bit) resolution ≈ non-private baseline throughput** | MEASURED |
|
||||
| **MIMOCrypt / WiShield** | 2023 / IEEE JSAC 2024 | Secret precoding / MIMO CSI manipulation so only the intended RX decodes | Anti-tracking | CLAIMED/formal |
|
||||
| **CSI Fuzzing / DP feature release** | IEEE 2024–25 | Randomized CSI features with DP budget | Formal DP guarantee | CLAIMED/formal |
|
||||
| **ScatterShield** | ACM IMWUT 2025 | Backscatter tags inject controlled clutter | Defeats unauthorized sensing | MEASURED |
|
||||
| **Adversarial packet perturbation** | ACM MobiCom 2024 | Small in-spec packet perturbations degrade attacker model | Symmetric defense | MEASURED |
|
||||
|
||||
**The fundamental tradeoff (MEASURED, DySPAN 2026).** Perturbing precoding/
|
||||
feedback that an attacker exploits also degrades legitimate beamforming gain —
|
||||
*but the cost collapses at fine feedback resolution*:
|
||||
|
||||
| Randomization | Attacker error | Beamforming gain retained |
|
||||
|---|---|---|
|
||||
| none | 19% | 100% |
|
||||
| moderate (p=0.3) | >50% | median >90% |
|
||||
| maximum (p≥0.9) | ~73% (≈chance) | median ~58% |
|
||||
|
||||
At **high (3-bit) feedback resolution, privacy was "nearly indistinguishable
|
||||
from the non-private baseline"** in link performance. This is the empirical basis
|
||||
for VEIL's design choice (compliant fine-resolution feedback shaping — see
|
||||
[03-countermeasure-design.md](03-countermeasure-design.md)).
|
||||
|
||||
---
|
||||
|
||||
## 5. Where VEIL sits
|
||||
|
||||
The literature has two families: **external** obfuscation (IRShield/ScatterShield
|
||||
— extra hardware, perturbs the channel) and **transmitter-side** feedback/precoder
|
||||
shaping (DP-Givens, MIMOCrypt — no extra hardware, perturbs your own report).
|
||||
VEIL is in the second family and adds the missing property the others do not all
|
||||
combine: a transform that is simultaneously **energy-preserving** (provably
|
||||
compliant), **key-reversible** (throughput-preserving for the legitimate link),
|
||||
and **session-fresh** (defeats cross-session re-identification), unified around
|
||||
the Givens-rotation primitive the report already uses.
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
- BFId — ACM CCS 2025: https://dl.acm.org/doi/10.1145/3719027.3765062 · KIT record: https://publikationen.bibliothek.kit.edu/1000185756
|
||||
- LeakyBeam — NDSS 2025: https://www.ndss-symposium.org/ndss-paper/lend-me-your-beam-privacy-implications-of-plaintext-beamforming-feedback-in-wifi/
|
||||
- BFIAttack — arXiv 2604.04179: https://arxiv.org/html/2604.04179v1
|
||||
- BeamSense — Computer Networks 2025: https://dl.acm.org/doi/10.1016/j.comnet.2024.111020 · arXiv 2303.09687: https://arxiv.org/pdf/2303.09687
|
||||
- Wi-BFI — arXiv 2309.04408: https://arxiv.org/pdf/2309.04408
|
||||
- SoK: Security Evaluation of Wi-Fi CSI Biometrics — arXiv 2511.11381: https://arxiv.org/pdf/2511.11381
|
||||
- WiWho (IPSN 2016): https://dl.acm.org/doi/10.5555/2959355.2959359 · WiPIN — arXiv 1810.04106: https://arxiv.org/pdf/1810.04106
|
||||
- Survey on Wi-Fi Sensing for Human Identity — MDPI Electronics 2023: https://www.mdpi.com/2079-9292/12/23/4858
|
||||
- IEEE Std 802.11bf-2025: https://standards.ieee.org/ieee/802.11bf/11574/ · Overview — IEEE COMST 2024: https://ieeexplore.ieee.org/document/10547188/ · NIST: https://www.nist.gov/publications/ieee-80211bf-enabling-widespread-adoption-wi-fi-sensing
|
||||
- 802.11bf privacy proposal withdrawal (802.11-23/0782), summarized: https://pascalpiron.substack.com/p/wifi-sensing-and-the-privacy-fix
|
||||
- IRShield — IEEE S&P 2022 / arXiv 2112.01967: https://arxiv.org/abs/2112.01967 · https://ieeexplore.ieee.org/document/9833676/
|
||||
- PhyCloak — USENIX NSDI 2016: https://www.usenix.org/conference/nsdi16/technical-sessions/presentation/qiao
|
||||
- Protecting Human Activity Signatures in Compressed 802.11 CSI Feedback — DySPAN 2026 / arXiv 2512.18529: https://arxiv.org/abs/2512.18529
|
||||
- MIMOCrypt — arXiv 2309.00250: https://arxiv.org/pdf/2309.00250 · WiShield — IEEE JSAC 2024: https://dl.acm.org/doi/abs/10.1109/JSAC.2024.3414597
|
||||
- ScatterShield — ACM IMWUT 2025: https://dl.acm.org/doi/abs/10.1145/3770653
|
||||
- Practical Adversarial Attack on WiFi Sensing — ACM MobiCom 2024: https://dx.doi.org/10.1145/3636534.3649367
|
||||
- Privacy-Preserving Wi-Fi Data Generation via DP — INFOCOM 2025: https://www.eng.auburn.edu/~szm0001/papers/INFOCOM25.pdf
|
||||
@@ -1,94 +0,0 @@
|
||||
# 02 — Threat Model
|
||||
|
||||
VEIL protects a physical space (a room, a ward, a boardroom, a SCIF) from
|
||||
*unauthorized* WiFi-based inference of **who is present** and **what they are
|
||||
doing**, without denying the space its own working WiFi. This file states the
|
||||
adversary classes, exactly what VEIL defends, and — just as importantly — what
|
||||
it does **not**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Assets
|
||||
|
||||
| Asset | Why it matters |
|
||||
|---|---|
|
||||
| **Identity linkage** | Re-identifying a specific person across time/sessions from their RF signature (BFId-class attack) |
|
||||
| **Occupancy / presence** | Whether the space is occupied, and by how many (LeakyBeam-class, through-wall) |
|
||||
| **Activity / motion** | Gait, gestures, keystrokes, respiration inferred from channel dynamics (BeamSense-class) |
|
||||
| **Communication utility** | The legitimate WiFi link must keep working (≥95% throughput bar) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Adversary classes
|
||||
|
||||
| Class | Position | Capability | In VEIL scope? |
|
||||
|---|---|---|---|
|
||||
| **A1 — external passive sniffer** | Outside the trust boundary (adjacent room, van, hallway), monitor mode | Captures plaintext BFI/CSI for every station; runs BFId/LeakyBeam/BeamSense offline | **Primary target — yes** |
|
||||
| **A2 — external active sensor** | Nearby, transmits its own probing/sounding to solicit measurable responses | Elicits sensing responses; 802.11bf "active" mode | **Partial** — cadence randomization + non-response policy help; full defense needs MAC-layer policy |
|
||||
| **A3 — associated but curious AP** | Inside the link; the party VEIL shares keys with | Sees the un-rotated report by construction | **Out of scope** — this is BFLD's detection/privacy-class problem (ADR-118/141) |
|
||||
| **A4 — supply-chain / firmware** | Compromised radio firmware | Can bypass any transmit-side control | Out of scope (integrity problem, not a waveform problem) |
|
||||
| **A5 — physical / RF-denial** | Wants to *block* WiFi | — | Explicitly rejected: VEIL never jams |
|
||||
|
||||
VEIL's design centers on **A1**, the attacker the literature demonstrates and
|
||||
the one no shipping product addresses.
|
||||
|
||||
---
|
||||
|
||||
## 3. What VEIL guarantees (and the evidence class)
|
||||
|
||||
1. **Cross-session identity unlinkability against A1.** Because the fine-subspace
|
||||
signature is rotated by a fresh secret orthogonal transform each session, an
|
||||
A1 attacker cannot average captures back to a stable per-person template.
|
||||
*Evidence: SYNTHETIC — re-ID collapses from 100% to ~chance in the reference
|
||||
experiment (`cargo test`); real-silicon witness is future work.*
|
||||
2. **Communication preservation.** The transform is key-reversible by the
|
||||
legitimate receiver, and acts only on the identity-bearing fine subspace, so
|
||||
link throughput stays ≥95%. *Evidence: SYNTHETIC model + MEASURED external
|
||||
corroboration (DySPAN 2026: fine-resolution feedback shaping is near-free).*
|
||||
3. **Compliance.** The transform is orthogonal ⇒ energy-preserving ⇒ adds no
|
||||
interfering emission ⇒ not jamming. *Evidence: machine-checked energy ratio =
|
||||
1.000000 in the `compliance` module; statutory analysis in
|
||||
[04-compliance-and-regulatory.md](04-compliance-and-regulatory.md).*
|
||||
|
||||
---
|
||||
|
||||
## 4. What VEIL does NOT do (non-goals, stated to prevent over-claiming)
|
||||
|
||||
- **It does not hide identity from the associated AP (A3).** That party holds the
|
||||
session key. Protecting against a malicious AP requires detection and policy
|
||||
(BFLD), not waveform shaping.
|
||||
- **It is not RF denial or jamming.** It never degrades another station's link.
|
||||
- **It does not, by itself, defeat within-session motion detection.** A single
|
||||
session's rotation is fixed, so coarse presence/motion may still be inferable
|
||||
within one capture window; sounding-cadence randomization mitigates but does
|
||||
not eliminate this. Identity *re-ID* (the brief's metric) is the guaranteed
|
||||
target; motion obfuscation is partial and tracked as future work.
|
||||
- **It is not a camera-grade or medical-grade claim in any direction.**
|
||||
- **It is not validated on hardware yet.** All quantitative defense results are
|
||||
SYNTHETIC until a captured boot/runtime log exists (CLAUDE.md hardware rule).
|
||||
|
||||
---
|
||||
|
||||
## 5. Trust boundary
|
||||
|
||||
```
|
||||
┌────────────────────── protected space ──────────────────────┐
|
||||
│ │
|
||||
│ [person] [person] legitimate STA ⇄ AP (VEIL) │
|
||||
│ │ │ │ shares session key │
|
||||
│ └──── RF ──────┘ │ rotates fine subspace│
|
||||
│ reflections ▼ of its own BFI │
|
||||
│ compliant, key-reversible, │
|
||||
│ energy-preserving emission │
|
||||
└───────────────────────────────────────┬──────────────────────┘
|
||||
│ plaintext BFI on air
|
||||
▼
|
||||
A1 external passive sniffer (monitor mode)
|
||||
sees a freshly-rotated signature each session
|
||||
→ cannot build a stable per-person template
|
||||
→ re-identification → chance
|
||||
```
|
||||
|
||||
The key never crosses the boundary to A1. The AP inside the boundary is trusted
|
||||
for key-sharing (A3 out of scope). No emission crosses the boundary with intent
|
||||
or effect of interfering with another station (A5 rejected).
|
||||
@@ -1,136 +0,0 @@
|
||||
# 03 — Countermeasure Design
|
||||
|
||||
How VEIL prevents unauthorized sensing with compliant waveform controls, and how
|
||||
the design maps to [`v2/crates/wifi-densepose-privshield`](../../../v2/crates/wifi-densepose-privshield).
|
||||
|
||||
---
|
||||
|
||||
## 1. The separable-subspace principle
|
||||
|
||||
A compressed beamforming report is not homogeneous. Two blocks carry different
|
||||
information:
|
||||
|
||||
- **Dominant beam direction (comm block).** The coarse steering the AP uses to
|
||||
aim data at the client. It varies with position and traffic and carries **no**
|
||||
stable identity. **Throughput rides here.**
|
||||
- **Fine cross-subcarrier phase structure (fine block).** The high-order
|
||||
multipath detail. It is *stable per person* across sessions and is what
|
||||
re-identification exploits (BFId). **Identity leaks here.** Communication
|
||||
barely uses it.
|
||||
|
||||
The whole design rests on this: **identity leakage and data throughput live in
|
||||
(mostly) separable subspaces.** A transform confined to the fine block can wreck
|
||||
re-identification while sparing the beam the link depends on. This is consistent
|
||||
with the DySPAN-2026 MEASURED result that shaping fine-resolution feedback is
|
||||
nearly free in throughput.
|
||||
|
||||
---
|
||||
|
||||
## 2. The four compliant waveform controls
|
||||
|
||||
VEIL alters "channel sounding, phase, or beam schedules" — exactly the levers the
|
||||
brief names — all within the 802.11 waveform envelope:
|
||||
|
||||
| Control | What it varies | Purpose |
|
||||
|---|---|---|
|
||||
| **Keyed precoder rotation** (primary) | A fresh secret orthogonal transform of the *fine* subspace each session, composed from extra Givens rotations | Destroys cross-session identity linkage; energy-preserving; key-reversible |
|
||||
| **Feedback quantization / dither** | Sub-step noise on reported φ/ψ angles | Adds report-level uncertainty; tunes the privacy–throughput point via `feedback_bits` |
|
||||
| **Sounding-cadence randomization** | Jitter on NDP sounding intervals | Under-samples motion for an eavesdropper; charged as the throughput overhead |
|
||||
| **MU-group / stream-mapping shuffle** | Which STAs are grouped, stream-to-antenna mapping | Rotates the spatial signature over time |
|
||||
|
||||
All four modify the node's **own** standards-conformant frames. None adds energy
|
||||
on top of another station (see [04](04-compliance-and-regulatory.md)).
|
||||
|
||||
---
|
||||
|
||||
## 3. Why the keyed Givens rotation is the right primitive
|
||||
|
||||
The compressed beamforming report is *already* a product of Givens rotations
|
||||
(the φ/ψ angles). VEIL composes **additional keyed Givens rotations** over the
|
||||
fine block. This choice gives three properties at once:
|
||||
|
||||
1. **Orthogonal ⇒ energy-preserving.** A Givens rotation preserves the vector's
|
||||
L2 norm exactly. Composing many still preserves it. So the emission carries
|
||||
the same power it always would — **no added energy, no interference, not
|
||||
jamming.** The `compliance` module checks this: energy ratio = 1.000000.
|
||||
2. **Keyed & reversible ⇒ throughput-preserving.** The legitimate AP/STA shares
|
||||
the per-session key, derives the identical rotation schedule, and applies the
|
||||
inverse (negated angles, reversed order) to recover the true precoder. It pays
|
||||
only the tiny residual from quantizing the extra angles at `feedback_bits`
|
||||
resolution — negligible across the 802.11 5–9-bit range — plus the sounding
|
||||
overhead. (The throughput-optimal resolution is derived in
|
||||
[08-optimization.md](08-optimization.md).)
|
||||
3. **Fresh per session ⇒ unlinkable.** A different rotation each session means an
|
||||
A1 sniffer sees `R_e · signature` for a new random `R_e` every time. Averaging
|
||||
over sessions (the natural enrollment attack) drives
|
||||
`mean_e(R_e · signature) → 0` for *every* identity, so all templates collapse
|
||||
toward the origin and become indistinguishable — re-identification → chance.
|
||||
This is the marginalized-mutual-information argument: over unknown rotations,
|
||||
the signature carries no stable discriminative information.
|
||||
|
||||
This is the shared-secret precoding idea (cf. MIMOCrypt) specialized to the
|
||||
identity-bearing subspace and unified around the report's native primitive.
|
||||
|
||||
---
|
||||
|
||||
## 4. Detect-then-act
|
||||
|
||||
Per the brief ("detect sensing activity and alter…"), VEIL need not perturb
|
||||
continuously. The `SensingDetector` exposes the decision rule: when the observed
|
||||
rate of sensing/NDP solicitations crosses a threshold, the control plane
|
||||
(ADR-280) engages the shield. Continuous operation is also valid; gating just
|
||||
saves the (already small) overhead when no sensing is present.
|
||||
|
||||
---
|
||||
|
||||
## 5. Module map
|
||||
|
||||
| Concept above | Crate module | Key items |
|
||||
|---|---|---|
|
||||
| Deterministic, WASM-safe randomness + keys | `prng` | `Rng` (SplitMix64), `fnv1a_64`, `derive_key` |
|
||||
| Givens algebra, energy conservation | `linalg` | `apply_givens`, `norm`, `dist_sq` |
|
||||
| SYNTHETIC two-subspace BFI model | `identity` | `SceneConfig`, `Channel`, `BfiSample` (`comm()`/`fine()`) |
|
||||
| The four controls (shield) | `protector` | `ShieldConfig`, `Protector::protect`/`recover`, `SensingDetector` |
|
||||
| Passive re-ID adversary | `attacker` | `NearestCentroidAttacker`, `Metric` |
|
||||
| Privacy–throughput tradeoff | `throughput` | `LinkModel::throughput_ratio`, `beamforming_residual`, `feedback_airtime` |
|
||||
| "Not jamming" audit | `compliance` | `ComplianceReport::audit`/`is_compliant` |
|
||||
| Attacker-vs-protector head-to-head | `experiment` | `ExperimentConfig`, `run`, `ExperimentReport` |
|
||||
| Config hyper-optimization | `optimize` | `hyper_optimize`, `min_givens_passes`, `pareto_frontier` |
|
||||
| Byte-stable deterministic witness | `proof` | `Proof::EXPECTED_WITNESS`, `Proof::witness` |
|
||||
|
||||
---
|
||||
|
||||
## 6. The privacy–throughput knobs (and which the optimizer turns)
|
||||
|
||||
- **`feedback_bits`:** the only knob with a genuine throughput tradeoff —
|
||||
residual falls with bits, feedback airtime rises with them, so there is an
|
||||
interior optimum (3 bits unconstrained; 5 bits within the 802.11-allowed set).
|
||||
Privacy is unaffected by bits (the rotation is fresh regardless).
|
||||
- **`givens_passes`:** the privacy/robustness knob. More mixing lowers re-ID at
|
||||
**no throughput cost** (the keyed rotation is never signaled), so it trades
|
||||
only compute. The optimizer finds the minimum for robust collapse and ships a
|
||||
free 2× margin.
|
||||
- **`sounding_overhead`:** a flat throughput cost from cadence randomization;
|
||||
trades motion-obfuscation strength against airtime (outside the re-ID metric).
|
||||
|
||||
The `optimize` module turns these knobs deterministically — see
|
||||
[08-optimization.md](08-optimization.md). It is what replaced the original
|
||||
hand-picked config.
|
||||
|
||||
The `throughput` module computes the ratio from these, so the tradeoff is
|
||||
inspectable rather than asserted (`cargo test throughput`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Honest limitations of the model
|
||||
|
||||
- The two-subspace split is an abstraction; on real hardware comm and identity
|
||||
information are only *approximately* separable, so the real throughput cost of
|
||||
fully hiding identity may be higher than the model's ~2%. The DySPAN-2026
|
||||
MEASURED curve is the external sanity check that it is *small* at fine
|
||||
resolution, not zero.
|
||||
- The nearest-centroid attacker is deliberately simple. The collapse argument is
|
||||
classifier-independent (it is about the signal, not the model), but a hardware
|
||||
study must confirm a strong learned attacker also collapses.
|
||||
- Within-session motion is not addressed by the rotation alone (see threat
|
||||
model §4).
|
||||
@@ -1,90 +0,0 @@
|
||||
# 04 — Compliance and Regulatory Line
|
||||
|
||||
**Non-negotiable:** VEIL uses compliant waveform controls and **never jams.**
|
||||
This file states the legal basis for that line and why every VEIL control falls
|
||||
on the compliant side of it. It is engineering analysis, not legal advice; a
|
||||
deployment in a given jurisdiction needs its own regulatory review.
|
||||
|
||||
---
|
||||
|
||||
## 1. The statutory line (United States)
|
||||
|
||||
The prohibition is on **interfering with others' transmissions**, not on how you
|
||||
shape **your own** signal.
|
||||
|
||||
| Authority | What it prohibits |
|
||||
|---|---|
|
||||
| **47 U.S.C. §333** | *Willful or malicious interference* with any licensed/authorized radio station or U.S. Government station |
|
||||
| **47 U.S.C. §302a(b)** | Manufacture, import, marketing, sale, or *operation* of non-compliant devices (jammers cannot be certified — their sole purpose is interference) |
|
||||
| **47 U.S.C. §301** | Requires a license/authorization to transmit; a jammer can never be authorized |
|
||||
| **47 U.S.C. §501 / §503** | Criminal penalties and forfeitures; FCC cites fines up to $112,500 per violation, **no exemptions** for business/residence/vehicle |
|
||||
|
||||
The distinguishing element of jamming is **intent to interfere plus effect on a
|
||||
third party's link.** A device that shapes its own standards-conformant emission
|
||||
— staying within transmit-power and spectral-mask limits, still type-certifiable
|
||||
— is not a jammer.
|
||||
|
||||
---
|
||||
|
||||
## 2. Why each VEIL control is compliant
|
||||
|
||||
| Control | Compliance argument |
|
||||
|---|---|
|
||||
| **Keyed precoder rotation** | Orthogonal ⇒ preserves the report's energy exactly ⇒ **adds no power on top of anyone's signal.** It is still a valid precoder within the 802.11 feedback format. Machine-checked: energy ratio = 1.000000 (`compliance` module) |
|
||||
| **Feedback quantization / dither** | Reports angles the standard already allows, at the standard's resolution; sub-step dither stays within the quantization envelope. No emission change beyond the node's own frame |
|
||||
| **Sounding-cadence randomization** | Chooses *when* the node sends its own NDP soundings, within permitted timing. Sending fewer/jittered soundings never interferes with another station |
|
||||
| **MU-group / stream-mapping shuffle** | Rearranges the node's own spatial mapping; a normal in-spec transmit choice |
|
||||
|
||||
None of the four transmits *to prevent* another station from communicating; none
|
||||
adds out-of-mask energy; each passes normal type certification. Contrast a
|
||||
jammer, whose defining purpose is to emit energy that denies others service.
|
||||
|
||||
---
|
||||
|
||||
## 3. The energy-conservation proof as a compliance artifact
|
||||
|
||||
VEIL turns "not jamming" from a promise into a **checked property.** The
|
||||
`compliance::ComplianceReport` audits each protection step:
|
||||
|
||||
```
|
||||
input_energy = ‖report_before‖²
|
||||
output_energy = ‖report_after‖²
|
||||
energy_ratio = output_energy / input_energy # ≈ 1.0 for a rotation
|
||||
energy_conserving = |energy_ratio − 1| ≤ 1e-2
|
||||
adds_interfering_energy = false # by construction
|
||||
is_compliant = energy_conserving ∧ ¬adds_interfering_energy
|
||||
```
|
||||
|
||||
A regulator, an auditor, or the runtime attestation layer (ADR-141) can read the
|
||||
report and verify the shield is a waveform-shaping control, not an interference
|
||||
source. On the reference experiment the measured ratio is **1.000000**.
|
||||
|
||||
---
|
||||
|
||||
## 4. Jurisdictional notes
|
||||
|
||||
- **EU (GDPR framing).** Covert WiFi body-sensing of vital signs is sensitive
|
||||
health data and "almost certainly illegal under GDPR," but effectively
|
||||
unenforceable (receivers are undetectable) — which is precisely why a
|
||||
*technical* control is needed. VEIL as a transmit-side control does not itself
|
||||
raise GDPR issues; it reduces the personal data an attacker can derive.
|
||||
- **RF-emission rules are jurisdiction-specific.** The energy-preserving property
|
||||
is the portable core of the compliance argument, but power/mask/timing limits
|
||||
differ by region and band; a deployment must confirm local rules.
|
||||
- **Deliberate transmit-nulling toward a *located* sniffer** (steering a spatial
|
||||
null at a known passive receiver) is still the node's own emission and adds no
|
||||
interference, but is more aggressive and should get explicit regulatory review
|
||||
before field use. It is not part of the default VEIL profile.
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
- 47 U.S.C. §333: https://www.law.cornell.edu/uscode/text/47/333
|
||||
- 47 U.S.C. §302a: https://www.law.cornell.edu/uscode/text/47/302a
|
||||
- FCC Jammer Enforcement: https://www.fcc.gov/general/jammer-enforcement · https://www.fcc.gov/enforcement/areas/jammers
|
||||
- FCC Cell/GPS Jamming guidance: https://www.fcc.gov/general/cell-phone-and-gps-jamming
|
||||
- FCC 14-92 enforcement order: https://docs.fcc.gov/public/attachments/FCC-14-92A1.pdf
|
||||
|
||||
*Caveat: FCC pages were cross-verified against Cornell LII; this is engineering
|
||||
analysis, not legal advice.*
|
||||
@@ -1,113 +0,0 @@
|
||||
# 05 — Experiment Protocol: Attacker vs. Protector
|
||||
|
||||
This is the "start today" deliverable from the brief: **make one RuView node the
|
||||
attacker and one the protector, and measure whether protection drives identity
|
||||
recognition toward chance while keeping throughput above 95%.** It is realized as
|
||||
a deterministic, reproducible experiment in
|
||||
[`v2/crates/wifi-densepose-privshield`](../../../v2/crates/wifi-densepose-privshield).
|
||||
|
||||
Because it runs on **SYNTHETIC** data (no radio is touched), its numbers describe
|
||||
the model, not real hardware — reproduced by `cargo test`, and to be
|
||||
re-established on silicon with a captured log before any deployment claim.
|
||||
|
||||
---
|
||||
|
||||
## 1. Setup
|
||||
|
||||
- **Protector node.** Emits beamforming feedback shaped by the VEIL controls
|
||||
(keyed per-session fine-subspace rotation + configured feedback resolution and
|
||||
sounding overhead). Models a legitimate AP/STA protecting a room.
|
||||
- **Attacker node.** A passive sniffer that enrolls a template per candidate from
|
||||
captured reports, then classifies fresh captures (nearest-centroid) — the
|
||||
BFId-class re-identification threat.
|
||||
- **Scene.** `SceneConfig` default: 64-dim report, 8 comm dims, **16 candidate
|
||||
identities** (chance = 1/16 = 6.25%), per-identity stable fine-block signature
|
||||
+ per-session environmental nuisance.
|
||||
|
||||
Two runs of the attacker are compared: **shield off** (the attacker sees raw
|
||||
reports) and **shield on** (every captured report is VEIL-protected). The same
|
||||
attacker faces both.
|
||||
|
||||
---
|
||||
|
||||
## 2. Metrics and acceptance bar
|
||||
|
||||
| Metric | Definition | Bar |
|
||||
|---|---|---|
|
||||
| **Re-ID accuracy, shield off** | Top-1 identity accuracy on unprotected traffic | Must be well above chance (threat is real) — bar ≥ 0.5 |
|
||||
| **Re-ID accuracy, shield on** | Top-1 identity accuracy on protected traffic | Must fall into the chance band `1/N · 2 + 0.03` |
|
||||
| **Throughput ratio** | Protected link capacity ÷ baseline capacity | **≥ 0.95** |
|
||||
| **Compliance** | Emission energy ratio ≈ 1 and non-interfering | `is_compliant == true` |
|
||||
|
||||
Overall `passed()` requires all four.
|
||||
|
||||
---
|
||||
|
||||
## 3. Results (SYNTHETIC, hyper-optimized default configuration)
|
||||
|
||||
Reproduce with `cargo test -p wifi-densepose-privshield` (all 35 tests + doctest
|
||||
pass). The default shield config is the `optimize` module's output — 96 Givens
|
||||
passes at 5-bit feedback resolution (see
|
||||
[08-optimization.md](08-optimization.md)). Salient values from the reference run:
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| Candidate identities | 16 |
|
||||
| Chance level | 6.25% |
|
||||
| Chance band (acceptance) | ≤ 15.5% |
|
||||
| **Re-ID accuracy, shield OFF** | **100.0%** |
|
||||
| **Re-ID accuracy, shield ON** | **4.7%** |
|
||||
| **Throughput ratio** | **97.60%** |
|
||||
| Emission energy ratio | 1.000000 |
|
||||
| Overall verdict | **PASS** |
|
||||
|
||||
Reading the result: the attacker is a *perfect* re-identifier without protection
|
||||
(the synthetic signatures are cleanly separable), and VEIL drives it *to the
|
||||
chance floor* (4.7% sits just below the ideal 6.25%, i.e. no better than
|
||||
guessing) — while the modeled link keeps 97.6% of its throughput and the
|
||||
emission conserves energy exactly (compliant, not jamming). The same collapse
|
||||
holds under a Cosine-metric attacker and at N=32, confirming it is a property of
|
||||
the signal, not the classifier.
|
||||
|
||||
---
|
||||
|
||||
## 4. Determinism and the witness
|
||||
|
||||
The experiment is byte-reproducible: no OS entropy, no wall-clock, no threads.
|
||||
`proof::Proof` folds the salient outputs (quantized to avoid last-bit f32
|
||||
round-off) into an FNV-1a witness pinned as `EXPECTED_WITNESS`. Any drift in the
|
||||
PRNG stream, rotation schedule, throughput formula, or scene geometry changes the
|
||||
witness and fails `witness_matches_pinned`. This is the same
|
||||
deterministic-proof discipline as `nvsim` and the Python `verify.py`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Sensitivity and what to vary next
|
||||
|
||||
`ExperimentConfig` exposes the levers for a fuller study:
|
||||
|
||||
- **`scene.identities`** — larger N lowers the chance floor; confirm collapse
|
||||
holds as candidates grow.
|
||||
- **`scene.env_sigma` / `beam_amplitude`** — nuisance and comm energy; stress the
|
||||
separability assumption.
|
||||
- **`shield.feedback_bits`** — trace the privacy–throughput curve (the
|
||||
`throughput` tests already show coarse resolution costs more).
|
||||
- **`shield.givens_passes`** — mixing strength; fewer passes should degrade the
|
||||
collapse gracefully.
|
||||
- **Stronger attacker** — swap in a learned classifier to confirm the collapse is
|
||||
signal-level, not classifier-level (the argument says it must be, but a
|
||||
hardware study should verify).
|
||||
|
||||
---
|
||||
|
||||
## 6. Path to a real two-node measurement
|
||||
|
||||
The synthetic experiment is the design proof. The hardware path (per CLAUDE.md,
|
||||
requires a captured log to claim MEASURED):
|
||||
|
||||
1. Two ESP32-S3/C6 or Nexmon-capable nodes: one runs Wi-BFI capture (attacker),
|
||||
one runs a VEIL-shaped feedback profile (protector).
|
||||
2. Enroll and test the same BFId-style classifier on captured BFI, shield off vs.
|
||||
on; log throughput via iperf across the legitimate link.
|
||||
3. Success = the same shape as §3 on real captures, with the boot/runtime log as
|
||||
the witness. Until then, all defense numbers remain SYNTHETIC.
|
||||
@@ -1,92 +0,0 @@
|
||||
# 06 — Market and Buyers
|
||||
|
||||
Facts are tagged **VERIFIED** (from a cited source), **CLAIMED** (asserted by a
|
||||
vendor/analyst/press source), or **SPECULATIVE** (our inference). Market figures
|
||||
are third-party projections, not independent measurements.
|
||||
|
||||
---
|
||||
|
||||
## 1. Why now
|
||||
|
||||
- **The threat is standardized and commercializing (VERIFIED/CLAIMED).** IEEE
|
||||
802.11bf was published Sep 2025; silicon (Infineon AIROC Wi-Fi 7 ACW741x,
|
||||
Qualcomm Dragonwing) lists 802.11bf sensing in 2026 briefs; Origin AI's
|
||||
embedded-sensing program targets late-2026 deployment; Plume/Cognitive Systems
|
||||
WiFi Motion is the largest deployed sensing footprint today.
|
||||
- **The standards body declined to fix privacy (VERIFIED).** The BFI
|
||||
"secure transmission mechanism" proposal (802.11-23/0782) was **withdrawn**;
|
||||
802.11bf shipped with no privacy protections. This is the strongest demand
|
||||
signal — the gap is structural and acknowledged.
|
||||
- **No targeted anti-sensing product ships (VERIFIED by absence).** Every
|
||||
countermeasure (IRShield, PhyCloak, MIMOCrypt, DP-Givens, ScatterShield) is
|
||||
research-stage. The claim "no obvious shipping product protects rooms from this
|
||||
inference" **holds** as of 2026, with one caveat below.
|
||||
|
||||
---
|
||||
|
||||
## 2. First buyers, ranked by procurement readiness
|
||||
|
||||
| Segment | Driver | Readiness |
|
||||
|---|---|---|
|
||||
| **Defence / government** | ICD 705 / DoD EMSEC already mandate RF attenuation in classified spaces; budgets and mandates exist | **Strongest beachhead (VERIFIED)** — but today they buy broadband shielding, not a sensing-specific control |
|
||||
| **Corporate boardrooms / counter-espionage** | TSCM firms (Bastille, Murray Associates) now include WiFi audits and rogue-AP detection; CSI keystroke/gesture inference makes a boardroom shield a natural extension | **VERIFIED demand, EMERGING WiFi-specific** |
|
||||
| **Hospitals** | RF-derived behavioral/vital data is HIPAA PHI; exam rooms, psychiatric units where inference is unwanted | **VERIFIED regulatory hook** — but the hook drives privacy-preserving *sensing* more than a *shield* |
|
||||
| **Hotels** | Documented guest backlash against in-room sensors; privacy as differentiation | **SPECULATIVE** — narrative-led, not procurement-led today |
|
||||
| **Router / AP manufacturers** | Ship opt-out/obfuscation as a firmware feature anticipating regulation | **SPECULATIVE** — no vendor has announced this |
|
||||
|
||||
---
|
||||
|
||||
## 3. Competitive landscape
|
||||
|
||||
- **Direct competitors:** none shipping. All targeted anti-sensing is academic.
|
||||
- **The real substitute (VERIFIED):** broadband RF shielding — SCIF/TEMPEST
|
||||
window film, paint, panels (Signals Defense SD2500: >40 dB, 30 MHz–6 GHz, ICD
|
||||
705 / ASTM F3057-14). It defeats WiFi sensing as a side effect but is **blunt**:
|
||||
it kills *all* RF and cannot coexist with wanted WiFi.
|
||||
- **TSCM services (VERIFIED):** detect, don't prevent.
|
||||
|
||||
**VEIL's differentiation** is exactly what the substitute lacks: **selective and
|
||||
coexisting** — it removes identity/activity leakage while keeping the room's WiFi
|
||||
working at ≥95% throughput, with a machine-checkable compliance artifact.
|
||||
|
||||
---
|
||||
|
||||
## 4. Market size (third-party projections, cite with care)
|
||||
|
||||
- **CLAIMED:** ABI Research — North American WiFi-sensing-compatible CPE install
|
||||
base to **112M by 2030 (51.6% CAGR)**.
|
||||
- **CLAIMED:** Global WiFi sensing market ~$402M (2024) → ~$2.13B (2033)
|
||||
(MarketIntelo).
|
||||
|
||||
Implication: a shield must **coexist** with a large installed sensing base, not
|
||||
assume RF denial — reinforcing the selective-coexistence positioning.
|
||||
|
||||
---
|
||||
|
||||
## 5. Where VEIL fits RuView's positioning
|
||||
|
||||
VEIL pairs with BFLD to make RuView the *both-sides* RF-perception platform:
|
||||
BFLD/AETHER do sensing responsibly and detect leakage; VEIL is the customer-
|
||||
facing **privacy firewall** that protects a room from *others'* sensing. That is a
|
||||
defensible, standards-anchored, gap-filling story: the standards body left the
|
||||
door open, the threat is shipping, and no one else sells the selective lock.
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
- IEEE 802.11bf privacy-proposal withdrawal (802.11-23/0782), summarized: https://pascalpiron.substack.com/p/wifi-sensing-and-the-privacy-fix
|
||||
- NIST 802.11bf: https://www.nist.gov/publications/ieee-80211bf-enabling-widespread-adoption-wi-fi-sensing
|
||||
- IRShield: https://arxiv.org/abs/2112.01967 · MIMOCrypt: https://arxiv.org/pdf/2309.00250 · ScatterShield: https://dl.acm.org/doi/abs/10.1145/3770653 · WiShield JSAC 2024: https://dl.acm.org/doi/abs/10.1109/JSAC.2024.3414597
|
||||
- Signals Defense TEMPEST/SCIF film: https://signalsdefense.com/tempest-and-scif-design/ · https://signalsdefense.com/shielding-films/
|
||||
- National Shielding SCIF/ICD-705: https://www.national-shielding.com/pages/scif-icd-705-secure-facility-shielding
|
||||
- Bastille TSCM: https://bastille.net/centers-of-excellence/tscm/ · IntellSIG TSCM overview: https://www.intellsig.com/2025/07/20/modern-eavesdropping-threats-a-tscm-overview/
|
||||
- Origin AI program: https://www.prnewswire.com/news-releases/origin-ai-launches-compatible-with-origin-program-to-meet-industry-demand-for-scalable-wifi-sensing-and-accelerate-integration-across-global-soc-platforms-302650963.html
|
||||
- MIT Tech Review, WiFi sensing: https://www.technologyreview.com/2024/02/27/1088154/wifi-sensing-tracking-movements/
|
||||
- ABI Research 112M forecast: https://www.abiresearch.com/press/north-american-wi-fi-sensing-cpe-installations-to-surge-to-112-million-by-2030-as-the-technologys-maturing-unleashes-new-business-and-service-models
|
||||
- MarketIntelo WiFi sensing market: https://marketintelo.com/report/wi-fi-sensing-market
|
||||
- HIPAA/PHI RF-sensing context (PMC): https://pmc.ncbi.nlm.nih.gov/articles/PMC11939480/
|
||||
|
||||
*Caveat: market figures are analyst/vendor projections; the "no shipping product"
|
||||
finding reflects absence of evidence in these searches and should be confirmed
|
||||
with a patent/vendor scan before anchoring a go-to-market claim.*
|
||||
@@ -1,117 +0,0 @@
|
||||
# 07 — Implementation and Roadmap
|
||||
|
||||
---
|
||||
|
||||
## 1. What ships in this bundle
|
||||
|
||||
- **Reference crate** `v2/crates/wifi-densepose-privshield` (VEIL): a
|
||||
deterministic, dependency-free, WASM-ready pure-compute leaf implementing the
|
||||
full attacker-vs-protector experiment, the four compliant controls, the
|
||||
throughput model, the compliance audit, the `optimize` hyper-optimizer, and a
|
||||
byte-stable proof. 35 tests + doctest pass; builds for
|
||||
`wasm32-unknown-unknown`; clippy-clean.
|
||||
- **This research bundle** (`docs/research/privacy-shield/`).
|
||||
- **[ADR-288](../../adr/ADR-288-veil-privacy-shield-compliant-waveform.md)** — the
|
||||
formal decision record.
|
||||
- **npm metaharness** `harness/wifi-densepose-privshield/`
|
||||
([ADR-289](../../adr/ADR-289-wifi-densepose-privshield-harness-via-metaharness.md))
|
||||
— a per-crate contributor harness (architect/implementer/reviewer/test-writer,
|
||||
router, flywheel) with a dependency-free `guidance` surface that serves this
|
||||
bundle's capability map. `npx wifi-densepose-privshield-harness guidance
|
||||
--topic optimization`.
|
||||
|
||||
The crate is intentionally a **leaf with no internal RuView dependencies**
|
||||
(mirrors `wifi-densepose-aether`), so it can be reasoned about, fuzzed, and
|
||||
ported independently, and so it can never accidentally acquire a path to a radio.
|
||||
|
||||
---
|
||||
|
||||
## 2. Reuse map (how VEIL composes with existing RuView)
|
||||
|
||||
| Existing subsystem | Relationship |
|
||||
|---|---|
|
||||
| **BFLD** (ADR-118/120/121, `wifi-densepose-bfld`) | Detection layer. Its `identity_risk_score` is the natural trigger for VEIL's `SensingDetector` — detect leakage, then shield |
|
||||
| **Privacy control plane** (ADR-141) | VEIL protection steps emit `ComplianceReport`s that fit the runtime-attestation model (which mode, which actions, which fields) |
|
||||
| **Active sensing / governed actuation** (ADR-280) | VEIL is a defensive `SensingAction`: a governed, privacy-ceiling-bounded emission-shaping action the control plane can schedule |
|
||||
| **Givens/beamforming primitives** | VEIL reuses the report's native Givens-rotation structure rather than inventing a new transform |
|
||||
| **Deterministic proof discipline** (`nvsim`, `archive/v1/verify.py`) | VEIL's `proof` module follows the same pinned-witness pattern |
|
||||
|
||||
---
|
||||
|
||||
## 3. Phased rollout
|
||||
|
||||
| Phase | Deliverable | Evidence class |
|
||||
|---|---|---|
|
||||
| **P1 — reference model (this PR)** | Crate + experiment + docs + ADR | SYNTHETIC (cargo test) |
|
||||
| **P2 — sensitivity study** | Sweep N, noise, resolution, mixing; add a learned attacker to confirm signal-level collapse | SYNTHETIC |
|
||||
| **P3 — BFLD integration** | Wire `identity_risk` → `SensingDetector` → shield engage; emit attestation | SYNTHETIC + integration tests |
|
||||
| **P4 — firmware feedback shaping** | Implement keyed fine-subspace rotation + cadence randomization in the **beamforming-feedback / spatial-mapping path** — see §3.1 for the (non-trivial) platform reality | build + hardware |
|
||||
| **P5 — two-node hardware measurement** | Attacker (Wi-BFI capture) vs. VEIL protector on real silicon; iperf throughput; captured log | **MEASURED** (with witness) |
|
||||
| **P6 — deployment profiles** | Per-segment profiles (SCIF, boardroom, ward) with regulatory review | operational |
|
||||
|
||||
No defense claim graduates from SYNTHETIC to MEASURED without a captured
|
||||
boot/runtime log (CLAUDE.md hardware rule).
|
||||
|
||||
### 3.1 Does this need custom WiFi firmware? (yes — and ESP32 is the wrong chip for the protector)
|
||||
|
||||
VEIL shapes the **compressed beamforming report** (the Givens φ/ψ angles) or the
|
||||
LTF **spatial mapping** as it is transmitted — machinery that lives *below* the
|
||||
driver, inside the chip's PHY/MAC firmware. It is **not** reachable from user
|
||||
space, so a real deployment is a firmware/driver change, not an app.
|
||||
|
||||
- **ESP32 — not viable as the protector.** Its WiFi lower layers are a closed
|
||||
Espressif blob. ESP-IDF exposes CSI *read* (`esp_wifi_set_csi`) — which is why
|
||||
`firmware/esp32-csi-node/` makes a great **attacker/sensor** node — but it does
|
||||
**not** let you rewrite how the chip builds/sends beamforming feedback. ESP32
|
||||
is the *attacker* in a testbed, not the shield.
|
||||
- **Realistic protector platforms:** **openwifi** (open 802.11 on SDR/FPGA —
|
||||
full PHY/MAC control incl. the AP-side compensation; the honest end-to-end
|
||||
route; Verilog + a C driver); **Nexmon** (C firmware *patches* for
|
||||
Broadcom/Cypress, e.g. RPi BCM43455 — the commodity path, and the same
|
||||
framework the BFI *attack* tools already use); open drivers (**ath9k/mt76**)
|
||||
for partial control; or **vendor firmware** for a production feature.
|
||||
- **Two firmware variants:** the **keyed-reversible** version (VEIL's ~98%
|
||||
throughput) needs changes on **both** ends plus key agreement (cf. the
|
||||
LeakyBeam AP-side `Q_obf` is *client-transparent* — only the AP changes — which
|
||||
is a deployment advantage worth adopting, §09 backlog item 3); the
|
||||
**emitter-only DP dither** version needs only the reporting device but pays the
|
||||
full throughput cost.
|
||||
|
||||
The current crate is deliberately a std-only, no-radio leaf and implements none
|
||||
of this; P4 is where it meets silicon.
|
||||
|
||||
---
|
||||
|
||||
## 4. Open problems (tracked honestly)
|
||||
|
||||
1. **Real-hardware separability.** Comm and identity information are only
|
||||
*approximately* separable on real radios; the true throughput cost of full
|
||||
identity hiding may exceed the model's ~2%. P2/P5 must bound it.
|
||||
2. **Within-session motion leakage.** A fixed per-session rotation does not
|
||||
obfuscate coarse motion within one capture window. Needs stronger cadence
|
||||
randomization or amplitude shaping; currently a stated non-goal for the re-ID
|
||||
metric.
|
||||
3. **Active adversary (A2).** An attacker that transmits its own soundings is
|
||||
only partially addressed by cadence control; a MAC-layer non-response policy
|
||||
is needed.
|
||||
4. **Key management.** The per-session rotation key must be derived from the
|
||||
negotiated link secret; VEIL's PRNG is explicitly *not* cryptographic and must
|
||||
not be used for real key material.
|
||||
5. **Regulatory review per jurisdiction.** The energy-conservation argument is
|
||||
portable, but power/mask/timing limits and any transmit-nulling profile need
|
||||
local review before field use.
|
||||
|
||||
---
|
||||
|
||||
## 5. Validation commands
|
||||
|
||||
```bash
|
||||
# Reference experiment + all unit/proof/doc tests
|
||||
cargo test -p wifi-densepose-privshield --no-default-features
|
||||
|
||||
# WASM portability (leaf builds with no radio path)
|
||||
cargo build -p wifi-densepose-privshield --target wasm32-unknown-unknown
|
||||
|
||||
# Lints
|
||||
cargo clippy -p wifi-densepose-privshield --all-targets
|
||||
```
|
||||
@@ -1,142 +0,0 @@
|
||||
# 08 — Hyper-Optimization
|
||||
|
||||
The reference crate first shipped a **hand-picked** shield config (112 Givens
|
||||
passes, 7-bit feedback). This file records how the `optimize` module replaces
|
||||
that guess with a *derived*, robustness-verified optimum, and what it found. All
|
||||
numbers are **SYNTHETIC / L0**, reproduced by
|
||||
`cargo test -p wifi-densepose-privshield`.
|
||||
|
||||
---
|
||||
|
||||
## 1. What is being optimized, and against what
|
||||
|
||||
Two knobs, two objectives, one hard constraint:
|
||||
|
||||
| Knob | Costs | Does it trade against privacy? |
|
||||
|---|---|---|
|
||||
| `feedback_bits` (angle resolution) | Throughput: **residual** falls with bits, **feedback airtime** rises with bits | No — the keyed rotation is applied regardless of resolution |
|
||||
| `givens_passes` (rotation mixing) | Compute only | Yes — more mixing ⇒ lower re-ID |
|
||||
|
||||
**Constraint:** re-ID must collapse into the chance band `1/N · 2 + 0.03` — and
|
||||
it must do so *robustly*: for **both** attacker metrics (Euclidean and Cosine)
|
||||
and **both** identity counts (N = 16 and N = 32, the harder, lower-chance case).
|
||||
|
||||
The key structural fact: **rotation mixing is throughput-free.** The per-session
|
||||
rotation is derived from the shared link secret on both ends (like MIMOCrypt) —
|
||||
it is never transmitted — so extra Givens passes cost compute, not airtime. That
|
||||
means privacy margin is essentially free; the only throughput tradeoff lives in
|
||||
`feedback_bits`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Throughput is a 1-D problem with an interior optimum
|
||||
|
||||
Because the residual falls with bits while feedback airtime rises, throughput
|
||||
has a genuine interior optimum in `feedback_bits` (`LinkModel`, default SNR 20 dB,
|
||||
`feedback_overhead_per_bit = 0.0008`):
|
||||
|
||||
| bits | throughput ratio |
|
||||
|---|---|
|
||||
| 1 | 0.9681 |
|
||||
| 2 | 0.9757 |
|
||||
| **3** | **0.9769** ← unconstrained optimum |
|
||||
| 4 | 0.9766 |
|
||||
| **5** | **0.9760** ← shipped (spec-allowed) |
|
||||
| 7 | 0.9744 (the old hand-picked value) |
|
||||
| 9 | 0.9728 |
|
||||
| 12 | 0.9704 |
|
||||
|
||||
The unconstrained optimum is **3 bits** — which coincides with the DySPAN-2026
|
||||
MEASURED finding that ~3-bit feedback is the privacy–utility sweet spot, because
|
||||
the receiver compensates the keyed rotation and extra bits mostly buy airtime.
|
||||
802.11 compressed beamforming quantizes ψ/φ to roughly 5–9 bits, so the shipped
|
||||
shield uses the throughput-best **spec-allowed** value, **5 bits** (0.9760),
|
||||
rather than the out-of-spec 3-bit optimum. Either way it beats the old 7-bit
|
||||
choice.
|
||||
|
||||
---
|
||||
|
||||
## 3. Mixing: the minimum robust budget, and a free margin
|
||||
|
||||
Worst-case shield-on re-ID vs. `givens_passes` (bits = 5; worst over Euclidean
|
||||
and Cosine):
|
||||
|
||||
| passes | re-ID @ N=16 | re-ID @ N=32 | robust collapse? |
|
||||
|---|---|---|---|
|
||||
| 16 | 0.75 | 0.62 | no |
|
||||
| 24 | 0.50 | 0.35 | no |
|
||||
| 32 | 0.20 | 0.14 | no (N=32 band is 0.0925) |
|
||||
| **48** | 0.12 | 0.057 | **yes** ← proven minimum |
|
||||
| 64 | 0.078 | 0.044 | yes |
|
||||
| **96** | **0.047** | **0.018** | **yes** ← shipped (2× margin) |
|
||||
| 112 | 0.078 | 0.042 | yes (the old default — no better than 96) |
|
||||
|
||||
The proven minimum for robust collapse is **48 passes** — the hand-picked 112 was
|
||||
**2.3× over-provisioned**. Since mixing is throughput-free, the shield ships
|
||||
**96 passes** (`PRIVACY_MARGIN_FACTOR = 2` × 48, rounded up to a candidate): it
|
||||
drives re-ID *below chance* at N=16 (0.047 < 0.0625) at zero throughput cost, and
|
||||
is still cheaper compute than the original 112.
|
||||
|
||||
---
|
||||
|
||||
## 4. The adopted config, and why it beats the original
|
||||
|
||||
| | Old (hand-picked) | Hyper-optimized (shipped) |
|
||||
|---|---|---|
|
||||
| Givens passes | 112 | **96** (from proven-min 48 × 2) |
|
||||
| Feedback bits | 7 | **5** (spec-optimal) |
|
||||
| Shield-on re-ID (N=16) | 0.078 | **0.047** |
|
||||
| Throughput ratio | 0.9744 | **0.9760** |
|
||||
| Robust across metrics & N | not checked | **verified** |
|
||||
|
||||
The optimum is **strictly better on privacy and throughput at once**, and is now
|
||||
*verified* rather than assumed. `ShieldConfig::default()` is exactly the
|
||||
optimizer's output; the test `optimize::shipped_default_equals_optimizer_output`
|
||||
fails if they ever drift apart.
|
||||
|
||||
---
|
||||
|
||||
## 5. The Pareto frontier (and an honest note)
|
||||
|
||||
`optimize::pareto_frontier` enumerates non-dominated (worst-case re-ID,
|
||||
throughput) points over a pass × bits grid. In this model the frontier
|
||||
**collapses toward the max-mixing, 5-bit point**, because mixing is
|
||||
throughput-free — so beyond the throughput knob (bits) there is no privacy–
|
||||
throughput tradeoff to trace. That degeneracy is itself the finding: *the only
|
||||
thing privacy costs here is feedback resolution, and even that is cheap.* On real
|
||||
hardware, where comm/identity subspaces are only approximately separable and
|
||||
where more aggressive mixing may touch the data-carrying beam, this frontier is
|
||||
expected to open up — a hardware study (roadmap P5) will re-measure it.
|
||||
|
||||
---
|
||||
|
||||
## 6. Per-deployment adaptivity
|
||||
|
||||
The optimum is not one number — `optimize` derives it per deployment:
|
||||
|
||||
- **SNR → feedback resolution.** `optimal_bits_across_snr` shows the
|
||||
*unconstrained* throughput-optimal resolution shifting with SNR: **4 bits at
|
||||
5–10 dB, 3 bits at 20–40 dB** (low SNR values fine resolution more because
|
||||
the Shannon capacity is near-linear there, so the residual costs more). Within
|
||||
the spec-allowed {5,7,9} set the choice is 5 bits across this whole range —
|
||||
the residual is already negligible at 5 bits — which is why the shipped shield
|
||||
is SNR-stable.
|
||||
- **Identity count → mixing.** `adaptive_shield(base, n)` derives the config for
|
||||
a room with `n` expected occupants. A notable finding: in this model the
|
||||
collapse budget is **N-independent** (min 48 passes collapses N∈{8,64}
|
||||
alike), because a well-mixed Haar-like rotation destroys per-identity
|
||||
structure regardless of how many identities there are — the budget is set by
|
||||
the fine-subspace dimension, not the candidate count. So `adaptive_shield`
|
||||
returns the same 96/5 across that range: the default is robust, not a point
|
||||
tuning.
|
||||
|
||||
Both are surfaced through the harness `guidance --topic optimization`.
|
||||
|
||||
## 7. Robustness caveats (unchanged from the threat model)
|
||||
|
||||
- The collapse is verified against two classifiers and two N; a learned
|
||||
attacker on real captures must still be checked (P2/P5).
|
||||
- `feedback_bits` affects only throughput in this model, not re-ID; on hardware,
|
||||
coarse quantization also adds obfuscation, which would *help* privacy — the
|
||||
model conservatively ignores that.
|
||||
- All optimization results are SYNTHETIC until a hardware witness exists.
|
||||
@@ -1,151 +0,0 @@
|
||||
# 09 — SOTA Update (2025–2026) and VEIL Improvement Backlog
|
||||
|
||||
Source: a fan-out deep-research run (5 angles → 20 primary sources → 93 claims →
|
||||
top 25 adversarially verified with 3-vote panels → 24 confirmed, 1 refuted).
|
||||
Each finding carries its **evidence class** (`MEASURED` with metric / `CLAIMED`
|
||||
/ `SYNTHETIC` / `STANDARDS-MINUTE`) and a primary URL. This file records what
|
||||
changed in the field and the concrete backlog it implies for VEIL (ADR-288/289).
|
||||
Nothing here upgrades VEIL's own numbers to `MEASURED` — that still requires a
|
||||
captured hardware log (CLAUDE.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. The threat surface got worse (and cheaper)
|
||||
|
||||
| Finding | Evidence | Source |
|
||||
|---|---|---|
|
||||
| **BFId** — first *identity* inference from plaintext BFI: **99.5% over 197 people**, perspective/gait-independent; BFI carries ~740 features vs 212 for CSI, so it *beats* CSI for identity; one eavesdropper captures BFI from all clients | `MEASURED` (top-1, N=197, CCS 2025) | [dl.acm.org/10.1145/3719027.3765062](https://dl.acm.org/doi/10.1145/3719027.3765062) |
|
||||
| **LeakyBeam** — through-wall occupancy at **20 m** (TPR 82.7% / TNR 96.7%) **and breathing/vital-sign** leakage from *stationary* occupants; single antenna, Wireshark, no keys | `MEASURED` (NDSS 2025) | [ndss 2025-5](https://www.ndss-symposium.org/wp-content/uploads/2025-5-paper.pdf) |
|
||||
| **WiKI-Eve / SThief** — keystroke & PIN/password theft from BFI (88.9% per-keystroke; 65.8% top-10 app passwords; POS keypads) with no device compromise | `MEASURED` (CCS 2023 / IEEE) | [WiKI-Eve](https://dl.acm.org/doi/10.1145/3576915.3623088) · [SThief](https://ieeexplore.ieee.org/document/10621321/) |
|
||||
| **BFIAttack** — **reconstructs full CSI from sniffed BFI**: closed-form ≥93% (single-antenna, 1 attempt); MLE with physics/standard constraints 73% (multi-antenna, ≤5 attempts). Collapses the BFI-vs-CSI distinction | `MEASURED` (arXiv Apr 2026) | [arxiv 2604.04179](https://arxiv.org/html/2604.04179v1) |
|
||||
| **BeamSense** — BFI sensing is standards-compliant, needs no firmware mod, ~10% higher activity accuracy than CSI | `MEASURED` | [BFISense/BeamSense](https://www.researchgate.net/publication/402468114_BFISense_Using_Beamforming_Feedback_Information_for_Wi-Fi_Sensing) |
|
||||
|
||||
**Implication:** the attacker is a *passive, keyless, single commodity antenna at
|
||||
~20 m, through walls*, that can (a) identify people, (b) read vitals and
|
||||
keystrokes, and (c) **reconstruct CSI from the BFI itself.** VEIL's threat model
|
||||
must treat all four as baseline.
|
||||
|
||||
---
|
||||
|
||||
## 2. Defenses — the field validates VEIL's family and adds stronger primitives
|
||||
|
||||
| Defense | Mechanism | Effect | Evidence | Source |
|
||||
|---|---|---|---|---|
|
||||
| **LeakyBeam defense** | AP-side **per-packet random unitary** `Q_obf` on the LTF via the 802.11 spatial-mapping mechanism (standard says "not restricted"); AP recovers `V = Q_obf · V_obf`; **clients unmodified** | attack **89.7% → ~51%** across 8 APs (~1.6M packets/49 h) | `MEASURED` | [ndss 2025-5](https://www.ndss-symposium.org/wp-content/uploads/2025-5-paper.pdf) |
|
||||
| **PrivISAC (RIS)** | Paired per-row RIS vectors, one randomly active per slot; preserves comm-direction response, corrupts sensing direction; time-domain mask/demask for the authorized RX | **93% → ~30%**, and **29% vs. retrained 5-location adaptive attacker** | `MEASURED` (64-element FPGA RIS, Intel 5300, ~2,700 OTA samples) | [arxiv 2601.04488](https://arxiv.org/html/2601.04488) |
|
||||
| **DP-Givens** | ε-DP stochastic quantizer on the Givens rotation/phase angles; closed-form angular sensitivity → principled ε budget; preserves 802.11 feedback structure | frontier: attacker error 19% → ~73%; beamforming gain 0.97 → 0.89 median (0.54 at full) | `SYNTHETIC` (Monte-Carlo) | [arxiv 2512.18529](https://arxiv.org/pdf/2512.18529) |
|
||||
| **Adaptive-DP (CSI spectrogram)** | Importance-weighted (non-uniform) DP budget across the time-frequency plane | better privacy-utility than flat noise at equal ε∈[0.5,2]; cuts identity + membership inference | `CLAIMED` (unrefereed) | [arxiv 2512.20323](https://arxiv.org/abs/2512.20323) |
|
||||
| **BeamDancer** | Randomized native-beamforming obfuscation | defeats supervised + unsupervised localization and micro-Doppler; **compliant, not jamming** | `MEASURED` (IEEE TWC 2024) — **do NOT cite its ">96% PDR" (refuted here)** | [ieee 10739908](https://ieeexplore.ieee.org/document/10739908/) |
|
||||
| **TX-side CSI obfuscation (+ counter-attacks)** | Filter the whole frame incl. LTS; DNN de-obfuscation for authorized sensing | **security contested**: "Defeating CSI obfuscation" + SnoopFi FIA/CRA recover the signal | `CLAIMED` design + published rebuttal | [C&S 2025](https://www.sciencedirect.com/science/article/abs/pii/S0167404825002834) |
|
||||
|
||||
**Where VEIL sits:** VEIL's keyed Givens rotation is the *same family* as the
|
||||
LeakyBeam per-packet unitary and the DP-Givens knob — and unlike additive/DP
|
||||
dither, VEIL's transform is **secret and orthogonal**, which is exactly the
|
||||
property that should resist the BFIAttack closed-form/MLE inversion (the attacker
|
||||
has no key, so there is no closed-form to invert to). That is now the decisive
|
||||
claim to *test*, not assume.
|
||||
|
||||
---
|
||||
|
||||
## 3. Compliance / legal line
|
||||
|
||||
- **BeamDancer (IEEE TWC 2024)** is the peer-reviewed precedent for VEIL's
|
||||
stance: **jamming and geofencing are non-compliant / non-scalable; exploiting
|
||||
the standard beamforming mechanism stays 802.11-compliant** (validated without
|
||||
disabling firmware). Cite it as the compliance precedent — but **not** its
|
||||
refuted throughput figure.
|
||||
- **Governance gap (unfilled):** *no* claim on the 802.11bf-2025 standard's
|
||||
privacy provisions, the withdrawn secure-LTF-from-11az proposal, or
|
||||
GDPR/HIPAA/EMSEC/ICD-705 boundaries **survived 3-vote verification** in this
|
||||
run. Blog/secondary sources assert a withdrawn privacy proposal, but it needs
|
||||
primary WG-minute/draft sourcing before VEIL relies on it. Tracked as an open
|
||||
question.
|
||||
|
||||
---
|
||||
|
||||
## 4. VEIL improvement backlog (derived, prioritized)
|
||||
|
||||
Priority = (verified severity) × (fit to VEIL). `[code]` = crate change,
|
||||
`[docs]` = documentation, `[hw]` = hardware path.
|
||||
|
||||
1. **`[code]` ✅ implemented — Reconstruction-aware attacker (decisive).** A
|
||||
BFIAttack-style adversary (`attacker::ReconstructionAttacker`,
|
||||
`AttackerKind::Reconstruction`) recovers the direction of the CSI consistent
|
||||
with the *captured* report and classifies it; the test
|
||||
`reconstruction_attacker_collapses` confirms the keyed *orthogonal secret*
|
||||
rotation leaves it at chance (no key → it only ever recovers the rotated
|
||||
direction) while it still wins on unprotected traffic. *(BFIAttack, MEASURED)*
|
||||
2. **`[code]` ✅ implemented — Adaptive, multi-capture attacker.**
|
||||
`attacker::AdaptivePoolingAttacker` (`AttackerKind::AdaptivePooling`) pools all
|
||||
captures per identity and whitens by per-dimension std before matching (the
|
||||
PrivISAC adaptive/retraining adversary); `adaptive_pooling_attacker_collapses`
|
||||
confirms collapse still holds. *(PrivISAC, MEASURED)*
|
||||
3. **`[code]` ✅ implemented — Per-packet random-unitary mode.**
|
||||
`protector::ObfMode::PerPacketUnitary` applies a fresh unitary per packet,
|
||||
AP-side and **client-transparent** (LeakyBeam family; 802.11 spatial mapping
|
||||
"not restricted" as the compliance basis);
|
||||
`per_packet_unitary_mode_collapses_and_is_compliant` verifies it. *(LeakyBeam
|
||||
defense, MEASURED)*
|
||||
4. **`[code]` ✅ implemented — DP-Givens ε knob.** `ShieldConfig.dp_epsilon` adds
|
||||
an ε-scaled angular dither, renormalized to preserve emission energy (still
|
||||
not jamming); `throughput::dp_residual` makes ε a real privacy↔throughput knob
|
||||
(`dp_epsilon_lowers_throughput_as_it_tightens`), and the combined
|
||||
rotation+DP still collapses and stays compliant. Outputs `SYNTHETIC`.
|
||||
*(DP-Givens, SYNTHETIC)*
|
||||
|
||||
> Items 1–4 landed with the reference **witness unchanged**
|
||||
> (`0x350d…f448`) — the new controls/attackers are opt-in fields; the shipped
|
||||
> default config and its numbers are byte-identical.
|
||||
5. **`[code/docs]` Privacy–throughput *frontier*, not binary claims.** Report
|
||||
attacker-error-vs-privacy and gain/PDR-vs-privacy curves (we already have the
|
||||
throughput-vs-bits and reid-vs-passes curves; add the joined frontier).
|
||||
6. **`[docs]` Threat-model upgrade.** Elevate identity/gait re-ID, through-wall
|
||||
vitals, keystroke/PIN, and **BFI→CSI reconstruction** to primary threats in
|
||||
ADR-288 §threat and bundle 02; add the passive/keyless/20 m/through-wall
|
||||
adversary as the default. *(done in this update)*
|
||||
7. **`[docs]` Security honesty.** State that VEIL's shield security is `CLAIMED`
|
||||
until it survives published de-obfuscation attacks (SnoopFi / "Defeating CSI
|
||||
obfuscation"); add learned de-obfuscation to the attacker roadmap.
|
||||
8. **`[code/docs]` Evaluation battery.** Adopt BeamDancer's three-attacker matrix
|
||||
(supervised localizer + unsupervised clusterer + model-based Doppler) as a
|
||||
minimum test set, plus identity + membership-inference metrics.
|
||||
9. **`[hw]` Hardware-validation path.** Mirror the RIS/8-AP OTA testbeds for P5.
|
||||
**Correction:** ESP32 is an *attacker/sensor* node only (its WiFi lower layer
|
||||
is a closed blob exposing CSI *read*, not TX-feedback shaping); the protector
|
||||
needs **openwifi (SDR/FPGA), Nexmon (C firmware patches), or vendor
|
||||
firmware** + key agreement for the keyed-reversible version. See roadmap §P4.
|
||||
10. **`[docs]` Governance sourcing.** Fill the 802.11bf privacy-provision gap
|
||||
with primary WG minutes/draft; scope FCC Part 15, GDPR/HIPAA (inferred
|
||||
biometric/health), and EMSEC/ICD-705 deployability.
|
||||
|
||||
---
|
||||
|
||||
## 5. Open questions the evidence did not close
|
||||
|
||||
- Does VEIL's obfuscation degrade **CSI *reconstructed* from BFI** (BFIAttack),
|
||||
or only raise raw-BFI feature noise? *(the decisive effectiveness question)*
|
||||
- What is VEIL's **own MEASURED** privacy–throughput frontier on silicon (the
|
||||
only measured PDR number in the field was refuted; the DP curves are
|
||||
simulation-only)?
|
||||
- Does 802.11bf-2025 contain any privacy provision or a withdrawn one, and what
|
||||
are the concrete FCC/GDPR/HIPAA/ICD-705 deployment boundaries?
|
||||
|
||||
---
|
||||
|
||||
## Sources (primary, verified in this run)
|
||||
|
||||
- BFId — CCS 2025: https://dl.acm.org/doi/10.1145/3719027.3765062
|
||||
- LeakyBeam (attack + per-packet-unitary defense) — NDSS 2025: https://www.ndss-symposium.org/wp-content/uploads/2025-5-paper.pdf
|
||||
- BFIAttack (BFI→CSI reconstruction) — arXiv 2026: https://arxiv.org/html/2604.04179v1
|
||||
- WiKI-Eve — CCS 2023: https://dl.acm.org/doi/10.1145/3576915.3623088
|
||||
- SThief — IEEE: https://ieeexplore.ieee.org/document/10621321/
|
||||
- BeamSense/BFISense: https://www.researchgate.net/publication/402468114_BFISense_Using_Beamforming_Feedback_Information_for_Wi-Fi_Sensing
|
||||
- PrivISAC (RIS) — arXiv 2026: https://arxiv.org/html/2601.04488
|
||||
- DP-Givens — arXiv 2512.18529: https://arxiv.org/pdf/2512.18529
|
||||
- Adaptive-DP spectrogram — arXiv 2512.20323: https://arxiv.org/abs/2512.20323
|
||||
- BeamDancer — IEEE TWC 2024: https://ieeexplore.ieee.org/document/10739908/
|
||||
- TX-side CSI obfuscation — Computers & Security 2025: https://www.sciencedirect.com/science/article/abs/pii/S0167404825002834
|
||||
|
||||
*Refuted (do not cite): BeamDancer ">96% PDR in LoS" (verification 1–2). Two DP
|
||||
mechanisms are SYNTHETIC/CLAIMED, not silicon. Governance/standard pillar
|
||||
unverified in this run.*
|
||||
@@ -1,102 +0,0 @@
|
||||
# Privacy Shield Research Bundle — WiFi Veil
|
||||
|
||||
**WiFi Veil** (codename **VEIL** — Verifiable Emission-shaping for
|
||||
Identity-Leakage prevention) is a privacy *firewall* for WiFi sensing: it
|
||||
prevents unauthorized identity and
|
||||
activity inference from a room's WiFi while preserving normal communications. It
|
||||
is the **countermeasure** counterpart to [BFLD](../BFLD/) — where BFLD *detects*
|
||||
when beamforming feedback becomes identifying, WiFi Veil *acts* by shaping the node's
|
||||
own compliant waveform (channel sounding, precoder phase, beam/feedback
|
||||
schedules) so identity and activity inference fail, while a legitimate receiver
|
||||
sees an essentially unchanged link.
|
||||
|
||||
**This must use compliant waveform controls, never jamming.** Every technique
|
||||
here operates on the defender's *own* legitimately transmitted, standards-
|
||||
conformant frames. Nothing adds energy to interfere with another station's
|
||||
transmission (the statutory definition of jamming, 47 U.S.C. §333/§302a).
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| [01-sota-survey.md](01-sota-survey.md) | State of the art: identity/activity inference attacks (BFI + CSI), the IEEE 802.11bf-2025 standard, and privacy-preserving countermeasures |
|
||||
| [02-threat-model.md](02-threat-model.md) | Adversary classes, what WiFi Veil defends and what it explicitly does not, trust boundary |
|
||||
| [03-countermeasure-design.md](03-countermeasure-design.md) | The compliant waveform controls, the separable-subspace principle, keyed Givens-rotation shield, and how it maps to the crate |
|
||||
| [04-compliance-and-regulatory.md](04-compliance-and-regulatory.md) | The legal line between compliant waveform control and jamming, with statutory citations |
|
||||
| [05-experiment-protocol.md](05-experiment-protocol.md) | The attacker-vs-protector experiment: metrics, acceptance bar, reproducer, and results |
|
||||
| [06-market-and-buyers.md](06-market-and-buyers.md) | First buyers, procurement drivers, competitive landscape, and the standards-body gap |
|
||||
| [07-implementation-and-roadmap.md](07-implementation-and-roadmap.md) | Crate layout, reuse map, hardware path, phased rollout, and open problems |
|
||||
| [08-optimization.md](08-optimization.md) | Hyper-optimization: throughput-optimal feedback resolution, minimum robust mixing budget, Pareto frontier, and the adopted config |
|
||||
| [09-sota-update-2026.md](09-sota-update-2026.md) | 2025–2026 SOTA update (verified, cited): stronger attacks (BFI→CSI reconstruction, through-wall vitals, keystroke), validated compliant defenses, and the derived WiFi Veil improvement backlog |
|
||||
|
||||
Formal decision: [ADR-288](../../adr/ADR-288-veil-privacy-shield-compliant-waveform.md).
|
||||
Reference implementation: [`v2/crates/wifi-densepose-privshield`](../../../v2/crates/wifi-densepose-privshield).
|
||||
|
||||
---
|
||||
|
||||
## Executive summary
|
||||
|
||||
1. **The threat is real and now standardized.** IEEE 802.11ac/ax beamforming
|
||||
feedback (BFI) — the compressed Givens-rotation angle matrices (φ/ψ) a client
|
||||
sends the AP — travels **unencrypted on the management plane**. Any device in
|
||||
monitor mode can capture it for every client at once, no network access, and
|
||||
the target need carry no device. **BFId** (KIT, ACM CCS 2025) re-identifies
|
||||
individuals from BFI alone; **LeakyBeam** (NDSS 2025) detects occupancy
|
||||
through walls at ~20 m from BFI; **BeamSense** recognizes activities at up to
|
||||
99.28% from BFI. IEEE Std **802.11bf-2025** (published 26 Sep 2025)
|
||||
standardizes the sensing measurement/feedback surface these attacks abuse.
|
||||
|
||||
2. **The standards body declined to fix it.** A 2023 proposal for a BFI
|
||||
"secure transmission mechanism" (IEEE 802.11-23/0782) was **withdrawn** —
|
||||
the working group did not align on characterizing sensing privacy as a
|
||||
distinct problem. 802.11bf shipped without privacy protections. This is the
|
||||
single strongest demand signal: the gap is structural and acknowledged.
|
||||
|
||||
3. **No targeted anti-sensing product ships (as of 2026).** Every countermeasure
|
||||
in the literature — IRShield, PhyCloak, MIMOCrypt, DP-Givens dithering,
|
||||
ScatterShield — is research-stage. The only shipping substitute is broadband
|
||||
RF shielding (SCIF/TEMPEST film/paint), which is blunt: it kills *all* RF and
|
||||
cannot coexist with wanted WiFi. The whitespace is a **selective, coexisting,
|
||||
software/PHY** shield.
|
||||
|
||||
4. **The WiFi Veil mechanism.** Identity leaks through the *fine* cross-subcarrier
|
||||
phase structure of a beamforming report; throughput rides the *dominant*
|
||||
beam direction. These are (mostly) separable subspaces. WiFi Veil composes extra
|
||||
**keyed Givens rotations** over the fine subspace only. The rotation is
|
||||
*orthogonal* (energy-preserving ⇒ not jamming), *keyed per session* (the
|
||||
legitimate receiver inverts it ⇒ throughput preserved), and *fresh each
|
||||
session* (a sniffer cannot average it back ⇒ re-ID collapses to chance).
|
||||
|
||||
5. **Measured on the reference model (SYNTHETIC), at the hyper-optimized
|
||||
operating point.** On the default synthetic scene (16 candidate identities),
|
||||
a passive re-identifier scores **100% with the shield off** and **4.7% with
|
||||
it on** (chance = 6.25%), while modeled link throughput stays at **97.6%** of
|
||||
baseline and the emission energy ratio is **1.000000** (compliant). The shield
|
||||
config is chosen by the `optimize` module — 96 Givens passes (2× the proven-
|
||||
minimum 48 for robust collapse across both attacker metrics and N∈{16,32}) at
|
||||
5-bit feedback resolution — not hand-picked (see
|
||||
[08-optimization.md](08-optimization.md)). Reproduce:
|
||||
`cargo test -p wifi-densepose-privshield`.
|
||||
|
||||
6. **Scope, honestly.** WiFi Veil defends against a *third-party passive sniffer*. It
|
||||
does **not** hide identity from the associated AP (that party holds the key)
|
||||
— that is BFLD's detection/policy problem. WiFi Veil is a reference model, not
|
||||
hardware: real-silicon validation (per CLAUDE.md) is future work with a
|
||||
captured-log witness.
|
||||
|
||||
---
|
||||
|
||||
## Evidence discipline
|
||||
|
||||
Per repository policy, every quantitative claim is tagged:
|
||||
|
||||
- **MEASURED** — from a cited primary source with its metric and conditions.
|
||||
- **CLAIMED** — asserted by a source (vendor PR, press, standards minutes)
|
||||
without an independent measurement.
|
||||
- **SYNTHETIC** — produced by WiFi Veil's own deterministic model; reproduced by
|
||||
`cargo test`, describing the model and not real hardware.
|
||||
|
||||
WiFi sensing is never presented here as camera-grade, and no WiFi Veil result implies
|
||||
a defense guarantee on real silicon until a hardware witness exists.
|
||||
@@ -1,306 +0,0 @@
|
||||
# Coherent Wideband RF Tomography: Simulating and Reconstructing with `wifi-densepose-sar`
|
||||
|
||||
A walkthrough of the `wifi-densepose-sar` crate (ADR-287): simulating
|
||||
synthetic-aperture radar (SAR) style measurements and reconstructing a 3D
|
||||
reflectivity image from them via delay-and-sum backprojection.
|
||||
|
||||
**Estimated time:** 30 minutes.
|
||||
|
||||
**What you will build:** A small Rust program that simulates a handheld
|
||||
stepped-frequency radar sweep past a couple of point targets, reconstructs
|
||||
a 3D image from the resulting complex measurements, and extracts a sparse
|
||||
point cloud from it — then verifies the reconstruction's resolution
|
||||
against closed-form theory.
|
||||
|
||||
**Who this is for:** Rust developers comfortable with basic signal
|
||||
processing terminology (frequency, bandwidth, phase) who want to
|
||||
understand what a coherent RF imaging pipeline actually computes, or who
|
||||
are evaluating whether this crate is a useful building block for their own
|
||||
radar-imaging research.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [What This Is (and Isn't)](#1-what-this-is-and-isnt)
|
||||
2. [Prerequisites](#2-prerequisites)
|
||||
3. [The Physics in Five Minutes](#3-the-physics-in-five-minutes)
|
||||
4. [Your First Reconstruction](#4-your-first-reconstruction)
|
||||
5. [Range Resolution: Why Bandwidth Matters](#5-range-resolution-why-bandwidth-matters)
|
||||
6. [Cross-Range Resolution: Why You Need to Move the Antenna](#6-cross-range-resolution-why-you-need-to-move-the-antenna)
|
||||
7. [The Antenna-Pose Coherence Budget](#7-the-antenna-pose-coherence-budget)
|
||||
8. [Extracting a Point Cloud](#8-extracting-a-point-cloud)
|
||||
9. [Benchmarking Your Own Scenario](#9-benchmarking-your-own-scenario)
|
||||
10. [Where This Could Go Next](#10-where-this-could-go-next)
|
||||
11. [Troubleshooting](#11-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## 1. What This Is (and Isn't)
|
||||
|
||||
This crate exists because of a real question: could this repo build
|
||||
something like [Applied Electrodynamics' WaveSight](https://www.ae-dyn.com/)
|
||||
— a handheld device that images through walls using radio waves? The
|
||||
honest answer, worked out in ADR-287, is **no, not as a hardware product**
|
||||
— that needs a custom coherent RF front end, a calibrated antenna array,
|
||||
and real-time reconstruction hardware, which is an 18–36 month, high
|
||||
six-to-seven-figure hardware engineering program, not a software change.
|
||||
|
||||
What *is* useful to build, and what this crate is, is the **reconstruction
|
||||
algorithm** such a device needs: given coherent, phase-preserving,
|
||||
stepped-frequency measurements recorded from several known antenna
|
||||
positions, recover the 3D locations of the things that reflected the
|
||||
signal. That's a well-understood problem (synthetic-aperture radar,
|
||||
ground-penetrating radar imaging, and microwave tomography all solve
|
||||
versions of it) with textbook closed-form math behind it.
|
||||
|
||||
Every number in this crate comes from its own **synthetic forward
|
||||
simulator** — there is no real radio hardware anywhere in this crate, and
|
||||
none of its tests, benchmarks, or accuracy numbers say anything about how
|
||||
well a real device would perform through a real wall. That's evidence
|
||||
level **L0 (Synthetic)** in this repo's [ADR-282](../adr/ADR-282-ruview-ecosystem-positioning.md)
|
||||
evidence ladder, and it stays L0 until (if ever) real wideband RF hardware
|
||||
feeds this pipeline real measurements.
|
||||
|
||||
## 2. Prerequisites
|
||||
|
||||
- Rust 1.75+ (workspace MSRV), already set up if you can build the rest of
|
||||
this repo's `v2/` workspace.
|
||||
- No special hardware. Everything in this tutorial runs from synthetic
|
||||
data.
|
||||
|
||||
```bash
|
||||
cd v2
|
||||
cargo test -p wifi-densepose-sar --no-default-features
|
||||
```
|
||||
|
||||
If that passes (24 tests, 0 failed), you're ready.
|
||||
|
||||
## 3. The Physics in Five Minutes
|
||||
|
||||
A stepped-frequency radar sweeps `K` frequencies `f_0..f_{K-1}` across a
|
||||
band of total width `B` (the bandwidth). At each of `M` antenna positions
|
||||
`p_0..p_{M-1}` along a handheld sweep, it records one complex number per
|
||||
frequency — amplitude and phase, not just amplitude, which is what makes
|
||||
this "coherent."
|
||||
|
||||
For a point scatterer at position `x` with reflectivity `σ`, range
|
||||
`R = |p_m - x|` from antenna position `m`, the forward model this crate
|
||||
simulates is:
|
||||
|
||||
```text
|
||||
y_{m,k} = sigma / R^2 * exp(-i * 4*pi * f_k * R / c)
|
||||
```
|
||||
|
||||
`4*pi*f*R/c` is the two-way (round-trip) propagation phase; `1/R^2` is the
|
||||
two-way free-space spreading loss. With several targets, the measurement
|
||||
is just the sum of each target's contribution (superposition — this crate
|
||||
never models multipath/interaction between targets, only free-space direct
|
||||
paths).
|
||||
|
||||
**Reconstruction (backprojection)** inverts this: for every candidate
|
||||
voxel `x` in a 3D grid, it multiplies each measurement by the *complex
|
||||
conjugate* of the phase the forward model would have applied for a target
|
||||
at `x`, then sums:
|
||||
|
||||
```text
|
||||
I(x) = | (1/MK) * sum_m sum_k y_{m,k} * R_{m,x}^2 * exp(+i * 4*pi * f_k * R_{m,x} / c) |
|
||||
```
|
||||
|
||||
If `x` coincides with a real target, every term's phase correction exactly
|
||||
cancels the phase the forward model applied — the sum adds up
|
||||
constructively ("coherent gain"). At any other voxel, the phases are
|
||||
essentially uncorrelated across the `(m, k)` grid and the sum averages
|
||||
toward zero. That's the entire algorithm: matched filtering, done in 3D,
|
||||
one voxel at a time.
|
||||
|
||||
## 4. Your First Reconstruction
|
||||
|
||||
Add `wifi-densepose-sar` to a scratch binary or run this in a workspace
|
||||
example. It simulates two targets, reconstructs, and finds the brightest
|
||||
voxel:
|
||||
|
||||
```rust
|
||||
use wifi_densepose_sar::{
|
||||
backproject, linear_aperture, simulate_measurement, FrequencySweep,
|
||||
Point3, ScatteringTarget, VoxelGrid,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
// A 1-meter handheld sweep, 21 antenna positions along it.
|
||||
let poses = linear_aperture(
|
||||
Point3::new(-0.5, 0.0, 0.0),
|
||||
Point3::new(0.5, 0.0, 0.0),
|
||||
21,
|
||||
);
|
||||
|
||||
// Sweep 2-6 GHz (4 GHz of bandwidth) in 32 steps.
|
||||
let sweep = FrequencySweep::new(2.0e9, 6.0e9, 32);
|
||||
|
||||
// One target, 2 meters downrange, reflectivity 1.0 (arbitrary units).
|
||||
let target = ScatteringTarget::new(Point3::new(0.0, 2.0, 0.0), 1.0);
|
||||
|
||||
// Simulate the measurement with a touch of noise (seeded -- rerunning
|
||||
// with the same seed gives byte-identical output).
|
||||
let measurement = simulate_measurement(&poses, &sweep, &[target], 0.01, 42);
|
||||
|
||||
// Reconstruct a 21x21x21 voxel grid around where we expect the target.
|
||||
let grid = VoxelGrid::new(Point3::new(-0.3, 1.7, -0.3), 0.03, 21, 21, 21);
|
||||
let image = backproject(&measurement, &poses, &sweep, &grid);
|
||||
|
||||
let (peak_location, peak_magnitude) = image.peak();
|
||||
println!("true target: {:?}", target.position);
|
||||
println!("reconstructed peak: {peak_location:?} (magnitude {peak_magnitude:.4})");
|
||||
}
|
||||
```
|
||||
|
||||
Run it and you should see the reconstructed peak within a couple of
|
||||
centimeters of the true target position — well inside the voxel spacing
|
||||
used here (3 cm). That's `tests/reconstruct.rs::single_point_target_reconstructs_at_its_true_location`
|
||||
running live.
|
||||
|
||||
## 5. Range Resolution: Why Bandwidth Matters
|
||||
|
||||
How close together can two targets be *along the same bearing* (same
|
||||
antenna, different distance) before they blur into one blob? The classic
|
||||
radar answer: `ΔR = c / (2B)` — resolution improves with more swept
|
||||
bandwidth, full stop. Carrier frequency, antenna count, and aperture
|
||||
length don't enter into it at all.
|
||||
|
||||
```rust
|
||||
use wifi_densepose_sar::resolution::range_resolution_m;
|
||||
|
||||
let dr = range_resolution_m(4.0e9); // 4 GHz swept bandwidth
|
||||
println!("range resolution: {:.1} cm", dr * 100.0);
|
||||
// -> range resolution: 3.7 cm
|
||||
```
|
||||
|
||||
`tests/physics_validation.rs::range_separated_targets_resolve_only_beyond_range_resolution`
|
||||
proves this isn't just a formula sitting in a doc comment: it forward-simulates
|
||||
two targets 4x `ΔR` apart (they resolve into two distinct peaks) and 0.25x
|
||||
`ΔR` apart (they merge into one), using the *same* `range_resolution_m`
|
||||
call to pick the separations.
|
||||
|
||||
## 6. Cross-Range Resolution: Why You Need to Move the Antenna
|
||||
|
||||
A single antenna position, no matter how much bandwidth it sweeps, cannot
|
||||
tell two targets apart if they're at the same range but different bearing
|
||||
— all it measures is round-trip distance, which is the same for both. This
|
||||
is exactly why "handheld... sweep the antenna around" matters: moving the
|
||||
antenna across a synthetic aperture of length `L` gives you angular
|
||||
information, with cross-range resolution:
|
||||
|
||||
```text
|
||||
delta_CR ~= lambda * R / (2 * L)
|
||||
```
|
||||
|
||||
— finer with a longer aperture, a shorter wavelength (higher carrier
|
||||
frequency), or a closer target.
|
||||
|
||||
```rust
|
||||
use wifi_densepose_sar::resolution::cross_range_resolution_m;
|
||||
|
||||
let short = cross_range_resolution_m(4.0e9, 0.05, 2.0); // 5cm sweep
|
||||
let long = cross_range_resolution_m(4.0e9, 1.0, 2.0); // 1m sweep
|
||||
println!("5cm aperture: {:.2} m cross-range resolution", short);
|
||||
println!("1m aperture: {:.2} m cross-range resolution", long);
|
||||
// -> a 20x longer aperture gives 20x finer cross-range resolution
|
||||
```
|
||||
|
||||
`tests/physics_validation.rs::cross_range_separated_targets_resolve_only_with_long_enough_aperture`
|
||||
demonstrates this end-to-end: the same pair of cross-range-separated
|
||||
targets resolves into two peaks with a 1m synthetic aperture and collapses
|
||||
into one with a 5cm aperture, no other change.
|
||||
|
||||
## 7. The Antenna-Pose Coherence Budget
|
||||
|
||||
Backprojection assumes you know exactly where the antenna was at each
|
||||
measurement. If your position tracking (in a real device: visual-inertial
|
||||
odometry, encoders, whatever) is off by `Δp`, the phase correction applied
|
||||
during reconstruction is wrong by an amount that grows with `Δp` and with
|
||||
frequency. The classical rule of thumb for "still well focused": keep the
|
||||
round-trip path error under a quarter wavelength, which works out to an
|
||||
antenna-position tolerance of `λ/8`:
|
||||
|
||||
```rust
|
||||
use wifi_densepose_sar::resolution::max_coherent_pose_error_m;
|
||||
|
||||
let budget = max_coherent_pose_error_m(8.0e9); // 8 GHz carrier
|
||||
println!("position tolerance at 8 GHz: {:.1} mm", budget * 1000.0);
|
||||
// -> position tolerance at 8 GHz: 4.7 mm
|
||||
```
|
||||
|
||||
`tests/physics_validation.rs::phase_error_from_pose_jitter_degrades_focus_beyond_pose_budget`
|
||||
verifies this isn't just asserted: it perturbs the *true* antenna positions
|
||||
away from the *assumed* ones used in reconstruction, and shows focus at the
|
||||
true target location degrades as that perturbation grows — the concrete
|
||||
mechanism behind why real SAR/GPR imaging systems need accurate pose
|
||||
tracking, not just a good radio.
|
||||
|
||||
## 8. Extracting a Point Cloud
|
||||
|
||||
A dense voxel grid isn't a useful end product — you want a short list of
|
||||
detected points:
|
||||
|
||||
Continuing the program from §4 (which already has `image` in scope):
|
||||
|
||||
```rust
|
||||
use wifi_densepose_sar::extract_point_cloud;
|
||||
|
||||
let points = extract_point_cloud(&image, 0.5); // 50%-of-peak threshold
|
||||
for p in &points {
|
||||
println!("{:?} magnitude={:.3}", p.position, p.magnitude);
|
||||
}
|
||||
```
|
||||
|
||||
`extract_point_cloud` does threshold + 6-connected local-maximum
|
||||
extraction — a real blob will still yield one point, not one per voxel
|
||||
inside it. There is deliberately no clustering, material classification,
|
||||
or confidence calibration here (ADR-287 §5): that needs real data to
|
||||
calibrate against, which this crate does not have.
|
||||
|
||||
## 9. Benchmarking Your Own Scenario
|
||||
|
||||
```bash
|
||||
cargo bench -p wifi-densepose-sar
|
||||
```
|
||||
|
||||
The shipped benchmark (`benches/backprojection_bench.rs`) sweeps 512 /
|
||||
4,096 / 32,768-voxel grids with 21 poses x 32 frequencies. Reconstruction
|
||||
is embarrassingly parallel over voxels (each voxel's cost is independent),
|
||||
so it's rayon-parallelized already — see the crate README for the last
|
||||
recorded MEASURED numbers on the reference machine.
|
||||
|
||||
## 10. Where This Could Go Next
|
||||
|
||||
This crate deliberately stops short of several things (ADR-287 §5):
|
||||
|
||||
- It's monostatic (one antenna, both TX and RX) — real handheld SAR/MIMO
|
||||
devices often use multiple simultaneous antenna elements.
|
||||
- The forward model is free-space only — no multipath, no per-material
|
||||
attenuation (contrast `ruview-unified`'s narrowband Fresnel material
|
||||
model, which isn't yet extended to wideband).
|
||||
- It isn't wired into `ruview-unified`'s `FmcwRadarCube` adapter or
|
||||
`GaussianMap` — ADR-278 names that as the eventual integration point,
|
||||
once (and if) a reconstruction system is ready for it.
|
||||
|
||||
If you're picking this up to extend it, start with ADR-287's "Follow-up"
|
||||
section rather than guessing at scope.
|
||||
|
||||
## 11. Troubleshooting
|
||||
|
||||
**"My reconstructed peak isn't near my target."** Check your voxel grid
|
||||
actually covers the target's true location — `backproject` happily
|
||||
reconstructs whatever region you ask for; if the target is outside the
|
||||
grid, you'll get whatever's brightest inside it instead (usually noise).
|
||||
|
||||
**"Two targets I expected to resolve didn't."** Compute
|
||||
`range_resolution_m`/`cross_range_resolution_m` for your actual bandwidth
|
||||
and aperture length and check your separation against them — resolution
|
||||
is a hard physical limit here, not a tuning parameter.
|
||||
|
||||
**"Backprojection is slow for my grid size."** Cost is
|
||||
`O(voxels x poses x freqs)` and already parallelized over voxels via
|
||||
rayon; the only way to go faster is fewer voxels, fewer poses, or fewer
|
||||
frequency steps (each is a hard tradeoff against resolution or aperture
|
||||
coverage — see §5/§6).
|
||||
@@ -38,7 +38,6 @@ WiFi DensePose turns commodity WiFi signals into real-time human pose estimation
|
||||
14. [Training a Model](#training-a-model)
|
||||
- [CRV Signal-Line Protocol](#crv-signal-line-protocol)
|
||||
14. [RVF Model Containers](#rvf-model-containers)
|
||||
14. [Perception Certificate Spine (Developer Preview, ADR-300)](#perception-certificate-spine-developer-preview-adr-297)
|
||||
14. [Hardware Setup](#hardware-setup)
|
||||
- [ESP32-S3 Mesh](#esp32-s3-mesh)
|
||||
- [Intel 5300 / Atheros NIC](#intel-5300--atheros-nic)
|
||||
@@ -136,7 +135,7 @@ The compiled binary is at `target/release/sensing-server`.
|
||||
|
||||
### From crates.io (Individual Crates)
|
||||
|
||||
The workspace's crates publish independently, so versions vary crate to crate (`wifi-densepose-core` is at 0.3.2, `wifi-densepose-signal` at 0.3.6, etc. as of this writing) — `cargo add` resolves each to its own latest by default, so you don't need to track exact numbers yourself. Add individual crates to your own Rust project:
|
||||
All 16 crates are published to crates.io at v0.3.0. Add individual crates to your own Rust project:
|
||||
|
||||
```bash
|
||||
# Core types and traits
|
||||
@@ -162,11 +161,6 @@ cargo add wifi-densepose-wasm
|
||||
|
||||
# WASM edge runtime (lightweight, for embedded/IoT)
|
||||
cargo add wifi-densepose-wasm-edge
|
||||
|
||||
# Coherent wideband RF tomography research crate (ADR-287) — synthetic
|
||||
# stepped-frequency backprojection reconstruction. SYNTHETIC/L0 evidence
|
||||
# only; not wired into any sensing pipeline above. See its own README.
|
||||
cargo add wifi-densepose-sar
|
||||
```
|
||||
|
||||
See the full crate list and dependency order in [CLAUDE.md](../CLAUDE.md#crate-publishing-order).
|
||||
@@ -1494,78 +1488,6 @@ An RVF file contains: model weights, HNSW vector index, quantization codebooks,
|
||||
|
||||
---
|
||||
|
||||
## Perception Certificate Spine (Developer Preview, ADR-300)
|
||||
|
||||
RuView's perception substrate program (ADR-300) is building a `signal → observation →
|
||||
calibration → inference → uncertainty → evidence → certificate → policy → governed
|
||||
action` pipeline, where a downstream consumer either gets a calibrated, provenance-backed
|
||||
answer or an explicit `UNKNOWN` — never a confident-looking guess outside the sensor's
|
||||
proven operating envelope.
|
||||
|
||||
**Status: developer preview.** Phase 1 shipped nine new crates with their own test
|
||||
suites, and each one works correctly in isolation. **They are not yet wired together or
|
||||
into the live `sensing-server` request path** — there is currently no code path where a
|
||||
real drift signal from a running sensor flows through calibration → certificate
|
||||
invalidation → policy denial. Treat everything below as a library you can compose
|
||||
yourself today, not a safety guarantee the server enforces for you yet.
|
||||
|
||||
### The crates
|
||||
|
||||
| Crate | Role |
|
||||
|---|---|
|
||||
| `ruview-ontology` | Canonical `Site → … → Event` types |
|
||||
| `ruview-attest` | Signed measurement / RF chain-of-custody |
|
||||
| `ruview-evidence` | Append-only per-context ledger (no pooling, no evidence upgrade) |
|
||||
| `wifi-densepose-calibration` | Signed, drift-invalidatable calibration certificate |
|
||||
| `ruview-ood` | `Known` / `Degraded` / `Unknown` staleness-guard domain gating |
|
||||
| `ruview-witness` | Hash-linked staged provenance chain |
|
||||
| `ruview-certify` | Capability certificate, conditional on a live domain signature |
|
||||
| `ruview-scorecard` | Multi-domain scorecard, worst-domain promotion gate |
|
||||
| `ruview-policy` | Fail-closed action authorization gate |
|
||||
|
||||
### Minting and checking a certificate
|
||||
|
||||
```rust
|
||||
use ruview_certify::{mint, CapabilityCertificate, DomainState};
|
||||
|
||||
// `signer`, `request`, and `evidence_slice` come from your own calibration run —
|
||||
// see each crate's README for how to build them.
|
||||
let cert = mint(&signer, request, &evidence_slice)?;
|
||||
|
||||
// A certificate is only valid at a given instant AND domain state — the same
|
||||
// signed certificate is rejected the moment the live domain degrades:
|
||||
assert!(cert.is_valid(now_ms, DomainState::Known));
|
||||
assert!(!cert.is_valid(now_ms, DomainState::Degraded));
|
||||
assert!(!cert.is_valid(now_ms, DomainState::Unknown));
|
||||
```
|
||||
|
||||
### Gating an action
|
||||
|
||||
```rust
|
||||
use ruview_policy::{authorize, ActionClass, DomainState};
|
||||
|
||||
let decision = authorize(ActionClass::SafetyCritical, &inputs);
|
||||
// Deny with a named FailedCondition (e.g. `domain_not_known`) rather than a
|
||||
// silent false-positive, whenever the domain isn't KNOWN.
|
||||
```
|
||||
|
||||
**Important:** `ruview_certify::DomainState` and `ruview_policy::DomainState` (and
|
||||
`ruview_ood`'s) are currently three separate enum types — `ruview-ood`'s `Degraded`
|
||||
variant even carries different data. There is no automatic conversion between them.
|
||||
If you compose these crates yourself today, you own writing that bridge; don't assume
|
||||
one crate's domain read automatically reaches another's gate.
|
||||
|
||||
### What's genuinely enforced today, for comparison
|
||||
|
||||
Not every ADR-295–296 remediation item is preview-only. Two are live now:
|
||||
|
||||
- **UDP data-plane bind hardening (ADR-296)** — `sensing-server`'s `UdpSourceAllowlist`
|
||||
is checked on every incoming packet (`main.rs`), not just defined.
|
||||
- **CSI data-incident repo controls (ADR-299)** — `scripts/csi-data-policy-check.sh`
|
||||
runs in CI on every push/PR and fails the build on a policy violation.
|
||||
|
||||
---
|
||||
|
||||
## Hardware Setup
|
||||
|
||||
### Supported targets
|
||||
|
||||
@@ -123,7 +123,7 @@ esp_err_t c6_softap_he_start(uint8_t *out_channel)
|
||||
if (ssid_len > 32) ssid_len = 32;
|
||||
memcpy(ap_cfg.ap.ssid, ssid, ssid_len);
|
||||
ap_cfg.ap.ssid_len = (uint8_t)ssid_len;
|
||||
strlcpy((char *)ap_cfg.ap.password, psk, sizeof(ap_cfg.ap.password));
|
||||
strncpy((char *)ap_cfg.ap.password, psk, sizeof(ap_cfg.ap.password) - 1);
|
||||
ap_cfg.ap.channel = s_channel;
|
||||
ap_cfg.ap.max_connection = 4;
|
||||
ap_cfg.ap.authmode = strlen(psk) >= 8 ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user