Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions docs/adr/ADR-100-cog-packaging-specification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# ADR-100: Cognitum Cog Packaging Specification

- **Status:** Accepted (formalises existing convention)
- **Date:** 2026-05-19
- **Deciders:** ruv

## Context

The Cognitum V0 Appliance (`/var/lib/cognitum/apps/`) deploys discrete units called **Cogs**. They appear in the Appliance dashboard (`http://cognitum-v0:9000/cogs`) under an app-store UI (Today / Apps / Categories / Search / Updates). Until this ADR, the packaging convention has been **implicit** — derived from inspecting installed cogs (`anomaly-detect`, `presence`, `seizure-detect`, etc.) on a live appliance. Bringing new Cogs to the platform required reverse-engineering the layout each time.

This ADR formalises the layout so:

1. A repo crate can be built into a Cog with a deterministic Makefile / CI pipeline.
2. Cog binaries can be cross-compiled for every supported architecture from a single source.
3. The appliance's installer (`cognitum-cog-gateway`) can verify manifests without bespoke per-cog adapters.
4. Future Cogs in this repo (starting with `cog-pose-estimation` — see ADR-101) follow a single rule.

## Decision

### On-device layout

Each installed Cog lives at:

```
/var/lib/cognitum/apps/<cog-id>/
├── cog-<cog-id>-<arch> # single self-contained executable
├── manifest.json # immutable; signed by the publisher
├── config.json # mutable; runtime config, owned by the appliance
├── pid # current PID when running; absent when stopped
├── output.log # stdout (truncated on rotation)
└── error.log # stderr (truncated on rotation)
```

`<cog-id>` is kebab-case, ASCII, `[a-z0-9-]{2,32}`. `<arch>` is one of:

| arch | target triple | hardware |
|------|---------------|----------|
| `arm` | `aarch64-unknown-linux-gnu` | Raspberry Pi 5 (cognitum-v0, cluster Pis) |
| `x86_64` | `x86_64-unknown-linux-gnu` | ruvultra, generic Linux dev |
| `hailo8` | `aarch64-unknown-linux-gnu` + Hailo HEF sidecar | Pi + Hailo-8 hat (26 TOPS) |
| `hailo10` | `aarch64-unknown-linux-gnu` + Hailo HEF sidecar | Pi + Hailo-10 hat (40 TOPS) |

### `manifest.json` schema

```json
{
"id": "anomaly-detect",
"version": "0.1.0",
"binary_url": "https://storage.googleapis.com/cognitum-apps/cogs/arm/cog-anomaly-detect-arm",
"binary_bytes": 461904,
"binary_sha256": "<hex>",
"binary_signature": "<base64 Ed25519 sig over binary_sha256, signed with COGNITUM_OWNER_SIGNING_KEY>",
"installed_at": 1778772536,
"status": "installed"
}
```

Fields:

- `id`, `version`, `binary_url`, `binary_bytes`, `installed_at`, `status` — already implemented and observed in production manifests (e.g. `anomaly-detect@0.0.0`). Documented here without change.
- `binary_sha256`, `binary_signature` — **new**, REQUIRED for any Cog shipped from this repo. Backwards-compatible with existing manifests: the appliance gateway treats both fields as optional today, MUST verify them when present. ADR-103 (witness chain) covers the trust model in more detail.
- `status` values: `"installed"`, `"running"`, `"stopped"`, `"failed"`, `"updating"`.

### Binary hosting

Cog binaries live in **Google Cloud Storage**, public-read, at:

```
gs://cognitum-apps/cogs/<arch>/cog-<id>-<arch>
```

The HTTPS form is `https://storage.googleapis.com/cognitum-apps/cogs/<arch>/cog-<id>-<arch>` (no trailing extension; the URL is the canonical artifact). For Hailo variants, the HEF model file is sibling: `cog-<id>-<arch>.hef`.

Bucket conventions:

- Bucket is public-read; write requires `roles/storage.objectAdmin` in project `cognitum-20260110`.
- Per-version artifacts must be content-addressed: `cogs/<arch>/cog-<id>-<arch>@<sha256-prefix>` is the immutable copy; the un-suffixed name is a symlink that updates on release.
- `COGNITUM_OWNER_SIGNING_KEY` (GCP Secret Manager) signs every binary before upload.

### Source-tree layout (this repo)

Each Cog lives under `v2/crates/cog-<id>/`:

```
v2/crates/cog-<id>/
├── Cargo.toml # crate name = cog-<id>; binary = cog-<id>
├── src/
│ ├── main.rs # CLI: cog-<id> run | status | version
│ ├── lib.rs
│ └── inference.rs # the actual work
├── cog/
│ ├── manifest.template.json
│ ├── config.schema.json # JSON schema for runtime config
│ ├── README.md # consumer-facing description (used by the App Store UI)
│ ├── icon.svg # 1024×1024 icon (used by App Store hero)
│ └── Makefile # build / sign / upload targets
└── tests/
├── smoke.rs
└── manifest_signature.rs
```

### Build pipeline

```
cd v2/crates/cog-<id>
make build-arm # cross-compile to aarch64-unknown-linux-gnu
make build-x86_64 # x86_64 Linux build
make build-hailo8 # arm + HEF compilation (requires Hailo Dataflow Compiler)
make build-hailo10 # arm + HEF compilation
make sign # produce binary_sha256 + binary_signature
make upload # gsutil cp to gs://cognitum-apps/cogs/<arch>/
make manifest # emit manifest.json with all fields filled
```

CI (GitHub Actions) MUST run `make build-arm` + `make build-x86_64` on every PR touching `v2/crates/cog-*/`. Hailo HEF compilation requires the proprietary Hailo SDK and runs only on the Hailo-capable runners (currently a labelled self-hosted runner on the Pi cluster — TBD, separate ADR).

### Runtime contract

A Cog binary MUST implement:

| Subcommand | Behaviour |
|-----------|-----------|
| `cog-<id> version` | Print `<id> <version>` and exit 0. |
| `cog-<id> manifest` | Print the embedded manifest JSON and exit 0. |
| `cog-<id> run --config /path/to/config.json` | Long-running. Writes structured JSON logs to stdout (parsed by `cognitum-cog-gateway`). Exit code 0 on graceful shutdown, non-zero on fatal error. |
| `cog-<id> health` | One-shot. Exit 0 if the cog could come up healthy; non-zero with diagnostic on stderr. Called by the gateway before `run`. |

stdout JSON line format (one event per line):

```json
{"ts": 1779210883.444, "level": "info", "event": "<event-name>", "fields": { ... }}
```

## Consequences

### Positive

- New Cogs can be added without RE-ing the layout each time.
- CI can verify the manifest schema before merge.
- Signed binaries close a real supply-chain gap — current installed cogs (`anomaly-detect@0.0.0`) have no signature, and a compromised GCS object could push malicious code to every appliance.
- The runtime contract (`run | health | version | manifest`) is uniform across cogs, so `cognitum-cog-gateway` can stop carrying per-cog adapters.

### Negative

- Existing installed cogs must be re-published with signatures within one minor release of the gateway adopting the verify-when-present rule.
- Hailo HEF cross-compile is gated on a self-hosted runner; we accept that PRs touching Hailo variants will be slower to land.

### Risks

- **Signing key rotation**: `COGNITUM_OWNER_SIGNING_KEY` (Ed25519) is a single root-of-trust today. ADR-103 (witness chain) describes the rotation/recovery path; this ADR depends on that.
- **GCS bucket misconfiguration**: a public-read bucket with versioning-off could allow rollback attacks. Bucket MUST have Object Versioning enabled + 90-day non-current-version retention.

## Migration

1. Land this ADR.
2. Land ADR-101 (`cog-pose-estimation` — first Cog built to this spec).
3. After two clean releases of `cog-pose-estimation`, re-publish the existing cogs (`anomaly-detect`, `presence`, etc.) with `binary_sha256` + `binary_signature`. Track in a follow-up issue.
4. Flip `cognitum-cog-gateway` from "verify when present" to "require signature" — separate ADR, separate review.

## See also

- ADR-101: Pose Estimation Cog (first Cog built to this spec).
- ADR-103: Witness chain trust model (signing key rotation, future ADR).
- `docs/adr/ADR-079-camera-ground-truth-training.md` — the training pipeline behind `cog-pose-estimation`.
- `CLAUDE.local.md` § "Fleet Infrastructure (Tailscale)" — appliance layout this ADR describes.
178 changes: 178 additions & 0 deletions docs/adr/ADR-101-pose-estimation-cog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# ADR-101: Pose Estimation Cog (WiFi-DensePose side)

- **Status:** Accepted
- **Date:** 2026-05-19
- **Deciders:** ruv
- **Companion ADR (v0-appliance side):** v0-appliance ADR-225 (cognitum-pose-estimation crate)

## Context

ADR-079 designed the 17-keypoint COCO pose-estimation training pipeline. ADR-100 formalised the Cognitum Cog packaging spec. This ADR is the bridge: it specifies how the wifi-densepose training pipeline produces an artifact that ships as a Cog (`cog-pose-estimation`) onto the Cognitum V0 appliance and out to the Pi+Hailo cluster.

It is the next product step beyond the published `presence` Cog (binary head trained from the contrastive encoder on Hugging Face at `ruvnet/wifi-densepose-pretrained`). Where `presence` reports a single boolean per tick, `cog-pose-estimation` reports 17 (x, y) keypoints per person, per tick.

## Decision

### Pipeline

```
(training side — ruvultra GPU)
ESP32 / rvcsi ─► collect-ground-truth.py + sensing-server recording
data/paired/*.paired.jsonl (CSI window + camera keypoints)
v2/crates/wifi-densepose-train ──► Rust + libtorch trainer
(uses RTX 5080 / CUDA 12.x) │
init from ruvnet/wifi-densepose-pretrained
model.safetensors (encoder + pose head)
─────────────┴─────────────
│ │
▼ ▼
v2/crates/cog-pose-estimation export to ONNX
(this repo) │
• emits manifest.json ▼
• produces cog binary cognitum-hailo
• signs + uploads to GCS (v0-appliance side)
cog-pose-estimation.hef
(appliance side — cognitum-v0 + Pi+Hailo cluster)

gs://cognitum-apps/cogs/{arm,hailo8,hailo10}/cog-pose-estimation-<arch>
`cognitum-cog-gateway` pulls artifact + manifest, verifies signature, installs
into /var/lib/cognitum/apps/pose-estimation/
run loop: read CSI frames from local sensing-server
→ encoder → pose head → emit `{ts, persons: [{keypoints: [...17 x,y...] }]}`
on stdout as the Cog runtime contract requires
```

### Architecture (model)

| Stage | Module | Notes |
|-------|--------|-------|
| Input | `[56 subcarriers × 20 frames]` per CSI window | matches today's `data/paired/wiflow-p7-*.paired.jsonl` |
| Encoder | TCN-lite or contrastive encoder lifted from HF presence model | 128-dim embedding; weights init from `ruvnet/wifi-densepose-pretrained/model.safetensors` |
| Pose head | 2-layer MLP `(128 → 256 → 34)` | 34 = 17 × (x, y) |
| Output | `[B, 17, 2]` keypoints in `[0, 1]` image-normalised coords | confidence is implicit in keypoint variance over time; ADR-079 P9 will add explicit per-joint confidence |
| Loss | Confidence-weighted SmoothL1 (frame-level) + bone-length regulariser + temporal smoothness | per ADR-079 Phase 3 refinement |
| Init | Encoder = HF presence weights (frozen for 50 epochs, then jointly fine-tuned) | unblocks the sigmoid-saturation failure mode observed in #640 |
| Training | `v2/crates/wifi-densepose-train` with libtorch backend on RTX 5080 | replaces the pure-JS SPSA trainer that produced 0% PCK in #640 |

### Repo layout

```
v2/crates/cog-pose-estimation/ # NEW (this ADR)
├── Cargo.toml
├── src/
│ ├── main.rs # CLI: run | health | version | manifest
│ ├── lib.rs
│ ├── inference.rs # ONNX runtime + Hailo HEF runtime dispatch
│ ├── frame_subscriber.rs # local sensing-server subscriber
│ └── publisher.rs # emits structured JSON events per Cog contract
├── cog/
│ ├── manifest.template.json
│ ├── config.schema.json
│ ├── README.md
│ ├── icon.svg
│ └── Makefile # build-arm | build-x86_64 | sign | upload
└── tests/
├── manifest_signature.rs
└── inference_smoke.rs
```

### Runtime contract

Honours ADR-100's per-Cog CLI contract:

- `cog-pose-estimation version` → `pose-estimation 0.0.1`
- `cog-pose-estimation manifest` → JSON
- `cog-pose-estimation health` → 0 if encoder+head load and a synthetic frame produces a finite output
- `cog-pose-estimation run --config /etc/cognitum/cogs/pose-estimation/config.json` → long-running; emits one JSON event per inferred frame:

```json
{
"ts": 1779210883.444,
"level": "info",
"event": "pose.frame",
"fields": {
"tick": 12345,
"n_persons": 1,
"persons": [
{"keypoints": [[0.48, 0.31], [0.52, 0.28], ...], "confidence": 0.81}
]
}
}
```

### Hardware deployment

| Target | arch | runtime | notes |
|--------|------|---------|-------|
| ruvultra (dev) | `x86_64` | ONNX Runtime CPU/CUDA | development & smoke tests |
| cognitum-v0 (Pi 5) | `arm` | ONNX Runtime ARM | reference deploy; ~20 ms/frame |
| Pi + Hailo-8 hat | `hailo8` | Hailo HEF runtime via `cognitum-hailo` | ~2 ms/frame, 26 TOPS budget |
| Pi + Hailo-10 hat | `hailo10` | Hailo HEF runtime via `cognitum-hailo` | ~1 ms/frame, 40 TOPS budget |

### Acceptance gates

1. **Validates:** `cargo test -p cog-pose-estimation` green; `cog-pose-estimation health` returns 0 against a synthetic CSI window.
2. **Benchmarks:** end-to-end frame latency on each target arch logged in `target/criterion/`; published in `docs/benchmarks/pose-estimation-cog.md`.
3. **Optimised:** the Hailo-targeted ONNX graph passes through Hailo Dataflow Compiler without quantisation-aware-training warnings.
4. **Published:** signed binary at `gs://cognitum-apps/cogs/<arch>/cog-pose-estimation-<arch>`; manifest valid against the JSON schema in ADR-100; appliance installer can pull and run it.

PCK@20 is intentionally **not** an acceptance gate of this ADR. Achieving the ADR-079 ≥35% target is a separate, data-bound milestone tracked in #640. This ADR ships the **vehicle**, not the model accuracy.

### First measured run — v0.0.1 (2026-05-19)

A Candle-on-CUDA training run on `ruvultra`'s RTX 5080 against the same 1,077-sample paired session that produced the 0%/0% baseline in #640 yielded:

- **PCK@20 = 3.0%**, **PCK@50 = 18.5%**, **MPJPE = 0.093** (normalized).
- 400 epochs in **2.1 s** wall time (~5 ms/epoch, full-batch).
- Loss reduction 13× (0.181 → 0.014, eval 0.010).
- Strongest signal at `r_hip` (PCK@50 = 76.9%), `r_knee` (35.2%), `l_elbow` (26.4%).

This confirms the pipeline trains end-to-end and produces a signal-bearing model. The remaining gap to PCK@20 ≥ 35% is data-bound (1,077 samples is ≪ the ADR-079 target of ~30K). See `docs/benchmarks/pose-estimation-cog.md` for the full result dump.

## Consequences

### Positive

- First Cog from this repo that integrates with the appliance/cog-gateway pipeline. Future cogs (e.g. `cog-vitals`, `cog-fall-alert`) follow the same template.
- Closes the loop from data collection → training → quantisation → cluster deployment with a single repo-anchored artifact.
- Forces a real signature on cog binaries (per ADR-100), which improves supply-chain hygiene across the whole appliance.

### Negative

- Adds a hard dependency on the Hailo Dataflow Compiler, which lives behind a self-hosted runner — Hailo-targeted PRs land more slowly.
- The first published binary will have low PCK (data + training time gap, #640) — UX needs to surface this clearly so end users do not interpret bad keypoints as a bug.

### Risks

- **Model size on Hailo**: the encoder fits comfortably in Hailo-8's on-chip SRAM, but the pose-head expansion to `[17×2]` plus required temporal stacking pushes us close to the Hailo-8 envelope. Mitigation: Hailo-10 path is the primary deploy target; Hailo-8 is a stretch.
- **Sensing-server schema drift**: the cog subscribes to `/api/v1/sensing/latest` JSON. If the appliance's sensing-server schema changes, the cog fails open (logs warning, emits nothing). The `frame_subscriber.rs` module pins to schema version `2`.

## Migration / rollout

1. Land this ADR + ADR-100 on `main` of RuView.
2. Land companion ADR-225 + crate on `main` of v0-appliance.
3. First release `cog-pose-estimation@0.0.1` ships **only** to `ruvultra` and `cognitum-v0`. Not pushed to the cluster Pis yet.
4. After P7→P9 data work (#640) brings PCK above a usable threshold, rebuild + re-publish; only then enable cluster rollout via `cognitum-cog-gateway`'s OTA channel.

## See also

- ADR-079: Camera-supervised pose training pipeline (the model we're shipping).
- ADR-100: Cog packaging specification (the format we're shipping in).
- v0-appliance ADR-225: cognitum-pose-estimation crate (the appliance-side runtime).
- v0-appliance ADR-220: cog management surface (where this cog appears in the dashboard).
- Issue #640: PCK gap (current 0% → ≥35% target).
Loading
Loading