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/<unit>; 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.
