3 Commits
Author SHA1 Message Date
Juan DenisandClaude Opus 5 3c24037b64 test(subprocess): one stub kit for subprocess seams
Plan 75 §G7. The suite's hand-rolled stubs were entrenching the very
duplication the plan removes: _FakeProc pasted byte-identical in four
modules, ~25 fake_run closures re-implementing argv dispatch, and zero
tests patching the wrappers — every stub targeted raw subprocess inside
one service module, so each new service test recreated the raw seam.

tests/subprocess_stub.py is that seam. Tests script argv → result; when
G1 moves services onto run_checked() the retarget happens here once
instead of in twelve files.

Two properties are deliberate:

- An unscripted command raises instead of inventing returncode=0. It is a
  BaseException for the same reason SbinPathError is — services wrap
  subprocess in `except Exception`, so an AssertionError would be eaten
  by exactly the handlers the kit exists to test.
- Stubbed execs are still sbin-guarded. Patching subprocess removes the
  §B2 runtime guard, so the kit re-applies it; popen_guard grows a public
  guard_argv() as the one door for that check.

Matching normalises away a sudo prefix and argv[0] absolutisation, so one
scripted rule matches on a root box, a sudo box and a dev box alike.

Mutation-checked: removing the guard call fails the guard test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 11:24:20 -04:00
Juan Denis fbb09395fe test(utils): the Popen guard trusts helper-routed calls
The guard fired on real-exec integration suites (the §D matrix): under
an sbin-ful PATH, run_privileged passes a bare name through — correct
behavior, since the same call absolutizes under the unit's sbin-less
PATH. The guard's model ("dies under the unit") cannot see that
resolution from inside a dev PATH. It now stays silent when the exec
came through app/utils/system.py — the one door it exists to route
traffic through — and still fires on a raw call one frame away. The
§D integration module's opt-out is removed; the guard stays active
there, and the capdrop matrix leg passes with it on.
2026-08-17 22:58:18 -04:00
Juan Denis d5dd6a5dd2 test(utils): refuse bare-name sbin execs at runtime, not just in AST
The static sbin guard reads literal argv lists — 164 of 288 raw
subprocess sites (57%). The other 43% (variables, f-strings,
concatenation) is where plan 74's outage class can still hide: a call
that works in dev (PATH has sbin) and dies under the panel unit (PATH
has none).

An autouse conftest fixture now routes every subprocess.Popen the suite
makes through GuardedPopen, which sees the actual argv at call time and
refuses a bare name that resolves under the current PATH but not under
one with the sbin dirs removed. A binary simply absent on the test
machine still raises the honest FileNotFoundError — the guard judges
PATH shape, never machine contents. The refusal is a BaseException:
this bug class ships because blanket except handlers convert exec
failures into false facts, so an AssertionError would be swallowed by
exactly the handlers it hunts.

Coverage is 'whatever the suite exercises' — honest, measurable, and
growing for free with every new test. Full suite green with the guard
live (3791 passed; 2 pre-existing load flakes pass in isolation).
2026-08-17 22:06:07 -04:00