Skip to content
Dimitri Montgomery

Atlas 01 of 07

The Atlas contract

Four endpoints, and the argument that they are enough.

Shipped — Finished, or finished enough that the write-up is about what it does. Personal · 2026

4
Endpoints
4
Modules on it
0
App changes to add one
01

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.
02

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.

03

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

  1. 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.
  2. 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.
04

Built with

Contract
JSON SchemaWebSocketBearer auth
Consumers
Swift / SwiftUIPython × 4