step.25.txt -- b3ubot U5.4: CCS-unit workflows -- the grammar-aware arcs (validate / deliver+unpack / status) as engine functions over the CCSService port plus the `b3ubot ccs` CLI: the manual-relay operator's day-to-day grammar tools, and the deliver-on- convergence half the §2.10 shape was missing. Step: 25 Parent: end_to_end.md §7 P5 U5.4 row ("CCS-unit workflows: grammar-aware assistance (compile/validate/regenerate arcs in the adaptive loop)"); design.md §2.8 ("a workspace that contains .sgr specs gets grammar-aware assistance: compile, validate, round-trip, regenerate") + §2.10's iterate- cheaply/deliver-once dovetail -- validate existed as a VERIFYING tier (U5.2) but NOTHING could yet deliver a bundle INTO a workspace; this step is that missing half. UoW: U5.4 -- fourth P5 UoW; U5.4a (foreign workspace, by amendment) and U5.5 (the trinity gate) remain. Date: 2026-07-22 Origin: User directive 2026-07-22: "go with U5.4" (the (p1) sequence). ## (0) Status EXECUTED same-session. app/ccs/workflows.py: validate_spec (the cheap arc), deliver_unit (the paid arc -- generate through the plane, unpack INTO the workspace through the tool surface's NEW write_bytes, every member trail-recorded and C-4-B-scoped; REGENERATE = the same arc re-run), plane_status (the operator probe). CLI: the `b3ubot ccs` group (validate/generate/status) over a monkeypatchable _make_ccs_service seam (the _make_adapter precedent), exit-code contract 0=PASS/done, 1=spec-at-fault, 2=call/environment-at-fault. Round-trip deliberately NOT an arc: it lives in the VERIFYING tiers (oracles gate, arcs assist). Suite 623 passed + 24 skipped = 647 collected; p5_gates 13/13 live legs ALL PASS. One found-and-fixed regression from step.24's own DOCS edit -- see Q-25-C: the ledger table machinery (our own dogfooding) refused a mis-width hand-edited note cell; 16 table tests were red AT the step.24 WORK commit because the suite was measured BEFORE the docs edits landed. Fixed here; lesson recorded (measure AFTER the last edit -- docs are data too). ## (1) Ground truth probed at drafting (outputs -> named writers) READ app/cli.py conventions: workspace_root positional, thin presentation over pure raising functions, the _make_adapter factory seam, ledger's nested-subparser precedent -- the `ccs` group copies all four. CONFIRMED tools.write_file is TEXT-ONLY (write_text) -- bundle members (.bfgr) are binary; the step.22/23/24 live tests unpacked via zipfile.extractall OUTSIDE the tool surface (test-land, tolerable there; engine-land needs the trail) -> write_bytes (2.1). CONFIRMED nothing in the engine delivers artifacts into a workspace: Artifacts.zip_bytes existed since U5.1 with no consumer writing it anywhere -- the §2.10 "deliver once, on convergence" half was missing entirely. CONFIRMED the trail row key is `tool`, not `op` (a first-draft test assumed wrong and was corrected against trail.py's record_tool_call -- probe-before-parse). ## (2) What U5.4 delivers 2.1 **tools.write_bytes** (workspace.py + the hub): the binary sibling of write_file -- same C-4-B scoping via _resolve_within (a traversal-shaped zip member name REFUSES instead of escaping), trail row carries the byte COUNT, never the payload. 2.2 **app/ccs/workflows.py** -- the three arcs over the port: validate_spec(root, rel, service) -> Verdict (refuses non-.sgr via the new CCSWorkflowError; unit = stem, the path-identity rule); deliver_unit(root, rel, service, langs, dest_rel) -> DeliveredBundle{unit, revision, spec_sha256, dest_rel, files} (default dest generated/; dir members skipped; every file through write_bytes; regenerate = re-run, revision moves server-side); plane_status(service) -> {health, entitlement}. 2.3 **The `b3ubot ccs` CLI group**: validate (exit 0 PASS / 1 FAIL / 2 error, diagnostics printed -- the repair loop's food made operator-visible), generate (prints identity + the file inventory; a 422 prints the diagnostics and exits 1 -- validate-first avoids it), status (plane + account + the entitlement row). Service via _make_ccs_service (B3uDevClient, refused loudly when unavailable; tests monkeypatch the seam). 2.4 **Tests**: tests/test_ccs_workflows.py (13 hermetic -- write_bytes binary+trail+traversal, both verdicts, non-.sgr / missing-file refusals, unpack-through-the-trail with a binary member byte-exact + dir-member skip, dest override + regenerate-overwrites, status, and 5 CLI legs incl. both non-zero exit codes and the no-plane refusal) + tests/test_ccs_workflows_live.py (3 live -- both verdicts on the real plane; a real bundle delivered+unpacked with the revision moving on regenerate; status showing the local row). 2.5 **p5_gates.sh extended**: live 10 -> 13 legs; hermetic twin 43+10 -> 56+13; summary U5.1-U5.4. 2.6 **The step.24 table regression, fixed** (Q-25-C): the U5.3 ledger-note block's title cells re-normalized to the table's own border width; the 16 red table tests are green again. 2.7 **Docs**: design §2.8 U5.4 note; end_to_end U5.4 done; tests/testing.md re-measured same-commit. EXPLICITLY OUT OF SCOPE (each a named later UoW, never silent): - a round-trip ARC (it is an ORACLE -- [T3:validate]'s -u leg + [T3:scb_identity]; oracles gate, arcs assist) -> never - orchestrated deliver-on-close (wiring deliver_unit into closing.py/the arc -- U5.5 decides where delivery sits in the full trinity arc; premature coupling now) -> U5.5 - foreign-workspace operation -> U5.4a - spec-revision history client-side (the plane's unit store owns revisions; the client reads them off DeliveredBundle) -> later - `ccs packages`/runtime management CLI (ensure_runtime is engine-internal to the scb tier; no operator consumer yet) -> later ## (3) Verification gates G1 write_bytes: binary bytes land byte-exact; the trail row carries rel_path + count; traversal refuses (C-4-B). G2 ARCS hermetic: both verdicts through validate_spec; non-.sgr and missing-file refuse with named types; deliver_unit unpacks EVERY member through the trail (3 write_bytes rows), binary byte-exact, dir members skipped, dest override works, regenerate overwrites a corrupted member back to truth; plane_status bundles the two probes. G3 CLI: exit-code contract proven (0/1/2 legs); diagnostics reach stdout on FAIL; the inventory prints on generate; the no-plane refusal names the next action on stderr. G4 LIVE: 13/13 p5_gates legs -- the three workflow arcs against the launcher-booted b3u (real verdicts both ways; a real bundle unpacked into a workspace incl. binary .bfgr; the revision MOVES on regenerate; the local entitlement row); hermetic twin 56 passed + 13 SKIPPED; B-4 grep clean. G5 REGRESSION REPAIR: the 16 table tests red at the step.24 WORK commit are green; the ledger table round-trips again. G6 NO REGRESSION: full suite 623 passed + 24 skipped = 647 collected, 0 failed (measured AFTER the last edit of this step, docs included -- the Q-25-C lesson applied). G7 Hygiene: porcelain after the WORK commit; testing.md moved with the tests; ledger + design current; gdiff via ../scripts/step_gdiff. ## (4) LOCKs C-25-A b3ubot porcelain clean after each commit. C-25-B DELIVERY GOES THROUGH THE TRAIL: every bundle member a workflow arc writes lands via tools.write_bytes -- workspace-scoped, trail-recorded; zipfile.extractall never touches a workspace from engine code. C-25-C ORACLES GATE, ARCS ASSIST: the workflow arcs never record verdicts into verifications.jsonl and never advance engine state -- a `ccs validate` PASS is assistance, not evidence; evidence comes from the VERIFYING tiers. C-25-D EXIT CODES ARE THE CONTRACT: 0 done / 1 spec-at-fault / 2 call-or-environment-at-fault -- scripts (and U5.5's arc) may branch on them; changing the mapping is a breaking change to record loudly. C-25-E standing locks inherited: C-24-B (client posture), C-22-D (the contract is the seam), C-23-B/C (verdict vs defect; engine facts), B-1/B-3/B-4. ## (5) Open questions Q-25-A Where does delivery sit in the ORCHESTRATED loop (DEFERRED to U5.5, named): deliver-on-close vs an explicit post-close operator step vs a dag node kind -- the trinity gate's full arc is the first real consumer and decides with evidence; wiring it now would be speculation (the d9 posture). Q-25-B CLI service seam (DECIDED: a module-level _make_ccs_service factory, monkeypatched by tests -- the _make_adapter precedent verbatim; a --base-url flag was REJECTED: $B3U_BASE_URL already owns that override, C-22-C's env>config>default chain, one owner per knob). Q-25-C The step.24 DOCS regression (FOUND HERE, FIXED HERE): the hand-edited U5.3 ledger-note block carried a 39-char title cell against the table's 38-char border; b3ubot's OWN table parser (the live-ledger dogfooding) refused -- 16 tests red at the step.24 WORK commit, invisible because that step's suite run happened BEFORE its docs edits. DECIDED: (a) fixed by re-normalizing the block (2.6); (b) the lesson joins the house rules -- MEASURE AFTER THE LAST EDIT: docs are data to this machinery (ledger tables, step files) exactly like code. The named future remedy is machine-written note blocks (`ledger transition` owns rows; a note-block emitter would own these), recorded, not built. ## (6) Acceptance - [x] write_bytes + hub export, tested (2.1, G1). - [x] workflows.py arcs + CCSWorkflowError, tested (2.2, G2). - [x] `b3ubot ccs` group + seam + exit-code contract (2.3, G3). - [x] 16 new tests (13 hermetic + 3 live); p5_gates 13/13 ALL PASS; hermetic twin 56+13 (2.4, 2.5, G4). - [x] step.24 table regression fixed; table suites green (2.6, G5). - [x] Full suite 623 passed + 24 skipped = 647 collected, 0 failed, measured after the last edit (G6). - [x] Docs moved in the same commit (2.7, G7). ## (7) Hash backfill WORK commit (everything above + this file): (this commit) GDIFF commit (step.25.gdiff.txt): (HEAD) End of step.