Skip to content

test(verify): add host-run live validation script for host-authoritative config - #682

Open
mogul wants to merge 33 commits into
mainfrom
feat/639-live-validation-script
Open

mogul wants to merge 33 commits into
mainfrom
feat/639-live-validation-script

Conversation

@mogul

@mogul mogul commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

🤖: AI agent (First Mate), AI-assisted change (OpenCode), posting on behalf of @mogul. Human review required before merge.

Summary

Prepares the human-run live validation for #639. This sandbox has no sbx or msb, so nothing here was run live. The script was only syntax-checked (bash -n), shellchecked, and dry-run tested through offline bats. No live result is claimed or recorded; every row in the new results template is BLOCKED / unrun.

  • scripts/verify-host-authority-live: walks the "Still unverified on a real host" checklist. Prints PASS / FAIL / BLOCKED per step with the acq revision and backend version, uses timeout --foreground, tees live output to a transcript, has a per-step timeout, an INT/TERM/EXIT trap that removes created sandboxes, lowercase hal-<ddhhmmss>-<x> names, ACQ_NO_PROMPT=1, closed stdin, and never prints secrets. Output defaults to <repo>/tmp/verify-host-authority-live-<stamp>/ (--out DIR overrides): transcript.log, results.tsv. --dry-run prints the plan and MANUAL instructions and touches nothing.
  • docs/VERIFY_PROVIDER_ADOPTION.md: new "Host Authority Live Validation Record" template (revision, backend version, date, result, transcript path), all BLOCKED/unrun.
  • test/bats/161-verify-host-authority-live.bats: 14 offline cases for argument parsing and --dry-run only. test/bats/162-verify-host-authority-live-runtime.bats: 21 RUNTIME regression tests of the verdict logic against stubbed acq/sbx/msb (seams HAL_ACQ_BIN, HAL_RUN_ID); stub results are not live proof.

Automated steps (12): msb: msb-ro-kit-create (readonly fixture kit file end to end on create; unblocked by the Patterns v1.19.0 pin), msb-inspect-shape (.active_config.mounts and .config.mounts), msb-exit-code, msb-ro-kit-heal (restart), msb-forged-markers (exec sessions), msb-install-gate (delete host install-* keys), msb-ro-oci (shipped oci-engine readonly files). sbx: sbx-baseline, sbx-forged-heal, sbx-record-change, sbx-stale-refresh, sbx-legacy-reseed.
Manual (7, printed with exact commands and what to observe): msb interactive forged markers and agent key; forged ssh socket with a live agent; reboot persistence; Linux / other msb versions; ADR-0037 steps 1-4 and 6; steps 5 and 8; step 7 (Windows).
Not repeated (already validated live per the issue): the msb 0.7.7 qualify-msb-ro-mount canary, the default-sandbox msb inspect mount shape, the provider-adoption gate.

Issues

Refs #639 (not closing it: the live runs are still to be done by a human). Depends on #649 (Patterns pin, merged).

Before / After

Before: the checklist in #639 was prose only. After: one host command produces per-step PASS / FAIL / BLOCKED rows plus a transcript, and a results table exists to record them.

Verification

Run in this sandbox (offline only; no sbx/msb available):

  • bash -n scripts/verify-host-authority-live: ok
  • shellcheck --severity=warning (via scripts/run-shellcheck, script and new bats): clean
  • ./scripts/test-acq-bats on bats 161, 162, 158, 150: 50 ok, 0 not ok (head 479aca0)
  • scripts/test-acq-bash32 --syntax could not run here (no bash 3.2 available); CI bash32-compat is the real check
  • ./acq kit validate on the generated fixture kit: OK
  • Live-mode run without backends: all 12 steps record BLOCKED, exit 2, no sandbox created
  • No bash 4 constructs (a bats case greps for them); the real bash 3.2 lane is not run here (CI bash32-compat)
  • markdownlint on the docs file: clean
  • A read-only adversarial reviewer found 4 blockers + 1 slug bug (steps able to PASS without evidence, empty selection exiting 0); all fixed and re-reviewed, final verdict NO BLOCKERS after the slug fix.

Not verified: every live code path (the 12 automated steps' real behavior against msb and sbx). Expect the first live run to find script bugs; treat a script error as BLOCKED, not as a product failure.

Review round 1 (basiliskus)

Addressed in 479aca0: stale-refresh now forces only its own sandbox's provenance stale, verifies acq kit check says stale, and requires exit 0, no warnings and all four kit lines; every step requires exit status 0 plus complete evidence, and timeouts and probe or inspect failures are BLOCKED, not FAIL; the install gate cannot PASS on a timed-out restart; unique run-id sandbox names with a name-collision refusal, cleanup only of sandboxes this run created, scratch dirs via checked mktemp -d under the output dir, MANUAL 5 uses checked scratch dirs with run-id names; all backend probes go through the bounded runner.

Merge Guard

Draft. Do not merge until a human has reviewed. Follow-up for a human with workflow scope: add scripts/verify-host-authority-live to the relevance list in .github/workflows/bash32-compat.yml (the push token lacks workflow scope, so I listed it under NOT_INPUTS in test/bats/158-bash32-relevance-paths.bats; the script is only executed in --dry-run by the suite). That move is a one-line change in both files.

Rollback

Revert this PR; it adds one script, one bats file, one docs section, and one .pre-commit-config.yaml regex entry. No product code changes.

Security Impact

None to acq. The script runs only on a host a human chooses, creates throwaway lowercase-named sandboxes, and removes them on exit or interrupt. It uses existing acq credentials but never prints, seeds, or writes secret values; msb inspect JSON (placeholders only) is saved under the gitignored tmp/. It deletes only host install-* keys and edits the host builtin-kits record of its own throwaway sandbox. It does not push or modify CI.

Co-authored-by: OpenCode Agent user@gsa.gov

@mogul
mogul marked this pull request as ready for review November 5, 2026 05:40
@mogul
mogul requested a review from a team as a code owner November 5, 2026 05:40

@basiliskus basiliskus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: request changes — a714d671001bcb5dd9f2f107f990ae95db9d176b

  1. major — scripts/verify-host-authority-live:622: Establish stale provenance before testing refresh. acq kit update exits successfully without applying anything when provenance is current (acq:1060–1063). Fresh creation records current provenance, and changing builtin-kits does not change that currency signal. Consequently this step can label a healthy backend FAIL without exercising refresh. Confirmed by source tracing and an extracted-function offline probe. Make only this throwaway sandbox stale and confirm all expected kits were attempted.

  2. major — scripts/verify-host-authority-live:623 (also :612, :531): Do not infer PASS from partial output while ignoring command failure. One successful kit's “refreshed” line makes the entire refresh PASS even if another kit fails and the command exits 1. The install-gate step likewise passes when the proof counter advances but restart times out (124). Confirmed with offline injected-command probes. Require successful execution and complete per-step evidence; classify timeout/probe failures as BLOCKED rather than a product result. Add runtime regression tests, not just dry-run assertions.

  3. major — scripts/verify-host-authority-live:172–188: Use uniquely owned manual fixtures and cleanup. MANUAL 5 reuses $HOME/hal37, overwrites its sentinel, then instructs rm -rf "$D"; the fixed sandbox names are also force-removed. These commands can damage files/sandboxes the check did not create. Confirmed by tracing the printed commands. Use a checked unique scratch directory and collision-safe names; clean only resources created by that run.

  4. minor — scripts/verify-host-authority-live:414: Bound all backend probes. proof_lines invokes msb exec without timeout; version/doctor preflights are also unbounded. A hung backend can bypass the advertised timeout and prevent reaching cleanup. Confirmed by source tracing. Route these through bounded capture with explicit status handling.

Not verified: No live backend execution; local Bats unavailable (uninitialized submodules). Saved CI logs show the 14 new parsing/dry-run tests ran under bash 3.2; they do not exercise these runtime paths.

Assisted-by: OpenCode [gpt-6.1-sol-latest-guardrails-defaultv2] skill:change-review

@mogul

mogul commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

🤖: AI agent (First Mate), posting on behalf of @mogul. Human review required before merge.

Addressed the review in one push (head 479aca0, merged origin/main with plain git merge; no rebase or force-push). Nothing here was run live; the script is still only syntax-checked, shellchecked and tested offline against stubs.

  1. Stale refresh (major): sbx-stale-refresh now makes only its own throwaway sandbox's provenance record stale, confirms acq kit check reports stale, then requires kit update exit 0, no warnings and all four kits reported (Zscaler CA, provider, playbook, git-ssh-sign); kit check must say current afterwards. An sbx that only prints the 0.38+ recreate notice is BLOCKED, not a refresh result. (a5e384f)
  2. Partial output (major): every step requires exit status 0 plus complete evidence; timeouts (124), refused creates and probe or inspect failures (including a null active_config) are BLOCKED, never FAIL or PASS. The install gate cannot PASS when the restart times out. record-change requires kit add attempts for all three record-tracked kits. (a5e384f, 479aca0)
  3. MANUAL 5 and cleanup (major): run-id sandbox names (hal-<run id>-...), refusal if a name is already in use, cleanup only of sandboxes this run tracked after the collision check; scratch dirs come from mktemp -d under the output dir (or under tmp/ in the MANUAL commands) with a path check before any rm -rf. (a5e384f)
  4. Unbounded probes (minor): msb --version, msb doctor, sbx version and the proof-file reads now go through a bounded probe with explicit status handling. (a5e384f)

Also: sbx-forged-heal now requires the probe file to exist before removal and a present host record. (479aca0)

Tests: new test/bats/162-verify-host-authority-live-runtime.bats (21 runtime cases against stubbed backends); bats 161, 162, 158 and 150 give 50 ok, 0 not ok; shellcheck clean. As before, .github/workflows/bash32-compat.yml is not edited (no workflow scope); the follow-up stays in the PR body.

mogul and others added 6 commits November 7, 2026 14:49
…pipes

timeout | tee waited for every descendant holding the pipe, so a TERM-ignoring background
helper hung the whole run past --create-timeout. Commands now write to files, run in the
background and are polled against a wall-clock deadline; on expiry the process tree is
TERMed then KILLed. Adds per-step budget, hang diagnostics (default on), bounded cleanup,
and a summary that always prints, including after SIGINT. A timed-out step is BLOCKED.

Co-authored-by: OpenCode Agent <user@gsa.gov>
@mogul

mogul commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

🤖: AI agent (First Mate), posting on behalf of @mogul. Human review required before merge.

Fix pushed for the unbounded wait in scripts/verify-host-authority-live (head 4e2e57e, wip commits 42ab64e..042bd50).

Cause (reproduced offline): timeout --foreground N cmd | tee returns only when every process holding the pipe exits. timeout kills only its direct child, so a TERM-ignoring background helper kept the pipe, and the script, alive far beyond --create-timeout.

Changes:

  • Every command (run_cmd, probes, msb inspect, acq rm / msb remove / sbx rm cleanup) writes to a file in the background; the script polls a wall-clock deadline and waits only for that PID. No pipe is ever read. At the deadline it TERMs, then KILLs, the command and its descendants (pgrep -P, or a ps fallback), with timeout -k as an outer backstop. No setsid or set -m.
  • Per-step wall-clock budget (--step-budget); any step with a timed-out command is reported BLOCKED(timeout) (PASS or FAIL verdicts are withheld).
  • Hang diagnostics (on by default, --no-hang-diagnostics to disable): on timeout, ps filtered to acq/msb/sbx/socat/sleep plus msb ls and sbx ls (each bounded) go into the transcript.
  • The summary always prints, including after SIGINT/TERM (exit 130, cleanup still runs and is bounded).
  • New runtime bats cases: TERM-ignoring background helper returns within the bound (the old script fails it), a parent that ignores TERM is KILLed, diagnostics on/off, cleanup after a timeout, summary after SIGINT, and a timed-out check downgrading FAIL to BLOCKED.

This fixes the unbounded wait. It is NOT live proof, and the ORIGINAL stuck process on the maintainer's macOS run is still unidentified until it is rerun with this revision (the transcript will now show what was stuck).

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.

2 participants