Skip to content

feat(control): pending change state, status age, setup URL re-prompt (#344) - #386

Merged
VijitSingh97 merged 1 commit into
developfrom
fix/344-control-api-ux
Aug 21, 2026
Merged

feat(control): pending change state, status age, setup URL re-prompt (#344)#386
VijitSingh97 merged 1 commit into
developfrom
fix/344-control-api-ux

Conversation

@VijitSingh97

Copy link
Copy Markdown
Contributor

Closes #344 — items 2, 3, and 4. Item 1 (a fast path for restart-free keys) is deliberately out of patch scope and filed separately as #381.

  • Item 2 — pending state. util/control-server.py records an accepted change_id as state/pending/<cid>.json ({status: pending, accepted_at}) the instant POST /apply stages it. GET /status?change_id checks the root-written terminal record first, then falls back to pending; unknown ids keep today's 404 exactly. A dedicated pending/ dir (not root-owned state/changes) avoids an ownership wall on rigs with prior control history; _control_status clears the marker when the terminal record lands; a crashed or superseded run stays honestly pending forever with visibly growing age — never guessed into a fake outcome (semantics documented at the write site). The receiver prunes pending/ to the newest 20.
  • Item 3 — staleness cue. Every served /status body (no-arg and change_id alike) carries a derived age_seconds next to its own applied_at/accepted_at, computed at serve time and never persisted. Additive only; nothing renamed.
  • Item 4 — setup re-prompt. An invalid pool URL in the first-run prompt re-prompts (bounded: SETUP_URL_TRIES, default 3, matching the APPLY_POOL_TRIES idiom) instead of exiting the whole script; EOF/non-interactive stdin still terminates.
  • Docs: the control-flow section of operations.md and the /status contract in pithead-integration.md updated to match.

Wire-visible changes (for the #351 contract fixture — land that PR first; this branch then rebases and updates tests/contract/v1/control-status.json as the new guard's first customer): new status value pending (change_id polls only), new derived key age_seconds on every /status response.

What was run: make lint (shellcheck 0.11.0 + shfmt 3.13.1) clean; python3 -m py_compile on control-server.py; markdownlint-cli2 0.22.1 on the touched docs (0 errors); full bash tests/run.sh foreground: pass, including the new assertions covering the re-prompt paths (blank/portless/bad-IPv6 inputs, the exhausted-retries give-up, the SETUP_URL_TRIES=1 bound), the pending lifecycle end-to-end against the real server (accept → 200 pending → terminal record wins → marker cleared), and age on genuinely stale records. Not run: make coverage (Linux-only) — the changed rigforge.sh lines are exercised by the new tests.

🤖 Generated with Claude Code

…mpts (#344)

A live walkthrough (v1.14.0) found three control-API UX gaps (#344 items
2-4; item 1, a fast-path apply pipeline for restart-free keys, is a
separate, deliberately out-of-scope change):

- GET :8082/status?change_id=<real> returned the same "no recorded
  outcome" a WRONG id gets while a change was still mid-apply (the
  pipeline can run tens of seconds) — a poller couldn't tell "in
  progress" from "never existed". The unprivileged receiver
  (control-server.py) now records the accepted change_id as pending
  (accepted_at stamp) in its own state/pending/ dir the instant /apply
  stages it — before the root control-apply oneshot even starts.
  state/changes/<cid>.json (the terminal record, written by root) is
  always checked first; pending/ is the fallback. A dead/superseded run
  (control-apply crashes, or a newer change supersedes this one before
  the oneshot ever reads it) is left pending FOREVER rather than guessed
  into a fake outcome — control_apply's _control_status clears the
  matching pending/ marker once it lands the real one. Unknown ids keep
  today's 404 exactly.

- The no-arg GET :8082/status could surface an outcome many days old
  with no staleness cue (an 11-day-old rollback record read as current).
  Every /status response now carries a derived age_seconds next to its
  own applied_at/accepted_at, computed at serve time and never persisted
  to disk — additive only.

- ensure_config_exists's first-run pool-URL prompt exited the whole
  script on a blank/portless/bad-host entry. It now re-prompts, bounded
  by SETUP_URL_TRIES (default 3, matching this file's existing
  APPLY_POOL_TRIES/CONTROL_LIVE_TRIES env-override idiom) so a
  non-interactive/EOF stdin still terminates instead of spinning.

Extends tests/run.sh's existing control-server wire suite (real HTTP
against the real python receiver) and the control_apply/_control_status
unit tests for every new branch; make lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@VijitSingh97
VijitSingh97 merged commit 9e6f4fd into develop Aug 21, 2026
9 checks passed
@VijitSingh97
VijitSingh97 deleted the fix/344-control-api-ux branch August 21, 2026 17:39
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.

Control API UX: no pending state, full-pipeline latency for light keys, stale first status, one-shot setup prompt

1 participant