diff --git a/devices/CHANGELOG.md b/devices/CHANGELOG.md index 6573fae..c34f1b7 100644 --- a/devices/CHANGELOG.md +++ b/devices/CHANGELOG.md @@ -4,6 +4,17 @@ This file records changes to public protocol behavior, shared vectors, SDK surfa ## Unreleased +### Changed + +- Reframed the Quickstart around the device–Host–Agent architecture and split + the XIAO two-button exercise into a clearly labeled Developer Reference + tutorial. +- Added a machine-readable SDK and public developer-enrollment availability + source for the repository and website. +- Expanded the security model with implemented BLE protection, Host + authorization and freshness checks, minimum disclosure, volatile-state + clearing, and the production identity boundary. + ## 0.2.0-experimental.2 — 2026-07-29 This release keeps wire major 1 and adds seven frozen interaction profiles diff --git a/devices/QUICKSTART.md b/devices/QUICKSTART.md index c610055..b918caa 100644 --- a/devices/QUICKSTART.md +++ b/devices/QUICKSTART.md @@ -1,92 +1,97 @@ -# Build your first Nexting device +# Start with Nexting -This is the shortest public path from an empty checkout to one real Allow or -Deny button press. It uses a Seeed XIAO nRF52840 / Sense and requires neither -Agent credentials nor unpublished product software. - -## 1. Gather and wire - -You need a XIAO nRF52840 or XIAO nRF52840 Sense, two normally-open momentary -buttons, jumper wires, and a data-capable USB-C cable. - -| Button | Connect | -| --- | --- | -| Allow | D0 to GND | -| Deny | D1 to GND | - -The reference firmware enables internal pull-ups. Do not connect either input -to 3.3 V. - -## 2. Get the exact release - -```sh -git clone --branch devices-v0.2.0-experimental.2 --depth 1 \ - https://github.com/Nexting-ai/nexting.git -cd nexting/devices -``` - -## 3. Build - -On macOS or Linux with Python 3.11+: - -```sh -./scripts/bootstrap-zephyr.sh \ - --board xiao-nrf52840-sense \ - --install-sdk \ - --build -``` - -The script creates an isolated Python environment beside `devices/`, initializes -the pinned Zephyr 4.3.0 workspace, installs west 1.5.0, optionally installs -Zephyr SDK 0.17.4, and builds: - -```text -devices/build/xiao-nrf52840-sense/zephyr/zephyr.uf2 -``` - -Rerunning is safe. Use `--dry-run` to inspect every resolved version and path -without changing the workspace. - -## 4. Flash - -1. Connect the XIAO over USB-C. -2. Double-press Reset. A volume named `XIAO BLE` appears. -3. Copy `zephyr.uf2` to that volume. It ejects automatically. -4. The Pending LED remains off until a Host presents an approval. - -## 5. Prove the public BLE path - -From `nexting/devices` on macOS: - -```sh -swift run --package-path sdk/swift nexting-device-host-smoke \ - --summary "Allow the Nexting hardware smoke test?" -``` - -Allow Bluetooth access. The Host prints the discovered device, Device Info, and -connection state. Press Allow or Deny once. Success is explicit: +Nexting turns nearby buttons, rotaries, LEDs, and displays into a physical +remote for an Agent. The device never talks directly to Claude Code, Codex, or +another Agent: ```text -PASS answer=allow +Nexting device ⇄ encrypted BLE ⇄ trusted Host ⇄ Agent integration ``` -or: - -```text -PASS answer=deny -``` - -This test proves discovery, encrypted subscription, bounded framing, the -`approval/1` state machine, and a real button without requiring an Agent. - -## 6. Connect an Agent - -Public availability is a separate gate. As of 2026-07-27, **App Store 2.4** -does not include Experimental 0.2 developer-device enrollment. Do not weaken -BLE security or look for an unpublished build. Use the public Host smoke test -today. The web SDK page will name the first verified iOS and Android versions -when the enrollment UI ships publicly. - -Next: read [troubleshooting](docs/troubleshooting.md), then the -[Device SDK](sdk/c/README.md), [Swift Host SDK](sdk/swift/README.md), or -[Kotlin Host SDK](sdk/kotlin/README.md). +The trusted Host is the Nexting App or another compatible phone/computer Host. +It owns credentials, authorization, policy, and Agent-specific mapping. The +device owns physical input and minimum display state. + +## What each layer does + +| Layer | Responsibility | Does not receive | +| --- | --- | --- | +| Nexting device | Report physical intent and render bounded state | Agent credentials, accounts, session routes | +| Trusted Host | Authorize the device, validate fresh input, and map profiles to Agent actions | Unbounded or unauthenticated device commands | +| Agent integration | Continue the Claude Code, Codex, or compatible Agent session | A direct connection from accessory firmware | + +This separation lets one public device protocol support a button, wearable, +macropad, desk panel, or custom display without placing private Agent logic in +firmware. + +## Choose your path + +| Path | Start | First proof | +| --- | --- | --- | +| Use a supported first-party Nexting product | Follow that product's in-App onboarding, connect the Agent, then pair and authorize the product | The same Agent session receives one validated physical action | +| Build with the Nexting SDK | Download the public SDK and run the public Host or simulator before adapting hardware | A deterministic profile exchange or `PASS answer=...` local protocol proof | + +First-party product onboarding does not authorize third-party or DIY hardware. +If you do not own a supported product, use the SDK path today. + +**Have a supported board?** +[Build the reference approval controller now](docs/reference-approval-controller.md) +and prove a real button without turning the general Quickstart into a board +assembly guide. + +Public third-party developer-device enrollment is a separate release gate. The +machine-readable status in [`docs/availability.json`](docs/availability.json) +currently records iOS and Android enrollment as unavailable. Do not use an +unpublished App build or weaken BLE authorization. The supported fallback is +the public Host smoke test. + +## First remote interaction + +Every supported interaction follows the same shape: + +1. The Agent produces a request or state change. +2. The Host verifies that the selected device is authorized and declared the + required profile. +3. The Host sends only bounded state over encrypted BLE. +4. The user presses, turns, navigates, or holds a physical control. +5. The Host validates identity, freshness, sequence, and policy before applying + the mapped action to the same Agent session. + +With a released first-party product and supported Agent integration, this is a +remote Agent interaction. With the public SDK and Host smoke test, it is a +local protocol proof: useful for validating the open contract, but not a claim +that the public Nexting App already enrolls DIY hardware. + +## Why the Host exists + +- Agent credentials and authoritative sessions stay on the trusted Host. +- Firmware implements stable physical intent rather than Claude Code or Codex + internals. +- Agent mappings and risk policy can change without reflashing the accessory. +- The Host rejects undeclared profiles, stale sequences, expired requests, + duplicate answers, and revoked devices. +- The same device contract works across richer custom hardware. + +## Security in one minute + +The local control link uses BLE LE Secure Connections, bonding, and encrypted +GATT access. The device receives minimum profile data, such as an opaque +request ID, bounded summary, fixed choices, relative lifetime, or volatile +display state. + +The Developer Reference uses BLE “Just Works.” That encrypts the bonded link +but does not authenticate against an active person-in-the-middle during +pairing. Production devices need authenticated application identity and signed +firmware. Read the complete [Security model](SECURITY.md) before making a +product claim. + +## Next steps + +- [Build the reference approval controller](docs/reference-approval-controller.md) +- [Troubleshoot setup, BLE, and Device Info](docs/troubleshooting.md) +- [Map a Codex App Server request in your own Host](docs/codex-app-server.md) +- [Integrate the C99 Device SDK](sdk/c/README.md) +- [Integrate the Swift Host SDK](sdk/swift/README.md) +- [Integrate the Kotlin Host SDK](sdk/kotlin/README.md) +- [Read every public interface](docs/interfaces.md) +- [Read the normative protocol](SPEC.md) diff --git a/devices/README.md b/devices/README.md index edec551..a566c3d 100644 --- a/devices/README.md +++ b/devices/README.md @@ -6,9 +6,12 @@ Nexting Devices lets a button, wearable, desk panel, macropad, or custom product control and display bounded AI Agent interactions through a trusted Host/App. -**New here?** [Build, flash, and prove a two-button XIAO nRF52840 device](QUICKSTART.md). -The guide owns the exact release, wiring, bootstrap command, expected output, -public-App boundary, and [troubleshooting path](docs/troubleshooting.md). +**New here?** +[Understand the connection and run your first interaction](QUICKSTART.md). +Hardware developers can then +[build the reference approval controller](docs/reference-approval-controller.md). +Public App availability is machine-readable in +[`docs/availability.json`](docs/availability.json). **Nearby device, agent anywhere.** The device talks to the Host App over Bluetooth LE; the Host reaches the user's agent wherever it runs — the computer across the room or across the internet. Every device built on this contract is a remote control surface for its owner's agents, not a desk-bound accessory. @@ -26,7 +29,8 @@ The device does not run the Agent session and does not receive Agent credentials | You want to… | Start here | | --- | --- | -| Build your first working device | [Follow the public Quickstart](QUICKSTART.md) | +| Understand and connect Nexting | [Follow the public Quickstart](QUICKSTART.md) | +| Build the XIAO approval reference | [Build the reference approval controller](docs/reference-approval-controller.md) | | Get ideas for what to build | [Browse the use cases](docs/use-cases.md) | | Build and flash a supported board | [Run the first approval](docs/first-approval.md), then use the [reference-board track](docs/implementation-tracks.md#track-1-run-a-reference-board) | | Add a physical control surface to a Host/App | [Browse every public interface](docs/interfaces.md), then use the [Host integration track](docs/implementation-tracks.md#track-2-integrate-a-host-or-app) | @@ -98,7 +102,8 @@ All four share one Zephyr application and the portable C99 core. All four are Bu ## Documentation -- [Build your first device](QUICKSTART.md) +- [Understand the device–Host–Agent connection](QUICKSTART.md) +- [Build the reference approval controller](docs/reference-approval-controller.md) - [Troubleshoot setup, build, BLE, and Device Info](docs/troubleshooting.md) - [Documentation by task](docs/README.md) - [Browse the use cases](docs/use-cases.md) diff --git a/devices/SECURITY.md b/devices/SECURITY.md index a7077f9..0b82e47 100644 --- a/devices/SECURITY.md +++ b/devices/SECURITY.md @@ -11,7 +11,8 @@ The device receives only an opaque request ID, a bounded summary, fixed choices, ## Minimum transport controls - BLE LE Secure Connections and bonding; -- encryption required for approval writes and notification subscription; +- encrypted GATT permissions required for control writes and notification + subscription; - explicit user authorization and revocation in the App; - fail-closed parsing and bounded buffers; - relative expiry, single consumption, and duplicate suppression; @@ -19,6 +20,44 @@ The device receives only an opaque request ID, a bounded summary, fixed choices, - production firmware signing; - no name-only authorization in release builds. +## Protected local link + +The Zephyr Developer Reference enables BLE LE Secure Connections, bonding, +controller privacy, and bonding-required policy. It requests +`BT_SECURITY_L2` after connecting. Control writes require +`BT_GATT_PERM_WRITE_ENCRYPT`; notification subscription requires encrypted read +and write permissions. + +Device Info may remain readable for compatibility discovery. Reading identity +and declared capabilities never authorizes a device or a physical action. + +## Authorization and freshness + +The trusted Host validates explicit authorization, current request identity, +expiry, choice membership, sequence or revision, duplicate and replay state, +and single consumption before it applies physical intent. Phone and hardware +inputs compete for the same one-answer gate. Unsupported, stale, malformed, or +already-consumed input fails closed. + +## Minimum disclosure + +Firmware receives bounded profile data such as an opaque request ID, short +summary, fixed choices, relative lifetime, or volatile display state. Agent +credentials, account, session, terminal, prompt and cloud route identifiers, +and tokens remain on the trusted Host. + +`voice/1` controls the Host microphone lifecycle and never carries audio bytes or transcripts. +Audio permission, capture, encoding, transport, and transcription remain Host +responsibilities. + +## State and revocation + +State and partial-frame clearing on disconnect prevents a device from rendering +or acting on an abandoned exchange. Disconnect, reboot, a new bond, or physical +bond reset clears the applicable volatile requests, rendered state, +sequence/revision memory, and partial frames defined by the protocol. The Host +can revoke a previously authorized device independently of its BLE bond. + ## Developer Reference is not certification The macOS simulator and reference boards are development tools. They must be visibly identified as Developer Reference devices in names, logs, and App UI. diff --git a/devices/SHA256SUMS b/devices/SHA256SUMS index 80a6f2b..c94019e 100644 --- a/devices/SHA256SUMS +++ b/devices/SHA256SUMS @@ -1,6 +1,7 @@ 5e79c3b2a9dc997d6faa762fa3d420da27243c27a60726a5b4d94dabfdfcfb17 AGENTS.md -0b0c0b9e38fec276e019b91023f41c1bcc26807c950dd749637e57bea3c6238b CHANGELOG.md +c04377581c1276abd5167357fdf5cc2b5f43374ad175407b50b1ea4e9bd04b9f CHANGELOG.md bca1e1281cdb9f6a6333b94732347e17ef1dfd71e37739e571931a78e9743da0 CONTRIBUTING.md +4e087afb9492832f8cfe8852b744b6a39609a99e71efe3e5f5547c92c8f66a4c docs/availability.json fee2bc61257031a3a77f16d08317ffc900399da391e23c34c5a8eeee5e1242a4 docs/board-verification.md 1f6e6328514c91f6fd7598f22e97ace2437b954c50a4bf90796d6e280b2272ab docs/codex-app-server.md b21af2b2e8f6909f6849c7b8c884a660cdba52d63e20f57b8a01558c01596a45 docs/conformance.md @@ -13,7 +14,8 @@ efa03687616804ce67309bf4a53c2b07442f92fbaf8e090dc50768d53921cc8f docs/interface 56bdbeb867f3322e41fde1dfb69a3577944bd3cfbb8526f189e9d8c350c9298f docs/migration-0.1-to-0.2.md b9dda6616039d28de4247731274a96b3b861fa5bd931a234ae4b05e3f2ca1978 docs/porting-guide.md 1e04597bbdbf582898af0209c2158a8a7a14e0a795798b6788862edbe10e1fc7 docs/project-status.md -c575a95b90019ed2c89507aeb1fa08b90f029b07391c9b50a180c85856a405eb docs/README.md +ed3527cebbde0b08ad5b24d1ce6b06e64bccb8884dc265553e22db4a3ee39a91 docs/README.md +3a4450b4a09222ab4c85f2deb608f956b36558003db9041c9c32e730596411c5 docs/reference-approval-controller.md 317190076de89ff907b686a29df6303e271ca65bb5383deee7e80122425d1cd9 docs/troubleshooting.md 8bded68d2fb5341d6595bc44ecfee4e16319f0f5a5a9431a09d8b013a63ba0cc docs/use-cases.md b7e876121a2ce1d8b008e80682f265d5538a2fc6bd7dfe6559ee63210d6fbf99 examples/macos-device-simulator/main.swift @@ -42,8 +44,8 @@ f6f614c1eba9506c4cdc0dbd3f6726c1f47e060962d90b9fdb540a88c007a6b6 protocol/vecto 592cf022e7fb1310fbded1a7e5389abffdc42faf1c1c226feb8be41f5849d2c0 protocol/vectors/text-v1.json 052e46a781878cf3f343370c0492391f011577df44c7e3f8fe81c7c21e8499b1 protocol/vectors/usage-v1.json 2a5298dba7fb95fc5883407723ee178065139d81b5c6f46248c65829d8a6afbf protocol/vectors/voice-v1.json -9c1f7d9b1006aadd75bb2a8df34f4382466a8f20de4dfaefb37a933d6c874022 QUICKSTART.md -b07b4715c7e94b3d15d3c7bbd2284a68eda371d01e0fb3d65233bed01443bb7e README.md +6d13eeab77c9b5d79acc120b72cf84079fc6580cf5620ae73fa01df902112ac1 QUICKSTART.md +ebed69b2df3cc842ca37d0317c5f7957e5514041740f886c4c8a4f81d6471ff2 README.md d34ae54889cc7d601bc57011a65f2377bf70ee08cbbfba2aec081bd5e9ec02c7 reference/js/package.json 18374cf8708c75abce5a22d6d445cf5ab48b6b62774ecf3af8f780693d1cf45e reference/js/README.md b25bb77cd8c9301aa664062b1ef00208f2e7d220419173268842338b7fd7254f reference/js/src/device-info.mjs @@ -63,10 +65,10 @@ edd88282a6903f32c585803a60c3c5bb076445d36fc76e511d9965ed57e20422 scripts/bootst 327730ddb2cc1bc0265d6a15df0cd89bb94b154d1eb18d698cc57fa2be4cacfd scripts/check-naming.mjs 0ac9fbc4e0688525458164868e417919bc176fbae0c21181b3ea4b8711cc9a34 scripts/check-public-boundary.mjs 8d7aad1d02044e2762835d765a6bad5fc41c454e15d75111f957db45bc72348c scripts/check-public-boundary.test.mjs -9db4c4a15e1d29f60d801cf5ba3ec9c033a095f6dfa5c69490d03c92b6930e25 scripts/documentation-contract.test.mjs -f2dcd8fe664a927112306827a14c4c42f2a6caae481afbf9a2be0da3eb32198b scripts/export-manifest.json +7460c7107f0c0fe0f1393aa7b7f9661370301b4964e858a23e4d3c7e81f15f67 scripts/documentation-contract.test.mjs +7c287522776d2c13efd1b612911fe9acb0d93c50363db2057c94aeae6725f29c scripts/export-manifest.json c9dbb012caf0fa12663dd833e532fa4c1c2c66e28d272b933f6c1c66971c2771 scripts/export-nexting-devices.mjs -b52cfb41334a0ec850bb834a81e02b90c03ff00009e74567805337a845a81081 scripts/export-nexting-devices.test.mjs +d44b2068c761ca2a57c8cef1828cbd46435342c3a1e9d002b0ca3266ab2d7413 scripts/export-nexting-devices.test.mjs acc23a2dc72940134997118b4cc09371fbe3cc98043ac7228e8b9c9f0d80d75f scripts/public-workflows/nexting-devices-ci.yml 61a898bbca6629ab9e990fb8fca9b22928cf245b6bfab91f85c9c70b96307e0d scripts/public-workflows/nexting-devices-firmware.yml df6af303e569f464ac35e3445fe59d26dbba21ea47583157eb4813464e018246 scripts/simulator-contract.test.mjs @@ -112,6 +114,6 @@ e756d07cd1e47b36d43c29f3bd85e144355e60da62d7ff4a7205cee93e269ec4 sdk/swift/Test baefe1d85ee7317797d03c5b12851ee4ed73eadbda8a0cdbd9215e60ddbe0e44 sdk/swift/Tests/NextingDeviceKitTests/RelayTests.swift 13f09fd666e7871d2298fcf2669bfda2f7adb4b6edb815c637b64c688f30d71c sdk/swift/Tests/NextingDeviceKitTests/StatusTests.swift aae4345e72b23ee5f64ab863406f88bd4b5af3544f1e23e7da0eaaac37fbff3a sdk/swift/Tests/NextingDeviceKitTests/VectorTests.swift -08e9f7804e570679cc89fca2d13d210f122ae5c05135777ccb1ae51f5751c5e8 SECURITY.md +5237d7e35cf872b9348db413bed8a500c7868f368be5fe9e5ccc8e27faffb961 SECURITY.md e9ac568e20666af8e92aed24192b5501391239000222fa4f4992b82d77084c50 SPEC.md 4196491d589dbc14ad631e03d48d6ee1d736e1a119960a0b0240f6e35c173f56 west.yml diff --git a/devices/docs/README.md b/devices/docs/README.md index cdb10ac..5a1c586 100644 --- a/devices/docs/README.md +++ b/devices/docs/README.md @@ -2,9 +2,10 @@ Choose the task you are trying to complete. -Start with the root [Quickstart](../QUICKSTART.md). It takes one supported -board from wiring to a public Host `PASS` result. If a checkpoint fails, use -[Troubleshooting](troubleshooting.md) before reading the architecture guides. +Start with the root [Quickstart](../QUICKSTART.md). It explains the +device–Host–Agent architecture, separates first-party product onboarding from +SDK development, and leads to a remote interaction or honest local protocol +proof. If a checkpoint fails, use [Troubleshooting](troubleshooting.md). ## Understand the product and foundation @@ -19,7 +20,9 @@ board from wiring to a public Host `PASS` result. If a checkpoint fails, use ## Run a working example -- [Public Quickstart](../QUICKSTART.md): wire, build, flash, and prove the golden board. +- [Public Quickstart](../QUICKSTART.md): understand, connect, and choose the supported first result. +- [Reference approval controller](reference-approval-controller.md): wire, build, flash, and prove the XIAO Developer Reference. +- [Public availability](availability.json): machine-readable SDK version and third-party App enrollment gate. - [Troubleshooting](troubleshooting.md): repair setup, toolchain, flash, BLE, and Device Info failures. - [First hardware approval](first-approval.md): build, flash, enroll, and answer one request. - [macOS BLE simulator](../examples/macos-device-simulator/README.md): exercise a real iPhone without a board. diff --git a/devices/docs/availability.json b/devices/docs/availability.json new file mode 100644 index 0000000..930217a --- /dev/null +++ b/devices/docs/availability.json @@ -0,0 +1,17 @@ +{ + "schemaVersion": 1, + "sdkVersion": "0.2.0-experimental.2", + "wireMajor": 1, + "checkedAt": "2026-07-31", + "developerEnrollment": { + "ios": { + "available": false, + "verifiedThrough": "App Store 2.4" + }, + "android": { + "available": false, + "verifiedThrough": "No public version verified" + } + }, + "fallback": "host-smoke" +} diff --git a/devices/docs/reference-approval-controller.md b/devices/docs/reference-approval-controller.md new file mode 100644 index 0000000..552c97f --- /dev/null +++ b/devices/docs/reference-approval-controller.md @@ -0,0 +1,96 @@ +# Build the reference approval controller + +This Developer Reference uses two buttons to demonstrate `approval/1`. It is +not the only or default shape of a Nexting device. A successful smoke test +proves a bounded physical approval path, not production identity, signed +firmware, live Agent mapping, or physical security. + +## 1. Gather and wire + +You need a Seeed XIAO nRF52840 or XIAO nRF52840 Sense, two normally-open +momentary buttons, jumper wires, and a data-capable USB-C cable. + +| Button | Connect | +| --- | --- | +| Allow | D0 to GND | +| Deny | D1 to GND | + +The reference firmware enables internal pull-ups. Do not connect either input +to 3.3 V. + +## 2. Get the exact release + +```sh +git clone --branch devices-v0.2.0-experimental.2 --depth 1 \ + https://github.com/Nexting-ai/nexting.git +cd nexting/devices +``` + +## 3. Build + +On macOS or Linux with Python 3.11+: + +```sh +./scripts/bootstrap-zephyr.sh \ + --board xiao-nrf52840-sense \ + --install-sdk \ + --build +``` + +The script creates an isolated Python environment beside `devices/`, +initializes the pinned Zephyr 4.3.0 workspace, installs west 1.5.0, optionally +installs Zephyr SDK 0.17.4, and builds: + +```text +devices/build/xiao-nrf52840-sense/zephyr/zephyr.uf2 +``` + +Rerunning is safe. Use `--dry-run` to inspect every resolved version and path +without changing the workspace. + +## 4. Flash + +1. Connect the XIAO over USB-C. +2. Double-press Reset. A volume named `XIAO BLE` appears. +3. Copy `zephyr.uf2` to that volume. It ejects automatically. +4. The Pending LED remains off until a Host presents an approval. + +## 5. Prove the public BLE path + +From `nexting/devices` on macOS: + +```sh +swift run --package-path sdk/swift nexting-device-host-smoke \ + --summary "Allow the Nexting hardware smoke test?" +``` + +Allow Bluetooth access. The Host prints the discovered device, Device Info, and +connection state. Press Allow or Deny once. Success is explicit: + +```text +PASS answer=allow +``` + +or: + +```text +PASS answer=deny +``` + +This proves discovery, encrypted subscription, bounded framing, the +`approval/1` state machine, and a real button without requiring Agent +credentials. + +## 6. Understand the evidence boundary + +The result is a local protocol proof. It is not production enrollment or a live +Agent connection, and it does not make this Developer Reference a certified +approval device. Before a production claim, add authenticated application +identity, signed firmware, explicit enrollment and revocation, and the dated +real-board evidence required by +[Conformance](conformance.md) and +[Board verification](board-verification.md). + +Public third-party enrollment is tracked in +[`availability.json`](availability.json). Do not use an unpublished App build +or weaken BLE authorization while that gate is closed. diff --git a/devices/scripts/documentation-contract.test.mjs b/devices/scripts/documentation-contract.test.mjs index 6e490fb..19ff0e6 100644 --- a/devices/scripts/documentation-contract.test.mjs +++ b/devices/scripts/documentation-contract.test.mjs @@ -1,4 +1,5 @@ import assert from "node:assert/strict"; +import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import test from "node:test"; @@ -268,33 +269,112 @@ test("Agent and maintainer guides use the same sources and claims", async () => } }); -test("public Quickstart is self-serve and honest about the public App gate", async () => { - const [overview, index, quickstart, troubleshooting, firstApproval] = - await Promise.all([ - read("README.md"), - read("docs/README.md"), - read("QUICKSTART.md"), - read("docs/troubleshooting.md"), - read("docs/first-approval.md"), - ]); +test("public Quickstart explains the product before optional reference hardware", async () => { + const referenceControllerUrl = new URL( + "docs/reference-approval-controller.md", + root, + ); + const availabilityUrl = new URL("docs/availability.json", root); + assert.ok( + existsSync(referenceControllerUrl), + "reference approval controller guide must exist", + ); + assert.ok(existsSync(availabilityUrl), "availability source must exist"); + + const [ + overview, + index, + quickstart, + referenceController, + availability, + packageJson, + security, + troubleshooting, + firstApproval, + ] = await Promise.all([ + read("README.md"), + read("docs/README.md"), + read("QUICKSTART.md"), + read("docs/reference-approval-controller.md"), + read("docs/availability.json").then(JSON.parse), + read("package.json").then(JSON.parse), + read("SECURITY.md"), + read("docs/troubleshooting.md"), + read("docs/first-approval.md"), + ]); for (const source of [overview, index]) { assert.match(source, /QUICKSTART\.md/); assert.match(source, /troubleshooting\.md/); } for (const marker of [ - "devices-v0.2.0-experimental.2", + "Nexting device", + "encrypted BLE", + "trusted Host", + "Agent integration", + "Use a supported first-party Nexting product", + "Build with the Nexting SDK", + "First remote interaction", + "local protocol proof", + "third-party developer-device enrollment", + ]) { + assert.ok(quickstart.includes(marker), "Quickstart missing marker: " + marker); + } + assert.doesNotMatch( + quickstart, + /Build your first Nexting device|two-button Nexting device|D0 to GND|D1 to GND/, + ); + assert.doesNotMatch(quickstart, /private Nexting App|Debug build/); + + for (const marker of [ + "Build the reference approval controller", + "Developer Reference", + "approval/1", "XIAO nRF52840", "D0", "D1", "bootstrap-zephyr.sh", "nexting-device-host-smoke", "PASS answer=", - "App Store 2.4", ]) { - assert.ok(quickstart.includes(marker), "Quickstart missing marker: " + marker); + assert.ok( + referenceController.includes(marker), + "reference controller missing marker: " + marker, + ); } - assert.doesNotMatch(quickstart, /private Nexting App|Debug build/); + assert.match( + referenceController, + /not the only or default shape of a Nexting device/i, + ); + assert.match(overview, /docs\/reference-approval-controller\.md/); + assert.match(index, /reference-approval-controller\.md/); + + assert.equal(availability.sdkVersion, packageJson.version); + assert.equal(availability.wireMajor, 1); + assert.equal(availability.developerEnrollment.ios.available, false); + assert.equal(availability.developerEnrollment.android.available, false); + assert.equal(availability.fallback, "host-smoke"); + + for (const marker of [ + "BLE LE Secure Connections", + "encrypted GATT", + "opaque request ID", + "single consumption", + "duplicate", + "replay", + "disconnect", + "Just Works", + "authenticated application identity", + "voice/1", + "never carries audio bytes or transcripts", + ]) { + assert.ok(security.includes(marker), "Security missing marker: " + marker); + } + assert.doesNotMatch( + security, + /fully secure|device-to-Agent end-to-end encryption|no data leaves your device/i, + ); + assert.doesNotMatch(firstApproval, /current private Nexting App|Debug build/); assert.match(troubleshooting, /west: unknown command "build"/); assert.match(troubleshooting, /Bluetooth/); diff --git a/devices/scripts/export-manifest.json b/devices/scripts/export-manifest.json index f7d21eb..7e9eabe 100644 --- a/devices/scripts/export-manifest.json +++ b/devices/scripts/export-manifest.json @@ -13,6 +13,7 @@ "SECURITY.md", "SPEC.md", "docs/README.md", + "docs/availability.json", "docs/board-verification.md", "docs/codex-app-server.md", "docs/conformance.md", @@ -25,6 +26,7 @@ "docs/migration-0.1-to-0.2.md", "docs/porting-guide.md", "docs/project-status.md", + "docs/reference-approval-controller.md", "docs/troubleshooting.md", "docs/use-cases.md", "examples/macos-device-simulator/README.md", diff --git a/devices/scripts/export-nexting-devices.test.mjs b/devices/scripts/export-nexting-devices.test.mjs index 46efe21..62cd2cc 100644 --- a/devices/scripts/export-nexting-devices.test.mjs +++ b/devices/scripts/export-nexting-devices.test.mjs @@ -39,6 +39,25 @@ test("exports a deterministic devices subtree without touching unrelated public assert.match(await readFile(join(publicCheckout, "README.md"), "utf8"), /Build hardware for Nexting/); assert.match(await readFile(join(publicCheckout, "Package.swift"), "utf8"), /NextingDeviceKit/); assert.match(await readFile(join(publicCheckout, "devices", "SHA256SUMS"), "utf8"), /SPEC\.md/); + assert.match( + await readFile( + join(publicCheckout, "devices", "docs", "availability.json"), + "utf8", + ), + /"fallback": "host-smoke"/, + ); + assert.match( + await readFile( + join( + publicCheckout, + "devices", + "docs", + "reference-approval-controller.md", + ), + "utf8", + ), + /Developer Reference/, + ); assert.match( await readFile( join(publicCheckout, ".github", "workflows", "nexting-devices-ci.yml"), @@ -70,7 +89,7 @@ test("CLI executes when invoked through a symlinked checkout path", async () => assert.match(stdout, /public export passed/); assert.match( await readFile(join(publicCheckout, "devices", "QUICKSTART.md"), "utf8"), - /Build your first Nexting device/, + /Nexting device ⇄ encrypted BLE ⇄ trusted Host ⇄ Agent integration/, ); } finally { await rm(root, { recursive: true, force: true });