Skip to content

node refs: the CLI prints node ids it then refuses to accept #336

Description

@shadowbrush

Every --json surface emits opaque node ids, and no node-ref argument accepts one back.

$ hadron node get cor:urn:010:01 -m hadronmemory.com::specs --json
  "id": "019e61808abb79a38c66c4cd5a46fb14"

$ hadron node get 019e61808abb79a38c66c4cd5a46fb14
hadron: "019e61808abb79a38c66c4cd5a46fb14" is not a fully-qualified node URN — expected
<org>::<memory>::<loc> (e.g. hadronmemory.com::dev::start-here), or pass -m <org::memory>
with a bare loc

The server has no such problem — the same id resolves fine:

$ hadron api 'query($r:[ID!]){ nodeBatch(refs:$r){ nodes{ loc } unavailable } }' \
    -F r='["019e61808abb79a38c66c4cd5a46fb14"]'
  nodes: [{ "loc": "cor:urn:010:01" }]   unavailable: []

So the only way to read a node by an id the CLI just gave you is to drop out of the CLI into hadron api with hand-written GraphQL.

Why this is a correctness issue, not just ergonomics

It produced a wrong conclusion that reached a merged doc.

While building coding review lint (#328) I found an edge on the review parent pointing at tasks:build-review-coverage. I could only probe it by loc — which failed — so I recorded it as a live instance of CLAUDE.md's list-vs-read visibility gap, and it went into docs/plans/coding-command-group.md as the motivating evidence for the check-node-resolves rule.

It was never unreadable. Reading it by id returns it immediately (tagged meta, outside the review: prefix, correctly excluded). I had to retract that in #332 — see Deviations §7.

The same root cause was a genuine bug in the shipped linter, caught in review: coding preflight lint rebuilt a URN per route target from the router's memory, so a route crossing into another memory was reported dead — or, worse, resolved a same-loc node from the home memory. Fixed in #332 by reading endpoints by id. The CLI's own linter fell into the gap node get creates, which is the clearest argument that the gap is worth closing.

Scope

The guard is cmdutil.ResolveNodeURN (internal/cmdutil/noderef.go), reached from 12 command files:

edge/add.go        edge/ls.go        memory/extract.go  node/clone.go
node/export.go     node/get.go       node/merge.go      node/move.go
node/revision.go   node/update.go    spec/spec.go       task/run.go

so every node-ref argument in the CLI shares the behaviour.

Suggested fix

ResolveNodeURN rejects anything unprefixed with fewer than two ::. That guard exists for a good reason — a single-colon ref whose loc contains colons (acme.com:kb:services:secureid:x) is genuinely ambiguous and must not be guessed at. An opaque id is not ambiguous: it has no colons at all.

Adding a colon-free branch that passes the ref through as an id looks sufficient, and mirrors what normalizeResourceRef in internal/cmd/access/access.go already does for AiServiceConfig ids ("a bare, colon-free id is read as an AiServiceConfig id"). memory get also already accepts a bare memory id, so accepting a bare node id is the consistent behaviour rather than a new concept.

Worth deciding whether it resolves via resolveUrn (which takes a URN) or short-circuits to the id directly, since ResolveNodeURN returns an id anyway.

Acceptance

  • hadron node get <id> works for an id the CLI printed
  • hadron edge ls <id> likewise, and the other ten
  • a colon-bearing ambiguous ref is still rejected with the existing message (the current guard's whole point)
  • --json round-trips: any id in output can be fed back to a node-ref argument

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions