Skip to content

20260922 - Let an owner claim their node from the configuration page - #94

Merged
Purple10101 merged 1 commit into
mainfrom
20260922-node-claim-section
Sep 22, 2026
Merged

Purple10101 merged 1 commit into
mainfrom
20260922-node-claim-section

Conversation

@Purple10101

Copy link
Copy Markdown
Collaborator

Half of one feature, across two repos. The other half is
offworldlabs/retina-telemetry#21, which reads what this writes and talks to the
server. This is safe on its own: with today's telemetry the section simply
reads "Not reported yet", and nothing else on the page changes.

What this is

Node ingest v1.3.0 added the claim: a node offers the address that owns it, the
server mails that address a link, and opening it binds the node to the account
behind it. Nothing on a node knew such an address, so nothing collected one.
This is that collection, as a section of its own between How we reach you and
Cloud services.

A section rather than a row inside the contact block, and the distance is the
point. The two boxes look identical and are not. The contact email answers
"whom do we ring about this node", is optional throughout and grants nobody
anything. This one answers "who owns it", and a wrong value mails a stranger a
link that hands them somebody's node. Separate files, and nothing anywhere
copies one into the other.

Two buttons, because one would not work

Save records the address, and a changed address is what makes retina-telemetry
offer it. That offer is the call that mails, so saving is never a second way to
ask for a link.

Send again exists because of something found by running the claim against
production rather than by reading the spec. Declining a link returns the node
to unclaimed but leaves the address on file
, and re-offering an address the
node already holds is accepted, changes nothing and mails nothing. So a node
can sit unclaimed with an address against it and no amount of saving will move
it. POST /nodes/claim/resend is the only way out, and the section says so in
as many words when it sees that state.

What this side deliberately does not know

Which of the two calls to make. The file carries the address and, when the
owner presses send again, a timestamp. retina-telemetry decides the verb,
because it is the only thing here that talks to the server and knows where the
claim actually stands. A rule implemented on both sides is one that eventually
disagrees with itself.

The timestamp is also how the ask reaches a service that binds no ports and
cannot be called: nothing pushes to retina-telemetry, so an event has to be
left somewhere for it to find.

What it shows, and what it refuses to guess

State comes from retina-telemetry's status document. When that is absent or
stale the section says it cannot tell, rather than defaulting to "not claimed":
telling an owner nobody owns their node is a claim in itself, and this page is
in no position to make it. An unrecognised state is shown as itself, so a value
from a later server reaches the owner rather than disappearing. A bounced
address gets its own line, because sending again cannot help until the address
changes.

Evidence

942 tests pass, ruff clean, and the dead-code hook passes.

CI cannot cover the thing most likely to break this, though: requirements.txt
leaves pydantic unpinned so CI installs v2, while every node runs v1.
So it was also run on a real node under pydantic 1.10.4, where all three
validation branches were exercised: the over-length address the model catches,
the malformed one the route catches, and a resend with no address. All three
return clean field errors.

On that node the whole chain ran against a mock server, so nothing was mailed:
saving wrote the file, telemetry offered it, and the page came back with "Link
sent. Waiting for ... to open it." The declined state was driven too, and
confirmed that Save then sends nothing while Send again produces exactly one
resend.

🤖 Generated with Claude Code

Node ingest v1.3.0 added the claim: a node offers the address that owns it, the
server mails that address a link, and opening it binds the node to the account
behind it. Nothing on a node knew such an address, so nothing collected one.
This is that collection, as a section of its own between How we reach you and
Cloud services.

A section rather than a row inside the contact block, and the distance is the
point. The two boxes look identical and are not: the contact email answers
"whom do we ring about this node", is optional throughout and grants nobody
anything, while this one answers "who owns it" and a wrong value mails a
stranger a link that hands them somebody's node. They are stored in separate
files and nothing anywhere copies one into the other. An owner may well give
the same address twice; that is their answer to two questions rather than our
licence to infer the second from the first.

## Two buttons, because one would not work

Save records the address. A *changed* address is a local change, so
retina-telemetry offers it, and that offer is the call that mails. Saving is
never a second way to ask for a link.

Send again exists because of what a declined link leaves behind, which was
found by running the claim end to end against production rather than by reading
the spec. Declining returns the node to `unclaimed` but the address stays on
file, and re-offering an address the node already holds is accepted, changes
nothing and mails nothing. So a node can sit unclaimed with an address against
it and no amount of saving will ever move it. `POST /nodes/claim/resend` is the
only way out, and the section says so in as many words when it sees that state.

## What this side deliberately does not know

Which of the two calls to make. The file carries the address and, when the
owner presses send again, a timestamp; retina-telemetry decides the verb,
because it is the only thing here that talks to the server and knows where the
claim actually stands. Putting that rule in both places is how they drift.

The timestamp is also how the ask reaches a service that binds no ports and
cannot be called. Nothing pushes to retina-telemetry: every input it has is a
poll or a file read, so an event has to be left somewhere for it to find.

## The status it shows

Read from retina-telemetry's own status document, which carries the claim as
of its last heartbeat. Absent on a node whose telemetry predates 1.4.0 or is
not running, and the section says it cannot tell rather than defaulting to "not
claimed" — telling an owner nobody owns their node is a claim in itself, and
this page is in no position to make it. An unrecognised state is shown as
itself for the same reason retina-telemetry passes the string through: a value
from a later server should reach the owner rather than disappear.

A bounced address gets its own line, because sending again cannot help until
the address changes, and an owner pressing the button repeatedly deserves to
know why nothing arrives.

Not wired end to end yet: retina-telemetry does not read this file, and the
fleet runs a version from before the claim existed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Purple10101
Purple10101 merged commit 7b5ffb7 into main Sep 22, 2026
3 checks passed
@Purple10101
Purple10101 deleted the 20260922-node-claim-section branch September 22, 2026 12:59
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