✅ fresh
last synced 2026-09-29T21:12:19.681221+00:00 · coverage 100% (
guard-probes)Validation by Beadloom
doc_sync— same source assync-check.
Guard Probes (component)
Internal building block of the CLI service.
Source: src/beadloom/services/guard_probes.py
Overview
The concrete, read-only probes that bind the flow guards to the real bd and git. Guard checks are written against ports declared in application/guards/contract.py; this is where those ports meet the actual tools.
It lives in the services layer for one hard reason: the bd seam does, and the application layer must not import services (architecture-layers, severity error). Wiring the adapters at the CLI boundary keeps the dependency pointing inward.
Public surface
build_probes(project_root)— the real probe set (GuardProbes).BdWorkTracker—claimed_beads()frombd list --status in_progress --json --limit 0, with the status re-checked client-side as belt-and-braces. ReturnsNone(not an empty tuple) when the project has no.beads/directory, whenbdis not installed, or when the call fails. EachClaimedBeadcarries adeclarationcomposed with the wave planner's owncompose_declaration, not by joining the tracker's fields here: the Gate's ownership report and the wave plan read one declaration through one parser, so they cannot come to disagree about what a bead said.bd list --jsonomits a field a bead does not carry, which the joiner already treats as an empty one.UNLIMITED— the--limitvalue that lifts bd's 50-row page.GitWorkspace—current_branch()viagit branch --show-current, which answers correctly on an unborn branch and prints nothing on a detached HEAD.CLAIMED_STATUS— the bd status token meaning "claimed and in progress".
Invariants
- Read-only.
bdis queried only when.beads/already exists, because invoking it in an unrelated repo could initialise state there — a guard must never change the project it inspects. - Unavailable is
None, never a default. Nothing here falls back to a value that would manufacture a green verdict;Nonemakes the guard skip with a stated reason. - All of the evidence, not the first page.
bd listcaps its answer at 50 rows unless told otherwise (--limit 0). Reading one page and filtering client-side made a bead claimed beyond it invisible, sobead-claimedreported "no bead is in progress" while one was — a false warning, and a false BLOCK under thestrictness: { epic: block }configuration the docs ship as the example. - Text is decoded by a stated codec, never by the image's locale.
gitis run withencoding="utf-8", errors="surrogateescape".text=Truedecodes withlocale.getpreferredencoding(False), which made the same repository answer differently depending on where the process ran: under a C-locale container the decode raised (and, being aUnicodeDecodeError, escaped the handler below), and under an 8-bit locale it returned a branch name nobody had checked out.surrogateescaperather thanstrictbecause it is the only one of the handlers that is injective — it round-trips to the exact bytes git holds, sobranch == trunkstays truthful and a legal-but-not-UTF-8 branch name does not silently switchworking-branchoff.strictwould answerNonethere and the guard would skip: an exemption nobody declared. (BDL-061.37) - The handler is as wide as the sentence it holds. "A probe that cannot answer returns
None" quantifies over every way the call can fail, so it catchesException— not an enumeration of classes, which is howUnicodeDecodeError(aValueError: neither anOSErrornor asubprocess.SubprocessError) used to escape and reach the boundary as anerrorverdict at exit 2. Blocking the edit is not the designed answer for "the probe cannot answer"; a skip that says why is. Deliberately notBaseException: an interrupt is the process being stopped, not git declining.
Collaborators
Used by beadloom guard (services/commands/guard.py), which passes the probe set into evaluate_guard. Tests patch the command's _probes seam for stubbed probes, and exercise these adapters against a real git repository and against a real bd project built by the test (bd init in a temp dir, one bead claimed, one left alone) — a stub proves the stub's contract, so the binary boundary gets its own test.