Skip to content

feat: document and enforce 5 SDK contracts for GUI consumers - #22

Merged
styu12 merged 1 commit into
mainfrom
feat/sdk-contract-docstrings-phase2
May 5, 2026
Merged

feat: document and enforce 5 SDK contracts for GUI consumers#22
styu12 merged 1 commit into
mainfrom
feat/sdk-contract-docstrings-phase2

Conversation

@styu12

@styu12 styu12 commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Documents and enforces the 5 SDK contracts that the no-code GUI flow (in the sibling syncfield repo) implicitly depends on. Each contract is now stated in MUST-form on the relevant base class and exercised by a unit test. Where the SDK previously violated a contract, the violation is fixed.

The 5 contracts:

  1. on_connect callback is non-blocking — slow user callbacks can't stall the connect path or the stream loop.
  2. expected_hz triggers burst-aware capture_ns interpolation — when one transport read returns N>1 samples, timestamps are spread across the read window instead of clustering on the read instant.
  3. Transient transport hiccups auto-reopen — adapters that own a transport (serial, BLE, USB) attempt up to 5 reopens with exponential backoff (≤30s total) before surfacing a stream error. Implemented as adapters/_generic.py:retry_open() and used by PollingSensorStream / PushSensorStream.
  4. SyncToneConfig.silent() MUST NOT register host_audio — adds suppress_host_audio flag set by the silent() factory. The orchestrator honours it in both _maybe_preregister_host_audio and _maybe_inject_host_audio. Direct SyncToneConfig(enabled=False) retains the legacy behaviour for backward compat. This is a real bug fix — flagged in the EMG report.
  5. Stream errors are isolated — a raise inside a stream's capture thread doesn't propagate to the SessionOrchestrator or transition the session out of RECORDING.

Why this matters

The sibling syncfield repo is shipping a Tier A/B no-code onboarding flow (BLE / serial / network / camera, with Live Explorer + bundled recipes). That flow's reliability promise leaks abstraction without these guarantees:

  • A user who completes the GUI flow but hits a transient USB hiccup currently has no recovery — Contract 3 fixes this.
  • A user who picks "silent mode" gets a ghost host_audio stream surfaced in the GUI — Contract 4 fixes this.
  • One sensor failing kills the whole session — Contract 5 fixes this.
  • 1kHz+ sensors get worse timestamp quality than hand-coded adapters — Contract 2 documents the design intent so it stops drifting.
  • Slow on_connect user callbacks hang the viewer at "Connecting..." — Contract 1 documents the design intent.

Without these contracts being explicit + tested, future SDK changes can silently break GUI assumptions.

Test plan

  • 27 new unit tests in tests/unit/test_sdk_contracts.py — all pass locally
  • Backward compatibility: existing public API preserved, new fields default to legacy behaviour
  • SyncToneConfig(enabled=False) direct constructor retains old behaviour (only the silent() factory triggers the new suppression flag)
  • CI green (verify on PR check)
  • Sibling syncfield repo's existing usage of SyncToneConfig.silent() benefits from Contract 4 — verify with a smoke session in the desktop app

Companion PR

This PR pairs with the sibling repo's sensor onboarding enhancements branch (feat/sensor-onboarding-phase1-sse-schema in OpenGraphLabs/syncfield). The GUI flow there assumes these guarantees. Either can land first — both are non-breaking — but landing this one first means the GUI flow ships on a documented foundation instead of an implicit one.

See syncfield-sensor-onboarding-enhancements.md §5 (in the sibling repo) for the contract rationale.

🤖 Generated with Claude Code

Documents and tests the 5 contracts that the syncfield no-code GUI flow
(in the sibling syncfield repo) implicitly depends on:

1. on_connect callback is non-blocking — slow user callbacks must not
   stall the connect path or stream loop.
2. expected_hz enables burst-aware capture_ns interpolation — when a
   single transport read returns N>1 samples, timestamps are spread
   across the read window, not clustered on the read instant.
3. Transient transport hiccups auto-reopen — adapters that own a
   transport (serial, BLE, USB) attempt up to 5 reopens with
   exponential backoff (≤30s total) before surfacing a stream error.
   Implemented in adapters/_generic.py:retry_open() and used by
   PollingSensorStream / PushSensorStream open paths.
4. SyncToneConfig.silent() MUST NOT register host_audio — adds
   suppress_host_audio flag set by the silent() factory; orchestrator
   honours it in both _maybe_preregister_host_audio and
   _maybe_inject_host_audio. Direct SyncToneConfig(enabled=False) keeps
   the legacy behaviour for backward compatibility.
5. Stream errors are isolated — a raise inside a stream's capture
   thread does not propagate to the SessionOrchestrator or transition
   the session out of RECORDING.

27 new tests in tests/unit/test_sdk_contracts.py, all passing.
No breaking changes — all existing public API preserved, new fields
default to backward-compatible values.

See syncfield-sensor-onboarding-enhancements.md §5 for the contract
rationale and how each guarantee underpins the GUI's no-code promise
for Tier A/B sensors.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@styu12
styu12 merged commit 5626f02 into main May 5, 2026
0 of 4 checks passed
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.

1 participant