20260922 - Let an owner claim their node from the configuration page - #94
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
unclaimedbut leaves the address on file, and re-offering an address thenode 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/resendis the only way out, and the section says so inas 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.txtleaves 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