Skip to content

20260916 - Ask the owner how we can reach them - #90

Merged
Purple10101 merged 1 commit into
mainfrom
20260916-collect-owner-contact
Sep 16, 2026
Merged

Purple10101 merged 1 commit into
mainfrom
20260916-collect-owner-contact

Conversation

@Purple10101

Copy link
Copy Markdown
Collaborator

Collects the owner's contact details for PUT /v1/nodes/contact, which arrived in node ingest spec v1.2.0 and gained a phone country in v1.2.2. Nothing on a node holds any of it today, so retina-telemetry has nothing to send until this lands. Same ordering the consent records went through.

The retina-telemetry half is offworldlabs/retina-telemetry#18. They are independent: that side treats an absent contact document as a complete answer, so neither breaks the other whichever way round they merge. This one first is the sensible order, because the feature only does anything once this is deployed.

Two surfaces, one route

A wizard step straight after the agreements step, so it is asked while the owner is still answering questions about themselves rather than about the radar. Plus a block under Remote support on the Configuration page for adding or amending it later. Both post to /set-up/contact, because handing them separate handlers is how two surfaces start storing different shapes of the same thing.

Under Remote support rather than a section of its own: those settings are how support reaches the node, and this is how they reach the owner.

Optional throughout, and that is the steady state

The spec is explicit that a node with nothing to report never calls the endpoint at all. So Skip is a real button rather than an escape hatch, and an absent file is how the node says it has nothing.

Clearing works, by doing less rather than more

A blank box is simply not written, and a document with nothing left in it removes the file. Skipping the step and emptying every box converge on the same state. The Save button says Details removed rather than Saved in that case: claiming to hold details we have just deleted would be the one confusing outcome here.

The endpoint replaces its document wholesale, so this end replaces rather than merges and the two cannot disagree about what is held.

Storage

/data/retina-gui/telemetry-contact.json, a cross-repo contract like the consent file, and deliberately not inside it. Those are versioned acceptances neither end may invent, and retina-telemetry refuses to register without all three; a mutable optional document sharing that file would let a malformed contact stop a node registering.

The shape mirrors the wire's NodeContact one-for-one, so nothing is translated between what the owner typed and what the server is told.

Notes for review

  • The caps are the spec's, commented as such beside TX_NAME_MAX_LENGTH's for the same reason: a value past one of them means retina-telemetry cannot build the payload, so the whole document is refused and the owner is never reachable.
  • country is checked in the route, not the model. It is ISO 3166-1 alpha-2 and belongs to the phone number rather than to the owner, which is what the server asked for. The check sits in the route because regex= is pydantic v1 and pattern= is v2; nodes run v1 and CI runs v2, so a constraint spelled either way breaks one of them.

Verification

920 tests pass (24 new), ruff and vulture clean.

Verified end to end on owl-ded9 by deploying this branch to the node, posting through the real endpoint with a real CSRF token, and confirming the file landed at the real path with gb normalised to GB. retina-telemetry then read that file and sent all five fields. The node was restored to v0.8.0 afterwards.

🤖 Generated with Claude Code

Spec v1.2.0 added PUT /v1/nodes/contact and v1.2.2 gave it a phone country.
Nothing on a node holds any of it, so retina-telemetry has nothing to send
until this end collects it, the same ordering the consent records went through.

Two surfaces, one route. A wizard step straight after the agreements step, so
it is asked while the owner is still answering questions about themselves
rather than about the radar, and a block under Remote support on the
configuration page for adding or amending it later. Both post to
/set-up/contact, because handing them separate handlers is how two surfaces
start storing different shapes of the same thing.

Under Remote support rather than a section of its own: those settings are how
support reaches the node, and this is how they reach the owner. Same subject,
so the same heading.

Optional throughout, and that is the steady state rather than an unfinished
one. The spec says a node with nothing to report never calls the endpoint at
all, so Skip is a real button rather than an escape hatch, and an absent file
is how the node says it has nothing.

Clearing works, and it works by doing less rather than more. A blank box is
simply not written, and a document with nothing left in it removes the file, so
skipping the step and emptying every box converge on the same state. The Save
button says "Details removed" rather than "Saved" in that case: claiming to
have stored details we have just deleted would be the one confusing outcome
here. The endpoint replaces its document wholesale, so this end replaces rather
than merges and the two cannot disagree about what is held.

Stored at /data/retina-gui/telemetry-contact.json, a cross-repo contract like
the consent file, and deliberately not inside it. Those are versioned
acceptances neither end may invent and retina-telemetry refuses to register
without all three; a mutable optional document sharing that file would let a
malformed contact stop a node registering. The shape mirrors the wire's
NodeContact one-for-one so nothing is translated between what the owner typed
and what the server is told.

The caps are the spec's, commented as such beside TX_NAME_MAX_LENGTH's for the
same reason: a value past one of them means retina-telemetry cannot build the
payload, so the whole document is refused and the owner is never reachable.
Better to refuse it at the box.

`country` is checked in the route rather than the model. It is ISO 3166-1
alpha-2 and belongs to the phone number rather than to the owner, which is what
the server asked for; asking "where do you live" would put a wrong answer
against a real person. The check is in the route because `regex=` is pydantic
v1 and `pattern=` is v2, nodes run v1 and CI runs v2, so a constraint spelled
either way breaks one of them. It joins the rules the routes already own, which
is also where a complaint can attach to the box it concerns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Purple10101
Purple10101 merged commit a5c82ad into main Sep 16, 2026
3 checks passed
@Purple10101
Purple10101 deleted the 20260916-collect-owner-contact branch September 16, 2026 17:42
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