feat(control): pending change state, status age, setup URL re-prompt (#344) - #386
Merged
Conversation
…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>
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.
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.
state/pending/<cid>.json({status: pending, accepted_at}) the instantPOST /applystages it.GET /status?change_idchecks the root-written terminal record first, then falls back to pending; unknown ids keep today's 404 exactly. A dedicatedpending/dir (not root-ownedstate/changes) avoids an ownership wall on rigs with prior control history;_control_statusclears the marker when the terminal record lands; a crashed or superseded run stays honestlypendingforever with visibly growing age — never guessed into a fake outcome (semantics documented at the write site). The receiver prunespending/to the newest 20./statusbody (no-arg and change_id alike) carries a derivedage_secondsnext to its ownapplied_at/accepted_at, computed at serve time and never persisted. Additive only; nothing renamed.SETUP_URL_TRIES, default 3, matching theAPPLY_POOL_TRIESidiom) instead of exiting the whole script; EOF/non-interactive stdin still terminates./statuscontract 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.jsonas the new guard's first customer): new status valuepending(change_id polls only), new derived keyage_secondson every/statusresponse.What was run:
make lint(shellcheck 0.11.0 + shfmt 3.13.1) clean;python3 -m py_compileon control-server.py; markdownlint-cli2 0.22.1 on the touched docs (0 errors); fullbash tests/run.shforeground: 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