Skip to content

docs(rf-sensing): Phase 12 spec — HTTP route surface for wizard endpoints#321

Open
kmay89 wants to merge 1 commit intomainfrom
claude/review-latest-prs-EF21b
Open

docs(rf-sensing): Phase 12 spec — HTTP route surface for wizard endpoints#321
kmay89 wants to merge 1 commit intomainfrom
claude/review-latest-prs-EF21b

Conversation

@kmay89
Copy link
Copy Markdown
Owner

@kmay89 kmay89 commented Apr 25, 2026

Summary

After Phase 11 (#320) merged, the rf-sensing v2 stack is functionally complete in C++ but unreachable from the SPA — every entry point (wizard::*, household::*, notify::*, etc.) is callable only from rf_presence::update. The Phase 10 PR (#318) deliberately deferred the HTTP routing layer; this PR is the spec that closes that gap.

This is a docs-only PR. Implementation is a follow-up.

The spec defines:

  • Nine /api/rf/* endpoints — one read (status), six setup-flow (zone, pair-start, pair-finish, restart-training, context, mute, forget), one self-test, all reusing the existing Bearer auth + rate-limit pattern from PR feat(canary): inject bearer into SPA + gate read endpoints (P3 phase 2.5) #309.
  • JSON request / response schemas for each endpoint.
  • Privacy invariants: only _for_export (DP-noised) paths are exposed; raw fingerprints / IRKs / MACs / precise timestamps must not leave the device. Lines up with THREAT_MODEL.md Principle 3.
  • Error envelope (bad_request, unauthorized, conflict, rate_limited, not_initialized, internal) using the existing wap_server::send_json_error helper.
  • SPA integration sketch (2 Hz polling of /api/rf/status, "Mute this pattern" button, etc.) under the existing 64 KB web_ui.h budget.
  • Acceptance criteria for the implementation PR.

Phase 13 (federated mesh transport) and Phase 1b (ESP32-C6 port) are explicitly listed as out-of-scope follow-ups so this phase stays bounded.

Housekeeping

Test plan

  • Spec reviewed for accuracy against the actual function signatures in wizard.h, notify.h, household.h, familiar.h, baseline.h, federated.h, dp.h, tests.h — these were read during drafting; flag if anything has drifted.
  • Endpoint inventory matches what the SPA needs (compare against the v0.2 wizard UI mock, if one exists).
  • Privacy invariants (§5) read by a second pair of eyes — explicit checklist of "what must never leave the firmware over HTTP".
  • Sign-off on deferring federated mesh transport to Phase 13 instead of bundling here.

https://claude.ai/code/session_01NHvYH8ioM2w3DnDXNeeJbt


Generated by Claude Code

…ints

Phase 10 deferred HTTP route registration; Phase 11 noted it as the
remaining gap before the rf-sensing v2 stack is user-driveable. This
spec defines the nine /api/rf/* endpoints needed to expose the
wizard / household / familiar / notify modules to the SPA, without
regressing the privacy invariants those modules already enforce
internally.

Specifies auth model (reuses Bearer pattern from PR #309), DP-noised
read paths, error envelope, SPA integration sketch, and acceptance
criteria. Defers federated mesh transport (Phase 13) and ESP32-C6
port (Phase 1b) explicitly so Phase 12 stays bounded.

https://claude.ai/code/session_01NHvYH8ioM2w3DnDXNeeJbt
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the specification for Phase 12 of the RF sensing stack, defining the HTTP route surface required for the SPA to interact with the firmware modules. The spec covers endpoints for status monitoring, setup wizard flows, alert management, and conformance testing, while enforcing privacy through DP-noised counters and the omission of raw identifiers. Review feedback highlights the need to align the JSON response schema with existing C++ data structures, correct a contradiction in UTF-8 validation for zone names, and mitigate a potential race condition in the self-test error handling logic.

Comment on lines +90 to +112
{
"state": "training",
"zone_name": "Back Door",
"context": "home",
"training": {
"progress_bps": 4200,
"complete": false
},
"household": {
"paired_count": 2,
"enrolling": false,
"enrollment_ms_remaining": 0
},
"baseline": {
"populated_buckets": 91
},
"activity_noised": {
"alerts_fired": 4,
"events_evaluated": 1281,
"ambient_suppressed": 612,
"household_suppressed": 38
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The proposed JSON structure for /api/rf/status introduces nesting and field renames (e.g., progress_bps vs training_progress_bps) that do not match the wizard::Status struct defined in wizard.h. Since the underlying C++ modules (Phases 4-11) are already implemented with a flat structure and specific field names, this mismatch will require a manual mapping layer in the implementation. Aligning the JSON schema with the existing C++ model simplifies the code and ensures consistency across the stack.

Suggested change
{
"state": "training",
"zone_name": "Back Door",
"context": "home",
"training": {
"progress_bps": 4200,
"complete": false
},
"household": {
"paired_count": 2,
"enrolling": false,
"enrollment_ms_remaining": 0
},
"baseline": {
"populated_buckets": 91
},
"activity_noised": {
"alerts_fired": 4,
"events_evaluated": 1281,
"ambient_suppressed": 612,
"household_suppressed": 38
}
}
{
"state": "training",
"zone_name": "Back Door",
"context": "home",
"training_progress_bps": 4200,
"training_complete": false,
"household_paired_count": 2,
"household_enrolling": false,
"household_enrollment_ms_remaining": 0,
"baseline_populated_buckets": 91,
"total_alerts_fired": 4,
"total_events_evaluated": 1281,
"total_ambient_suppressed": 612,
"total_household_suppressed": 38
}

Comment on lines +138 to +140
- Server trims trailing whitespace and rejects bytes < 0x20 or >= 0x7F
with `400 bad_request`. UI-only emoji are fine; we accept UTF-8 but
measure length in bytes against MAX_ZONE_NAME_LEN.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a contradiction in the validation logic for the zone name. The spec states it "rejects bytes < 0x20 or >= 0x7F" while also claiming to "accept UTF-8" and support emojis. Since UTF-8 multi-byte sequences (including all emojis) use bytes with the high bit set (>= 0x80), the proposed range check would effectively block all non-ASCII characters. The validation should be updated to allow valid UTF-8 sequences while still filtering out ASCII control characters.

Suggested change
- Server trims trailing whitespace and rejects bytes < 0x20 or >= 0x7F
with `400 bad_request`. UI-only emoji are fine; we accept UTF-8 but
measure length in bytes against MAX_ZONE_NAME_LEN.
- Server trims trailing whitespace and rejects ASCII control characters
(bytes < 0x20 or 0x7F) with 400 bad_request. UI-only emoji are fine;
we accept UTF-8 but measure length in bytes against MAX_ZONE_NAME_LEN.

Comment on lines +233 to +237
This endpoint runs synchronously on the HTTP task; max wall time
budget is 2 s under nominal load. If it exceeds, the handler returns
`500 internal` and the in-progress test continues to completion in the
background (the next call observes a clean state because each test
restores state on exit).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error handling for the self-test endpoint introduces a race condition risk. Returning 500 internal while allowing a state-mutating test to "continue to completion in the background" is problematic because these tests modify and then restore global state (NVS, module globals). If a subsequent request or another self-test is processed before the background execution finishes, it could lead to corrupted state or non-deterministic results. It is safer to ensure the test is fully aborted on timeout or to implement a "busy" state that prevents concurrent execution of state-sensitive operations.

Suggested change
This endpoint runs synchronously on the HTTP task; max wall time
budget is 2 s under nominal load. If it exceeds, the handler returns
`500 internal` and the in-progress test continues to completion in the
background (the next call observes a clean state because each test
restores state on exit).
This endpoint runs synchronously on the HTTP task; max wall time
budget is 2 s under nominal load. If it exceeds, the handler returns
500 internal and ensures the test is aborted or the system is marked
busy to prevent concurrent state-mutating operations. This prevents
race conditions during global state restoration.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4062de54a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +210 to +211
Calls `tests::run_all_conformance()` and returns the structured
report. Schema mirrors `tests::Report`:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match self-test response schema to tests::Report

This spec says /api/rf/self-test “mirrors tests::Report”, but the documented payload (passed/failed/results) does not match the actual tests::Report layout (conformance[], red_team[], per-group counts, total_ms, all_passed in firmware/projects/canary-wap/arduino/canary_wap/tests.h), and tests::run_all_conformance() currently emits only conformance checks (tests.cpp) rather than the red-team entries shown here. If implementation follows this text literally, either the API will diverge from the stated source type or clients will be built against a response shape the firmware does not naturally produce.

Useful? React with 👍 / 👎.

Comment on lines +187 to +190
Calls `wizard::always_ignore_last_decision()`. Returns:

- `200` `{ "muted": true }` on success
- `200` `{ "muted": false, "reason": "no_decision_yet" }` if there has
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Specify how mute endpoint distinguishes failure reasons

The endpoint contract requires two distinct failure reasons (no_decision_yet vs last_decision_fired) while also saying the handler should call wizard::always_ignore_last_decision(). In the current implementation (wizard.cpp), that helper returns only bool and collapses both cases to false, so the documented response reasons cannot be produced without bypassing or duplicating helper logic. This ambiguity will lead to inconsistent handler implementations unless the spec (or helper API) is clarified.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants