- 4
- Endpoints
- 4
- Modules on it
- 0
- App changes to add one
What it does
Four HTTP endpoints and one event envelope, defined in JSON Schema so neither the Python services nor the Swift app owns the definition of the thing between them. Every module implements exactly these and nothing more, which is why the app can display a module it has never heard of.
- Describes itself
- A module answers `GET /atlas/manifest` with what it is, what it can do, and which endpoints it serves. The app renders whatever is in there, so a new capability needs no client release.
- Reports its own health
- `GET /atlas/health` separates *reachable* from *working*. A module that answers but cannot see its camera says so, rather than appearing green because the socket opened.
- Streams what happened
- `WS /atlas/events` pushes finished, human-readable events. The same envelope also serves the history query and the push payload — one shape over three transports.
- Registers a phone
- `POST /atlas/devices` hands a module a push token directly, so notifications never route through a central server that would then have to be run and secured.
How it works
The idea
One phone app, several machines in different rooms doing unrelated jobs. The usual answer is a hub the modules register with. This is the other answer: a contract thin enough that each module implements it alone and the app learns nothing module-specific.
Four endpoints. A manifest saying what you are, a health check, an event feed, and a way to register for push.
The rule that makes it work
Modules pre-render their own sentences. The app never interprets an event type to write
text — it displays title and body verbatim. That single decision is why one feed can show
a face at a door, a parcel, and a drive that is failing, without the app knowing what any of
those are. It is also why adding a fourth module took zero app changes.
The cost is that every module carries a renderer and must keep it readable on a lock screen. A test in each service is exhaustive over its event enum to make sure that stays true.
Language-neutral on purpose
The schemas are JSON Schema rather than Python types or Swift Codable models. Two languages
consume this contract, and whichever one owned the definition would get to change it
unilaterally and find out the consequences later, on someone else’s device.
JSON Schema is nobody’s home language, which is the point. The Python services validate their real responses against it; the Swift side decodes and re-encodes the same envelopes, because a decoder that silently drops an unknown field reads as working right up until the app has to send one back.
One envelope, three transports
The event schema is the one worth the attention. The same shape travels over the live socket, the history query, and the push payload — so a change cannot be made “just for notifications”, which is exactly the shortcut that would otherwise be taken at 1am.
Testing
Conformance suites in all four services
Every module validates its own responses against the shared JSON Schemas in its own test suite, so contract drift fails that module's build rather than surfacing in the app.
What the tests defend
- A test asserts every path the manifest advertises actually resolves. One module once shipped a manifest pointing at /events when it only had /atlas/events — which reads to a user as "that address answered but it is not an Atlas module", sending them to check a hostname that was fine.
- The event tests are exhaustive over the event enum rather than sampled, so a new event type cannot be added without a pre-rendered title and a severity.
Built with
- Contract
- JSON SchemaWebSocketBearer auth
- Consumers
- Swift / SwiftUIPython × 4