Commit Graph

18 Commits

Author SHA1 Message Date
AletheiaVox
7bb9d8473b fix(security): require auth on every MCP request; stop stale refreshes banning clients
- Remove the authless "sole connected phone" fallback and SB_REQUIRE_MCP_AUTH:
  an unauthenticated request no longer reaches whichever phone is online alone.
- Mcp-Session-Id is no longer a credential; the Bearer token is checked on
  every request (MCP auth spec).
- Refresh tokens live 90 days (was 30, equal to the access token, so they
  were always dead when first needed).
- A rejected refresh no longer counts toward the IP ban: a client with an
  expired token was retrying into a self-renewing ban on its own IP.
- 401s carry the RFC 9728 WWW-Authenticate discovery header.
2026-09-27 12:55:16 +02:00
Aletheia
605f71b742 fix(deps): pin server dependencies, add python-multipart
The 2026-07-27 image rebuild resolved the unpinned '>=' ranges to a new
Starlette, which requires python-multipart for all form parsing — and
that package was missing from the deployed requirements file. Every
OAuth login (POST /oauth/authorize) then failed with a 500.

Pin the full server dependency set to the exact versions verified
running in production so a rebuild can never silently upgrade the
stack again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 12:10:53 +02:00
Aletheia
63d7176ee6 fix(governor): disabled means disabled, and timed commands stop accruing heat
enabled gated only Governor.check(). The heat model, the heartbeat
piggyback and the list_devices footer all ran unconditionally, so a
governor that was switched off could still report heat, still print
COOLDOWN, and still drive the phone's ACTIVE->COOLDOWN transition while
blocking nothing. tick() now returns early when disabled and clears
carried-over state; to_dict() reports enabled:false with zeroed values;
the list_devices footer is omitted.

Separately, current_intensity was only ever cleared by record_stop(),
whose three callers are an explicit stop, a phone emergency stop and the
dead man's switch. A pattern ending on its own duration told the server
nothing, so heat integrated at the last commanded intensity against idle
hardware forever. Commands now pass their duration through to
record_command() and the intensity expires when it lapses. duration:0
still means run-until-stop. For escalate the expiry is duration +
hold_seconds, and only when hold_seconds > 0, per the hold contract.

Adds tests/verify_governor.py: 22 offline checks over the disabled path,
timed-command expiry, and the enabled-path invariants. Existing
verify_server.py still passes 23/23.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 22:41:29 +02:00
Aletheia
b8a52e9658 test: offline verification suite for server and relay clients
Two self-contained scripts, no hardware or network needed:

- tests/verify_server.py boots the FastAPI app in-process against a
  throwaway database and walks the full surface: health, MCP protocol
  (202 notifications, version negotiation, tool schemas), the complete
  OAuth flow (register -> authorize -> code -> token -> authenticated
  MCP session), and the per-user safety config round-trip.
- tests/verify_relays.py drives both relay clients' pattern engines
  against a recorded fake of the Intiface layer: indefinite durations,
  the escalate hold contract, error-mid-ramp stops, pattern
  supersession, tracked auto-stops, feature_index routing, and the
  stop-unknown-device fallback.

Run before shipping changes: python tests/verify_server.py &&
python tests/verify_relays.py

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:28:03 +02:00
Aletheia
9dafd12104 docs: OAuth connector setup, governor config, feature_index; add changelog
- claude.ai connector section now documents the OAuth login flow as the
  primary path, with the authless single-user fallback as Option C.
- Project structure, requirements, and .env docs updated for the OAuth +
  governor modules; pointer to .env.example for the governor knobs.
- Tools table covers the extended output types, the escalate hold
  contract, and feature_index for multi-motor devices.
- Safety Features section documents the governor.
- CHANGELOG.md records v1.1 and the v1.0 baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:20:57 +02:00
Aletheia
7b69479e80 fix: direct commands supersede running patterns; duration auto-stops are tracked
Two races the ported Android engine also has (flagged for backport
there):

- A direct output command (vibrate etc.) now cancels the pattern
  running on the target device. Previously the pattern loop kept
  overwriting the direct command's value every few hundred ms, so the
  command appeared to do nothing.
- Duration auto-stops are now tracked per (device, channel, feature)
  and cancelled when a newer command, pattern, or stop takes over that
  channel. Previously an auto-stop from an earlier command could fire
  minutes later and silently zero output the user believed was running
  — e.g. kill an escalate hold with no ack and no log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:18:08 +02:00
Aletheia
7c61ecb410 fix: pattern lifecycle — indefinite durations, escalate hold contract, monotonic clocks
Ports the Android relay engine's pattern semantics (its 2026-06/2026-07
fix batches) to both Python relay clients:

- duration <= 0 on pulse/wave now runs the pattern until an explicit
  stop, matching how plain commands treat duration=0. Previously the
  loop condition was already false at start, so the pattern silently
  did nothing.
- escalate now honours its documented hold contract: hold_seconds <= 0
  suspends at peak until cancelled, > 0 holds then stops. Both paths
  run through a finally that stops the device, so an error mid-ramp can
  no longer leave hardware running at the last intensity it reached.
  (The naive finally alone would stop the device the moment the ramp
  finished — the same regression the Android app shipped and fixed.)
- Pattern timing uses time.monotonic() instead of time.time(): an NTP
  wall-clock jump could stretch, truncate, or instantly end a pattern.
- relay_client.py now keys pattern tasks by device (one pattern per
  device, as in the Android engine) instead of device:pattern, which
  let a wave and a pulse fight over the same actuator.
- Duplicate same-model devices get suffixed short names (lush, lush_2)
  instead of silently replacing each other.
- Dropped the unsolicited device_list push after auth — the server
  requests a scan on connect and the scan handler answers it; the
  double-send raced session registration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:14:32 +02:00
Aletheia
07e5f9c38c refactor: neutral engineering terminology in tool schemas and device profiles
Ported from the Android edition (its 'reworded device and tool schemas
in neutral terminology' change):

- Tool descriptions and devices.json now describe hardware in neutral
  actuator/engineering terms. Content filters on some LLM platforms
  refused to call tools whose schemas contained explicit anatomical
  language; the reworded schemas work across providers.
- list_devices now surfaces capability descriptions next to each output
  channel (e.g. 'vibrate (two independent eccentric-mass actuators…)')
  so the model learns what each channel does from the profile itself.
- All output/pattern tools now declare required=["device"]; pattern
  schema defaults adjusted to match the Android edition (intensity 0.5,
  duration 60).
- Dual-motor device profiles (Edge, Dolce) document feature_index usage.
- Fixed a stray ')' in nora's rotate capability (typo in the Android
  copy of devices.json).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:11:49 +02:00
Aletheia
136920b717 feat: feature_index — target individual motors on multi-actuator devices
Ported from the Android edition and completed for the Python relays
(the Android repo only implemented the phone side in Kotlin):

- models.py / mcp_tools.py: optional feature_index on every output and
  pattern tool, passed through to the phone relay.
- relay_client.py: routes targeted writes through buttplug-py's
  per-feature API (device.features[i].run_output) and validates the
  index up front so a bad one fails the ack with the valid indices
  listed, instead of dying silently inside a pattern task.
- termux_relay_v3.py: ScalarCmd entries filtered to the requested
  actuator index, same fallback semantics as the Android relay engine.

Lets Claude drive e.g. a Dolce's internal and external motors
independently (feature_index 0 / 1) instead of always both together.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:11:01 +02:00
Aletheia
263e6e9b75 fix: OAuth token endpoint multipart support + governor_enabled bool round-trip
Ported from the Android edition's server:

- Add python-multipart: the OAuth endpoints parse credentials with
  request.form(), and Starlette needs this package when a client POSTs
  the token request as multipart/form-data. Without it those requests
  500'd ('the small auth bug').
- governor_enabled now round-trips SQLite's 0/1 as JSON true/false on
  read and normalises any truthy input to 0/1 on write. Strict clients
  (kotlinx-serialization) reject anything else.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:06:55 +02:00
Aletheia
e987a48689 fix: MCP protocol compliance — ACK notifications with 202, negotiate protocolVersion
Ported from the Android edition's server:

- JSON-RPC notifications (requests without an id, e.g.
  notifications/initialized) now get the bare HTTP 202 the MCP spec
  requires instead of a malformed JSON-RPC error response, which strict
  clients rejected.
- initialize echoes the client's requested protocolVersion when it is a
  version we support (2024-11-05 / 2025-03-26 / 2025-06-18) instead of
  always claiming 2025-03-26.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:06:27 +02:00
Aletheia
6a8bc353c5 feat: OAuth 2.0 support, server-side safety governor, multi-user auth mode
Publishes server work that shipped in the Android edition but never made
it to this repo:

- Full OAuth 2.0 flow (discovery metadata, dynamic client registration,
  authorize + token endpoints) so claude.ai remote connectors and the
  Android app can authenticate per-user instead of relying on the
  sole-phone fallback.
- Safety governor: server-side heat model (intensity x time) with
  automatic cooldown, per-user overrides via GET/POST /safety/config,
  and governor state piggybacked on heartbeat pings so relay clients
  can display it.
- SB_REQUIRE_MCP_AUTH env flag for multi-user deployments (disables the
  unauthenticated sole-phone fallback).
- requirements-phone.txt and .env.example documenting the new knobs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:05:45 +02:00
Aletheia
f8a9245f90 chore: repo hygiene — gitignore, LF normalization, drop compiled bytecode and deploy tarball
The March upload accidentally included server/__pycache__/*.pyc (stale
compiled bytecode) and a prebuilt deploy tarball. The README already
instructs users to build their own bundle, so neither belongs in git.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:04:52 +02:00
Aletheia
dcc73f7d7b Add donation link and project credits to README 2026-04-14 18:55:39 +02:00
Aletheia
9a0d9dd172 Enhance README with project details and ethics
Added project overview, ethics, and security information to README.
2026-04-14 17:34:58 +02:00
Aletheia
b006c6c6b9 Add security warning and legitimate download links
Added a security warning to the README to inform users about potential malware forks and provide legitimate download links.
2026-04-06 20:06:21 +02:00
Aletheia
25678779a4 Add files via upload 2026-03-14 19:42:51 +01:00
Aletheia
142e83d512 Add files via upload 2026-03-14 19:40:48 +01:00