Show both node identifiers on the admin pages, and link the private one - #379
Merged
Merged
Conversation
The publication boundary (D16) leaves the admin pages with only half an identity: they are built on /api/radar/nodes and /api/radar/analytics, both keyed on node_ref and carrying no node_id at all. An admin needs the private id to say which box an operator is looking at, to reach the node's own site, and to join the node_id-keyed admin routes beside it. ref_to_id_map is the inverse of the boundary, so it is served from exactly one place, gated on require_admin. It widens the registry with ids the caller already holds, resolved through owner_identity, so a mirrored node and a synthetic one appear under the handle they publish as; a registry row wins over a mirrored ref, as it does on the way out. Worth knowing where this lands: prod and staging both run AUTH_ALLOW_ANONYMOUS_ADMIN with no OAuth, so require_admin admits every caller there and this mapping is effectively public until ClickUp 86cb1emcx closes that door. The gate is the right one; the environment is what makes it a no-op, and /api/admin/node-contacts already hands out node_ids the same way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The admin pages showed the node_ref alone under a column headed "Node ID", and handed that ref to RetnodeLink, which builds <label>.retnode.com — so every node link pointed at a hostname that resolves to nothing. A node's site is named after its node_id, which the public feeds no longer carry. Each page now joins through the admin ref map: the ref stays the label and the listing key, the node_id becomes the link target and a column of its own, and either one matches in the search box, since an operator arrives holding whichever identifier their last conversation used. The same missing join was quietly breaking two other things on the node cards. Contacts are keyed on node_id, so looking them up by ref left every card reading "nobody reported one"; the location-privacy override is keyed on node_id too, and its route accepts any string, so an admin toggling privacy there wrote a row under the ref that nothing ever consults. Both now address the node by its id, and the control says so rather than offering a write when no id can be resolved. The contact test fixtures were keyed on node_ids, which is what let the first of those pass while it was broken in front of users; they are now shaped like the feed the page actually reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bullet describes the one-way trip and stops there, so a reader looking for the mapping concludes it is nowhere. It is served, admin-only, and the dashboard depends on it. The filename was wrong too: node_refs.py, plural. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @Babissimo's task in 2m 49s —— View job Claude finished reviewing this PR
|
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.

What
The admin pages show the
node_refand thenode_idside by side, and a node'slink points at
<node_id>.retnode.comagain.#349 moved every public feed onto
node_ref, and the admin dashboard readsthose feeds —
/api/radar/nodes,/api/radar/analytics,/api/custody/status— so it was left holding only half an identity. Three things followed from
that, all of them visible to an operator:
RetnodeLinkwas handed the ref andbuilt
<node_ref>.retnode.com; a node's site is named after itsnode_id./api/admin/node-contacts, keyed onnode_id, and were being looked up byref.
/api/admin/nodes/{node_id}/location-privacyaccepts any string by design,so a ref was stored happily and never consulted; publication resolves privacy
by
node_id.Three columns headed "Node ID" were also showing refs.
How
GET /api/admin/node-refsserves{node_ref: node_id}for the fleet — theinverse of the boundary, in one place, gated on
require_admin. It covers theregistry plus ids the caller already holds, resolved through
owner_identity,so mirrored and synthetic nodes appear under the handle they publish as.
useNodeIds()fetches it once per page. The ref stays the label, the listingkey and the node-page address (the per-node analytics route behind it takes a
public identity); the
node_idbecomes the link target, a column of its own,and the key for the contact and privacy joins. Either identifier matches in the
search box. Where no id resolves, the id reads "—", nothing links, and the
privacy control says why rather than offering a write.
Pages touched: Network Health, Node Management, Chain of Custody.
Worth a reviewer's attention
The gate is a no-op where this ships. Prod and staging both set
AUTH_ALLOW_ANONYMOUS_ADMIN=1with no OAuth configured, sorequire_adminadmits every caller and this mapping is public there. Joined against the public
ref-keyed feeds it recovers exactly the linkage #349 withholds.
This is not a new class of exposure —
/api/admin/node-contactsalready handsanonymous callers
node_ids alongside personal contact details — but it widensit from contact-bearing nodes to the whole registry and serves the join
outright. Closing the door is tracked as ClickUp 86cb1emcx (urgent), which now
carries a comment about this route. Shipping knowingly rather than holding the
feature dark in every deployed environment.
A test was green over one of the bugs.
NodeContact.test.tsxkeyed itsnode-listing fixture on
node_ids, which is not the shape the page reads, sothe contact lookup passed in CI while showing "—" to every admin. The fixtures
are now shaped like the feed.
Verification
backend: full suite green (test_mlat_historyexcluded locally for time;CI runs it). New coverage in
test_node_refs.py::TestRefToIdMapandtest_admin_routes.py::TestNodeRefs, including the admin gate.dashboard: 62 tests green,typecheckclean,lintclean (9 pre-existingwarnings, none in the touched files).
pre-commit run --all-files: passed with everything staged.contracts/nodes-v1.openapi.yaml: regenerated, no drift — the route is notunder
/v1/nodes.Not yet verified on a live environment; the new columns want a look on
admin.retina.fmafter deploy.🤖 Generated with Claude Code