The Decomposition Machine
The decomposition machine
Section titled “The decomposition machine”bob decompose turns an approved capability or use case into filed child
requirements. It is BoB’s second state machine
(#2973), built beside
warp-drive rather than inside it. To run one, see
Decompose a capability.
Why decomposition gets a machine first
Section titled “Why decomposition gets a machine first”A bad requirement looks exactly like a good one while it sits in the backlog. It duplicates something filed in July, or has a criterion nobody can check, or has no parent link, and nobody finds out until an agent spends two hours on it. That kind of failure is silent, delayed and expensive, which makes decomposition the place where a machine pays off first.
Most of the checks already existed as code: cap-status.js, issue-dedupe.js,
the /groom orphan check and gh. The trouble was that they ran as advice
inside a prompt, only when the model remembered to run them. The machine puts
them in a fixed order and makes each one a transition the engine verifies.
Six phases, six gates
Section titled “Six phases, six gates”| Phase | Gate | Reuses |
|---|---|---|
sourcing |
The parent exists, is open, is a cap or use-case, and is approved |
cap-status.js planSource() |
drafting |
The draft is well-formed: title, req label, acceptance criteria, no report labels. The parent’s area label is inherited. Nothing is filed. |
the /capability and /use-case decomposition guidance |
deduping |
No candidate has an unresolved duplicate verdict. A duplicate is dropped or merged, and the decision is recorded. | issue-dedupe.js, via the decide() caller groom-dedupe |
testability |
No child has an unresolved non-observable criterion | the decompose-testability judge (#2977) |
tracing |
Every child carries Part of #N (Realizes #N for a use case), and the parent lists every child |
cap-status.js hasPartOf / parseChecklistRefs, the /groom orphan check |
filed |
Each child and the parent are read back from the forge and match | gh |
Every gate event is evidence-fired. bob decompose transition <event> runs
that phase’s gate, and only a gate that passes fires the event, with the gate
recorded as its evidence. A plain engine transition cannot claim a gate. A
failed gate leaves the run where it is and records a named refusal on the
run state: the gate, the reason, and the children at fault.
Fail-open where it is cheap, fail-visible where it is not
Section titled “Fail-open where it is cheap, fail-visible where it is not”The two decide() callers deliberately fail in different directions:
- Dedupe is a gate, so it fails open. If the model has no opinion, the
candidate is recorded as
skipped, and the worst outcome is one extra issue that a person can close. - Testability is a judge, so it fails visible. If the model has no opinion,
the child can still be filed, but it carries a
<!-- decide:abstained decompose-testability -->marker. “The machine had no opinion” must never look like “the machine approved”.
A criterion the judge answers not observable blocks filing until it is reworded or a human override is written on the child. The run cannot override as itself.
What it shares with warp-drive, and what it leaves out
Section titled “What it shares with warp-drive, and what it leaves out”The machine runs on the shared engine
(scripts/warp-drive/machine-engine.js, #2975):
the same atomic state-file write under a lock, the same transition validation,
and the same evidence-fired advancement. Its phases are its own, and warp-drive’s
frozen phase-contract/1 is untouched.
Its artifact is the set of filed issues, and nothing else. It writes no chunk
report, no session report, no commit and no branch. Because it writes issues and
not files, it is declared concurrent_with_warp: true and can run while a
warp-drive session codes in the same checkout. See
The shared machine engine.
Where it plugs in
Section titled “Where it plugs in”/capability and /use-case hand child creation to bob decompose at
automation Level 2 and above. /groom’s rule for an approved capability with no
children names it as the way to decompose. The engine is
scripts/decompose/decompose.js, with coverage in tests/test-decompose.js
(a stubbed gh and the deterministic decide() backend, with no key and no
network).