CaltechExperimentalGravity / CaltechExperimentalGravity/system_ident
Stage E: per-injection human-approval gate; reject --yes on hardware; fix _confirm(twin=True)
- Dominant language
- Python
- Stars
- 0
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
**Stage E (spec §1, §5).** Enforce in code the hardware-safety rules now in
[`CLAUDE.md`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/CLAUDE.md): *only humans authorize the hardware*, and *each individual injection needs separate
approval — one approval never carries over*.
The sibling project enforces **neither in code** — no `input()`, no `--confirm`, no dry-run on its hardware
path; the rules are documentation-only there. This repo should do better, because it already has most of
the machinery.
### The gate goes inside `CDSBackend.inject()`
It is the only code path from any caller to the actuator, and the only place holding the actual samples.
Rejected alternatives, and why:
- **A `Watchdog` pre-injection hook** or a `loop.py` change is a *convention*, not an invariant: any script,
notebook or test that constructs the backend and calls `inject()` directly bypasses it entirely. It also
makes `SysIDLoop` backend-aware, contradicting [`src/system_ident/backends/base.py:3`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/backends/base.py#L3) ("the loop and
the safety handoff use only these methods, so simulation and hardware are truly interchangeable"), and
the watchdog has no access to the drive array so it cannot show the operator what is about to happen.
- **A decorator or mixin** has the same bypass risk unless applied at class level, at which point it *is*
the inject-internal gate with extra indirection.
### Approval is a single-use token
Staging and actuation are now separate (`inject()` stages, `read()` executes), so approval granted at
`inject()` is for an actuation that happens later:
- `inject()` prompts — printing channel, hardware rate, duration, **RMS, peak and crest**, and the
`start_gps` window — then mints `_approval[channel]`.
- `read()` **refuses to start** any staged injection whose token is missing or consumed; consumes it on
start.
- Re-reads of an already-running injection do **not** re-prompt: nothing new is actuated. A new `inject()`
mints a new token. That is Rule 2 exactly.
- The prompt is injectable (`authorizer=`) for testability, and **defaults to deny** on `EOFError` /
non-TTY. Never proceed in batch.
### Fix the existing CLI gate
- [`src/system_ident/cli.py:89`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/cli.py#L89) calls `_confirm(twin=True)` **unconditionally**, so the `"HARDWARE"`
label at [`src/system_ident/cli.py:129`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/cli.py#L129) is dead code and a hardware run would announce itself as a
twin run → `_confirm(twin=args.twin or args.rtsfreerun)`.
- `--yes` ([`src/system_ident/cli.py:45`](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/src/system_ident/cli.py#L45)) is a campaign-wide skip, i.e. by definition a carry-over
approval, which Rule 2 forbids → **reject it at argparse level (exit 2)** when neither `--twin` nor
`--rtsfreerun` is given. The CLI prompt then becomes a pre-flight; the *gate* is the token.
### Acceptance — tests that assert it cannot be skipped
All on the Stage A fakes:
1. Denying authorizer → raises, **and `FakeArbitraryLoop` was never constructed** (assert the construction
counter, not `.start`).
2. Authorizer called exactly twice for two `inject()` calls; exactly once for one `inject()` + three
`read()`s.
3. `read()` with a staged-but-unapproved injection → raises, `.start` never called.
4. Default authorizer with `input` raising `EOFError` → denies.
5. `main(["run", cfg, "--yes"])` without `--twin` → exit 2.
---
**Campaign:** CDS hardware backend · branch [`feat/cds-hardware-backend`](https://github.com/CaltechExperimentalGravity/system_ident/tree/feat/cds-hardware-backend)
· [spec](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/docs/superpowers/specs/2026-08-03-cds-hardware-backend-design.md) · [plan](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/docs/superpowers/plans/2026-08-03-cds-hardware-backend.md) · [handoff](https://github.com/CaltechExperimentalGravity/system_ident/blob/feat/cds-hardware-backend/notes/cds-hardware-bringup-2026-08.md)
*Code is deferred until the plan and issues have been reviewed.*
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the CDSBackend inject/read path and the Stage A fakes described in the acceptance criteria, then inspect src/system_ident/cli.py and src/system_ident/backends/base.py. Confirm that each injection requires a single-use approval, denied approvals prevent construction or start, EOFError denies by default, and hardware rejects --yes with exit 2.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, cli
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100