Skip to content

docs(admin): document asynchronous remote-admin execution - #4490

Merged
Yeraze merged 2 commits into
mainfrom
docs/async-remote-admin
Aug 1, 2026
Merged

docs(admin): document asynchronous remote-admin execution#4490
Yeraze merged 2 commits into
mainfrom
docs/async-remote-admin

Conversation

@Yeraze

@Yeraze Yeraze commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Documents the async remote-admin behavior that shipped in #4485 / #4486 (issue #4482) with no user-facing docs.

Credit and context

The prose here originates from @wilhel1812's PR #4483. They reported the underlying bug (with browser network logs and host-level MAX_RETRANSMIT / proxy correlation), it became issue #4482, and they then opened a complete fix — including the documentation — at 13:43Z on 2026-08-01.

I opened #4485 at 14:28Z, 45 minutes later, without checking for open PRs against the issue, and it merged first. That left #4483 conflicting through no fault of its author. The two implementations converged on the same design; the one thing theirs had that mine did not was this documentation.

This ports it, adapted to the endpoint and error-code names that actually shipped. #4483 can be closed with a link here once this lands.

What it documents

In the Remote Node Support section:

  • Asynchronous execution and completion polling, as numbered behaviors alongside the existing ones
  • The contract: remote commands return 202 Accepted + an opaque operation ID, polled at GET /api/admin/operations/:id, both admin-authenticated, an operation visible only to the admin who started it
  • That ensure-session-passkey and import-config follow the same pattern, except a cached passkey still answers 200 immediately since no mesh round-trip is needed
  • A privacy property the implementation already holds but never stated: operation records keep only the command name, source/destination identifiers, lifecycle state, and timestamps — never command parameters or session keys. I verified this against AdminOperation rather than copying the claim across.
  • Lifecycle states, ten-minute terminal retention, and that operation state is in-memory only — a restart yields OPERATION_NOT_FOUND, which the client reports as interrupted, not success
  • Failure codes: PASSKEY_TIMEOUT, TX_DISABLED, IMPORT_CONFIG_FAILED, OPERATION_NOT_FOUND

New Confirmation Semantics subsection for favorite/unfavorite/ignore/unignore — confirmed vs. rejected vs. timeout-is-uncertain, and why the optimistic update survives a timeout but not a rejection. That behavior was implemented in #4485 (including the new ignore/unignore parity) but documented nowhere a user would look.

Also notes that a partial config import reports the channels that failed alongside those that landed, so "1 channel" is distinguishable from "1 of 2 channels" (#4486).

Adaptations from #4483

Their doc described their API; these are the shipped names:

#4483 shipped
GET /api/admin/commands/:operationId GET /api/admin/operations/:id
pending/running + separate phase axis single axis: pendingawaiting_passkeysendingawaiting_ack
succeeded/failed/timed_out/rejected succeeded/failed (ACK outcome carried in the result)
REMOTE_PASSKEY_TIMEOUT, TRANSPORT_FAILURE, ROUTING_REJECTED, ADMIN_OPERATION_NOT_FOUND PASSKEY_TIMEOUT, TX_DISABLED, IMPORT_CONFIG_FAILED, OPERATION_NOT_FOUND
five-minute retention ten-minute retention

Worth recording: @wilhel1812's four-state terminal model is more expressive than what shipped. Surfacing timed_out and rejected as operation states beats burying both in result.ack, since a caller reading only status learns more. The shipped version's advantage was needing no change to the frontend's existing ACK handling. Reworking it now would churn recently-reviewed code, so it's better as its own issue than as part of a docs PR.

Testing

Docs-only — no code changes. Every factual claim was checked against the merged implementation (adminOperationService.ts, adminRoutes.ts, api.ts) rather than carried over on trust.

🤖 Generated with Claude Code

https://claude.ai/code/session_015cgD5kSurLjdeVkZ6PcQD9

Remote admin commands, session-passkey acquisition, and remote config
import all became asynchronous in #4485/#4486 (issue #4482), but that
shipped with no user-facing documentation. This fills the gap in the
Remote Node Support section:

- asynchronous execution + completion polling as numbered behaviors
- the 202 + operation-id contract, the poll endpoint, and the fact that a
  cached passkey still answers 200 immediately
- the privacy property the implementation already holds but never stated:
  operation records keep the command name, source/destination, lifecycle
  state, and timestamps — never command parameters or session keys
- lifecycle states, ten-minute terminal retention, and that a restart
  surfaces as an interrupted operation rather than a success
- Confirmation Semantics for favorite/ignore: confirmed vs rejected vs
  timeout-is-uncertain, and why the optimistic update survives a timeout
- partial config imports reporting what failed alongside what landed

The prose originates from @wilhel1812's PR #4483, which solved the same
issue independently and documented it properly. That PR was preempted by
#4485 landing first; this ports its documentation, adapted to the endpoint
and error-code names that actually shipped.

Co-Authored-By: wilhel1812 <wilhel1812@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015cgD5kSurLjdeVkZ6PcQD9
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: docs(admin): document asynchronous remote-admin execution

Tasks:

  • Read the changed documentation file
  • Verify claims against implementation files
  • Provide review feedback

This is a docs-only PR documenting async remote-admin behavior. I verified all factual claims against adminOperationService.ts and adminRoutes.ts. Here's the review:

Accuracy: Verified ✅

Every claim I checked holds:

  • 202 Accepted + opaque operation ID — confirmed (adminRoutes.ts:1930, :1459, :699)
  • Poll endpoint: GET /api/admin/operations/:id — confirmed (adminRoutes.ts:1951)
  • Admin-authenticated, visible only to the admin who started it — confirmed: the route uses requireAdmin() and the handler checks operation.userId !== requesterId (adminRoutes.ts:1951–1959)
  • Lifecycle states pending → awaiting_passkey → sending → awaiting_ack → succeeded/failed — exactly matches AdminOperationStatus type (adminOperationService.ts:29–35)
  • Ten-minute terminal retention — confirmed: TERMINAL_RETENTION_MS = 10 * 60 * 1000 (adminOperationService.ts:89)
  • In-memory only; restart yields OPERATION_NOT_FOUND — confirmed (the service is a plain in-memory Map, no persistence layer)
  • Failure codes PASSKEY_TIMEOUT, TX_DISABLED, IMPORT_CONFIG_FAILED, OPERATION_NOT_FOUND — all present in the implementation
  • ensure-session-passkey returns 200 immediately when passkey is cached — confirmed (adminRoutes.ts:649–657)
  • Privacy claim: operation records hold only command name, source/destination IDs, lifecycle state, timestamps — confirmed against AdminOperation interface (adminOperationService.ts:68–81): fields are id, command, sourceId, destinationNodeNum, userId, status, timestamps, result, error — no parameter storage
  • ACK-awaited commands: favorite, unfavorite, ignore, unignore — confirmed via ACK_AWAITED_COMMANDS set (adminRoutes.ts:1483–1488)
  • Confirmed/Rejected/Timeout semantics for ACK commands — confirmed via executeAdminCommand return (adminRoutes.ts:1630–1642)
  • Partial config import reports both landed and failed channels — confirmed: failedChannels / failedChannelDetails in the result (adminRoutes.ts:1410–1422)

Minor Issues

1. Small inaccuracy in the privacy description (line 378–380)

The docs say: "Operation records hold only the command name, the source and destination identifiers, lifecycle state, and timestamps."

The AdminOperation interface also stores userId (the session user who created it). While userId is an internal implementation detail used for access control, it is technically stored in the operation record. This is minor since userId isn't meaningful to users, but the claim as written is slightly incomplete.

2. ensure-session-passkey async path — docs say 200 for cached, but implementation returns 202 for uncached (line 373–376)

The docs correctly note the cached passkey returns 200 immediately. What they don't mention is that an uncached passkey acquisition also goes async (returns 202) just like import-config. The sentence reads fine as-is for the user-visible contract, but someone implementing against the API could miss this. Consider rewording to clarify both cases:

ensure-session-passkey follows the same async pattern — except that a cached passkey still answers 200 OK immediately since no mesh round-trip is needed; only an uncached acquisition returns 202.

3. Lifecycle state for import-config (line 374)

The import route sets status to awaiting_passkey immediately when the background task starts (adminRoutes.ts:1439), but the route doesn't go through sending → awaiting_ack states — it proceeds directly from passkey acquisition to the import loop and then to succeeded/failed. The docs describe the full state machine as applying to all remote commands, which is accurate for POST /api/admin/commands but the import-config path skips sending and awaiting_ack. This is a minor nit since the docs don't claim every command goes through every state.

Suggestion (Non-blocking)

The "Other remote commands are reported as successful once transmitted" note (line 407–408) is accurate but could be slightly misleading — it implies no ACK is awaited at all, but the implementation does handle TX_DISABLED failures. The distinction is specifically "no routing ACK is awaited" vs. "reported as failed if transmit is disabled." Not a blocking issue.

Summary

The documentation is accurate, well-written, and adds real user-facing value that was genuinely missing. The above items are all minor — none block merging. The privacy property verification is a good addition; it's the kind of claim that's easy to assert but nice to have grounded against the interface definition.

Verdict: Approved ✅ — clean docs-only PR with verified factual accuracy. The adaptations from #4483 are faithfully mapped to the shipped API names.

Review follow-ups, all corrections to claims I made:

- The privacy sentence omitted userId, which AdminOperation does store for
  the ownership check. On a claim about what is retained, an incomplete
  list is the one kind of error that actually matters — now names the
  requesting administrator explicitly.
- Spell out both halves of the ensure-session-passkey contract: an uncached
  acquisition returns 202, a cached one answers 200. The earlier wording
  described only the 200 case and left the async path implicit.
- Note that not every command visits every lifecycle state — config import
  goes from awaiting_passkey straight to terminal, since it sends a
  sequence of packets rather than awaiting one routing ACK.
- Clarify that "successful once transmitted" means no routing ACK is
  awaited, not that the command cannot fail: an unsendable command (e.g.
  TX_DISABLED) still fails.

Co-Authored-By: wilhel1812 <wilhel1812@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015cgD5kSurLjdeVkZ6PcQD9
@Yeraze

Yeraze commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

All four addressed — thanks, and the first one mattered more than "minor".

1. userId omitted from the privacy claim — fixed. On a sentence describing what is retained, an incomplete list is precisely the kind of error worth catching; it now names the requesting administrator explicitly alongside the rest.

2. ensure-session-passkey 200 vs 202 — fixed. The earlier wording described only the cached-200 case and left the async path implicit, which is exactly the trap for someone implementing against the API. Both halves are now spelled out.

3. import-config skips sending/awaiting_ack — fixed. The docs now say not every command visits every state, and why: an import sends a sequence of packets rather than awaiting a single routing ACK, so it goes from awaiting_passkey straight to terminal.

4. "successful once transmitted" — fixed. Now says no routing ACK is awaited, and that this is not the same as never failing: an unsendable command (TX_DISABLED) still fails.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: docs(admin): document asynchronous remote-admin execution

Tasks:

  • Read the changed documentation file
  • Verify updated content against previous review feedback
  • Check accuracy of claims against implementation files
  • Provide review feedback

This is the re-review after Yeraze addressed all four points from the first pass. I re-read the changed section and re-verified every updated claim against the implementation.

All Four Fixes Verified ✅

1. userId in privacy claim (line 379–381)

"Operation records hold only the command name, the source and destination identifiers, the requesting administrator, lifecycle state, and timestamps."

Correctly now includes "the requesting administrator" — userId is present in AdminOperation (adminOperationService.ts:74). Fixed.

2. ensure-session-passkey 200 vs 202 (lines 373–377)

"...an uncached passkey acquisition returns 202 and completes in the background, while a request for a passkey that is already cached answers 200 immediately..."

Both cases are now explicitly described. Confirmed against adminRoutes.ts:649–657 (cached returns 200) and adminRoutes.ts:699 (uncached returns 202). Fixed.

3. import-config lifecycle states (lines 384–388)

"Not every command visits every state — a configuration import, for example, goes from awaiting_passkey straight to its terminal state, since it sends a sequence of packets rather than awaiting a single routing ACK."

This accurately captures the divergent path. The import route at adminRoutes.ts:1439 sets awaiting_passkey and then goes directly to terminal states without ever setting sending or awaiting_ack. Fixed.

4. "Successful once transmitted" clarification (lines 411–414)

"Other remote commands await no routing ACK and are reported as successful once transmitted, matching their previous behavior. That is not the same as never failing: a command still fails if it cannot be sent at all — for example when transmit is disabled on the source (TX_DISABLED)."

The distinction between "no routing ACK awaited" and "never fails" is now explicit. Accurate — TX_DISABLED at adminRoutes.ts:1916 confirms the failure path for unsendable commands. Fixed.

Additional Accuracy Spot-Checks

A few things I also verified while re-reading:

  • ACK_AWAITED_COMMANDS set (adminRoutes.ts:1483–1488): setFavoriteNode, removeFavoriteNode, setIgnoredNode, removeIgnoredNode — matches the doc's "Favorite, unfavorite, ignore, and unignore commands" ✅
  • failedChannels / failedChannelDetails in result (adminRoutes.ts:1417–1418): partial import reporting is accurate ✅
  • Operation visibility check (adminRoutes.ts:1958): operation.userId !== null && operation.userId !== requesterId — the doc's "visible only to the administrator who started it" is correct, with the nuance that a null-userId operation is not shared among null-session callers ✅

Summary

All four issues from the previous review are correctly addressed. The documentation is accurate, well-grounded in the implementation, and fills a genuine gap. No blocking issues.

Verdict: Approved ✅

@Yeraze
Yeraze merged commit 58d59ac into main Aug 1, 2026
16 checks passed
@Yeraze
Yeraze deleted the docs/async-remote-admin branch August 1, 2026 18:15
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