step.22.txt -- b3ubot U5.1: the CCS plane's client side -- the
               CCSService port (design §2.8), ReplayCCSService
               (deterministic fixtures, zero network), and
               B3uDevClient: the real HTTP implementation, written
               against b3u/docs/api_contract.md v1 and proven LIVE
               against a launcher-booted local b3u instance. P5
               opens; the trinity's b3ubot->b3u leg exists for real.

Step:     22
Parent:   end_to_end.md §7 P5 U5.1 row verbatim ("CCSService port +
          ReplayCCSService (deterministic fixtures) + B3uDevClient
          against the LOCAL b3u instance (login-token auth; B-1 by
          construction -- no CCS code in this repo)"). design.md §2.8
          (the port sketch + d5's client resolution), §2.9 (the
          trinity: 127.0.0.1 local b3u, the PL runbook), §2.5 (the T3
          tier this plane will feed at U5.2). Cross-repo inputs, both
          landed 2026-07-22: b3u UB.1 (verdict-only validate,
          b3u step.51) and b3u UB.2 (docs/api_contract.md v1,
          b3u step.52) -- the client is written against THAT document,
          never against b3u source (C-22-D).
UoW:      U5.1 -- first P5 UoW; U5.2 (T3 oracle over validate)
          consumes this port next; U5.5 is the trinity gate.
Date:     2026-07-22
Origin:   User directive 2026-07-22: "go with U5.1" (the (p1)
          sequence -- b3u UB.1 -> UB.2 -> b3ubot U5.1..U5.5).

## (0) Status

EXECUTED same-session. app/ccs/ package live (port + replay + client
+ error family); 31 new pytest oracles (24 hermetic + 7 live);
scripts/p5_gates.sh opened the P5 battery and ran ALL PASS on the
first invocation -- a real local b3u booted via the sibling's own PL
runbook, a throwaway account provisioned, and the 7 live legs green
over actual HTTP: the trinity's b3ubot->b3u leg is no longer a
diagram. Full suite 584 passed + 18 skipped = 602 collected (the 7
live legs skip honestly on a box with no b3u up; the last +2 are
THIS FILE entering the own-corpus parse/round-trip parametrization
-- the step file is data to the machinery it documents). One genuine
cross-repo finding: the b3u-corpus exact-count tripwire (==50) fired
exactly as its own comment designed, because b3u grew to 52 steps
during this same (p1) arc -- resolved by retiring the pin for
floor+contiguity (see (5) Q-22-F).

## (1) Ground truth probed at drafting (outputs -> named writers)

    READ      design.md §2.8 (364-419): the port sketch names
              generate/validate/deliver/entitlement; B3uDevClient
              (HTTP, /login HMAC token, first target 127.0.0.1:8941)
              and ReplayCCSService (fixtures, zero network) are the
              two named implementations; B-1 by construction.
    READ      b3u/docs/api_contract.md v1 (b3u step.52): the SEVEN
              frozen paths (health, login, me, generate, validate,
              packages list+fetch), the additive-only law, the
              200-on-FAIL verdict law, the 401/402/403/400/413/422
              taxonomy, bearer-only auth, 86400s TTL + UJ.4
              revocation semantics.
    CONFIRMED contract-v1 reality vs the §2.8 sketch: there is NO
              separate deliver op -- generate's 200 response IS the
              delivery (zip + X-B3U-Unit/-Revision/-Spec-Sha256), and
              b3u's eviction posture keeps no artifact at rest to
              fetch later -> Q-22-A.
    CONFIRMED no prior art in this repo: no app/ccs, no HTTP client,
              no requests/httpx dep (requirements.txt was pytest
              only), no base-URL config key. Greenfield, with the PAL
              port (app/pal/adapter.py ABC) as the shape precedent
              and app/orchestrator/errors.py as the error-family
              precedent.
    CONFIRMED the live-leg vehicle: b3u/scripts/b3u_local.sh (the PL
              runbook design §2.9 names) boots on $B3U_LOCAL_HOME/
              $B3U_LOCAL_PORT and its `signup` subcommand provisions
              + verifies an operator account with a caller-supplied
              $B3U_PASSWORD -- everything a gate script needs.
    CONFIRMED the skip-gating idiom to mirror: ClaudeCodeAdapter.
              is_available() -> (bool, reason) read at module scope
              by the keyed tests (tests/test_cli.py:31) -- never a
              bash re-derivation (the m1_oracle.sh rule).

## (2) What U5.1 delivers

  2.1 **app/ccs/service.py** -- the CCSService port (ABC, the
      app/pal mold): health() / validate() -> Verdict / generate()
      -> Artifacts / entitlement(). Frozen dataclasses Verdict{unit,
      passed, diagnostics} and Artifacts{unit, revision, spec_sha256,
      zip_bytes}. The Q-22-A amendment recorded in its docstring.
  2.2 **app/ccs/errors.py** -- the error family under one CCSError
      base: CCSUnavailableError, CCSAuthError, CCSEntitlementError
      (carries status+reason -- the 402/403 split), CCSRequestError
      (400/413), CCSGenerateRejected (422 + diagnostics),
      CCSFixtureError (replay's refuse-loudly).
  2.3 **app/ccs/replay.py** -- ReplayCCSService: pure lookups keyed
      by sha256(spec bytes) (content-determined, like the oracle it
      replays); from_dir() loads the JSON half of a fixture set;
      missing fixture = CCSFixtureError naming key + next action
      (Q-22-E). Shipped fixture set at tests/fixtures/ccs/
      (verdicts.json GOOD+BAD, entitlement.json + health.json modeled
      on the real local-mode bodies) -- U5.2's T3 tier eats these.
  2.4 **app/ccs/client.py** -- B3uDevClient: requests-based, written
      against contract v1 only. Lazy /login; ONE transparent re-login
      on 401 (token expiry is contract behavior, not a caller
      concern); the shared error mapping; base URL resolution
      $B3U_BASE_URL > config.txt b3u_base_url > loopback default;
      credentials $B3U_EMAIL/$B3U_PASSWORD env-ONLY (B-4), repr()
      redacted; is_available() -> (ok, reason) for honest gating.
  2.5 **Config + deps**: app/config/config.txt gains `b3u_base_url =
      "http://127.0.0.1:8941"` (the §2.8 first target; env
      overrides); requirements.txt gains requests==2.34.2 (the
      contract's own named client shape -- Q-22-B).
  2.6 **Tests** (31 new): tests/test_ccs_port.py (13 -- ABC
      enforcement, value types, error taxonomy, URL precedence, B-4
      redaction), tests/test_ccs_replay.py (11 -- shipped set,
      determinism, content-keying, refuse-loudly, programmatic
      artifacts), tests/test_ccs_client_live.py (7 -- env-gated
      module skipif via is_available(): health, PASS verdict on
      meta.sgr, FAIL-is-a-verdict + scrub, generate zip + identity
      headers, /me local row, wrong-password CCSAuthError, stale-
      token transparent refresh).
  2.7 **scripts/p5_gates.sh** -- the P5 battery, opened here:
      sibling+cppcc presence -> launcher-booted local b3u (scratch
      home, port 8951) -> runbook signup provisioning -> the 7 live
      pytest legs -> the hermetic twin (proves the same file SKIPS
      honestly keyless) -> B-4 grep (the generated password in NO
      gate artifact).
  2.8 **Docs**: design §2.8 realized addendum (the port as
      implemented + Q-22-A); end_to_end §11 U5.1 row done;
      tests/testing.md same-commit update (3 new sections, corpus-pin
      retirement noted, phase overview + totals re-measured).

EXPLICITLY OUT OF SCOPE (each a named later UoW, never silent):
  - wiring Verdict into VerificationRecord / the repair loop's
    oracle_feedback (the T3 tier)                                -> U5.2
  - SCB quartet byte-identity checks                             -> U5.3
  - grammar-aware workspace arcs (compile/validate/regenerate)   -> U5.4
  - driving the port from a foreign workspace                    -> U5.4a
  - the full trinity AP arc, mock AND live                       -> U5.5
  - hosted-b3u.dev auth (API tokens/scopes -- b3u UB.3, d15)     -> later
  - any CLI surface for the CCS plane (no consumer yet; the
    orchestrator wires it at U5.2 where the consumer exists)     -> U5.2

## (3) Verification gates

    G1  Port + values + errors: ABC refuses incomplete
        implementations at instantiation; Verdict/Artifacts frozen;
        every error subclasses CCSError -- hermetic pytest green.
    G2  Replay determinism: same spec bytes -> identical Verdict,
        content-keyed not unit-keyed; every missing fixture refuses
        loudly with the key named; the SHIPPED fixture set loads and
        answers -- hermetic pytest green, zero network.
    G3  LIVE (the U5.1 done-when): against a launcher-booted local
        b3u -- health ok; validate PASS on meta.sgr with empty
        diagnostics; validate FAIL is a VERDICT (never an exception),
        diagnostics non-empty and path-scrubbed; generate delivers a
        real zip + the three identity headers; /me shows the local
        entitlement row; wrong password -> CCSAuthError; a stale
        token survives via ONE transparent re-login. 7/7.
    G4  HONEST SKIPS: the same live file, run keyless, reports 7
        SKIPPED (never a fake pass) -- proven inside the battery as
        its own leg.
    G5  B-4: the generated password appears in NO gate artifact
        (grep leg); repr(client) never carries credentials (pytest).
    G6  NO REGRESSION: full suite 584 passed + 18 skipped = 602
        collected, 0 failed (measured AFTER this file joined the
        own-corpus parametrization) -- including the two corpus-pin tests
        REPAIRED per Q-22-F (the only prior-test delta, a fired
        tripwire, not a regression).
    G7  Hygiene: porcelain after the WORK commit; testing.md moved in
        the same commit as the tests; ledger + design current; gdiff
        via ../scripts/step_gdiff.

## (4) LOCKs

    C-22-A  b3ubot porcelain clean after each commit.
    C-22-B  B-1 BY CONSTRUCTION, kept visible: app/ccs moves bytes
            over HTTP and nothing else -- no cppcc invocation, no CCS
            algorithm, no SCB parsing lands in this package, ever.
    C-22-C  B-4 GENERALIZED TO THE CCS PLANE: b3u credentials are
            environment-only ($B3U_EMAIL/$B3U_PASSWORD) -- never a
            config key, never committed, never logged, never in
            repr(), never echoed into gate output (the p5_gates grep
            leg is the falsifier).
    C-22-D  THE CONTRACT IS THE SEAM: B3uDevClient is written against
            b3u/docs/api_contract.md v1 exclusively -- never imports
            b3u code, never touches an unfrozen operation
            (signup/verify provisioning lives in the GATE script via
            the sibling's own runbook, not in the client).
    C-22-E  LOOPBACK POSTURE (B-2): every committed default and every
            gate target is 127.0.0.1; pointing the client elsewhere
            is a deliberate operator act via $B3U_BASE_URL.
    C-22-F  standing locks inherited: B-3 (local-only, no push), B-5
            (untouched -- the CCS plane is not an AI provider; no
            workspace bytes leave except to the loopback b3u), B-6.

## (5) Open questions

    Q-22-A  deliver() vs contract v1 (DECIDED HERE, recorded as a
            design amendment in §2.8): the sketch's deliver(unit)
            cannot exist against v1 -- generate's response IS the
            delivery and nothing stays at rest server-side. DECIDED:
            generate() returns the Artifacts handle; health() (the
            liveness op a trinity client genuinely needs) takes the
            fourth port slot; a server-side deliver op, if ever
            wanted, is ADDITIVE under the contract's own law.
    Q-22-B  HTTP dependency (DECIDED: requests==2.34.2). The b3u UB.2
            card itself names "requests-based -- the b3ubot
            B3uDevClient's shape"; the sibling's contract client uses
            the same library at the same pin. stdlib urllib rejected:
            multipart uploads by hand are exactly the kind of code
            that grows bugs the contract can't see.
    Q-22-C  Config vs env split (DECIDED): base URL = config key with
            env override (not a secret; per-deployment); credentials
            = env ONLY (B-4). Never the reverse.
    Q-22-D  Live-leg vehicle (DECIDED): a NEW scripts/p5_gates.sh
            booting the SIBLING'S OWN runbook (b3u_local.sh) rather
            than a hand-rolled uvicorn line -- the trinity design
            names the PL runbook as the deployment, so the gate
            proves THAT path; port 8951 avoids the real instance
            (8941) and both siblings' batteries.
    Q-22-E  Replay missing-fixture behavior (DECIDED: refuse loudly,
            CCSFixtureError naming the sha + the next action). A
            synthesized default verdict would be a fake oracle -- the
            one thing an oracle plane must never produce.
    Q-22-F  The fired corpus tripwire (DECIDED HERE): the ==50 pin in
            test_ledger_parser/test_ledger_roundtrip fired because
            b3u grew to 52 steps in this same arc -- exactly the
            re-survey trigger its comment promised. DECIDED: retire
            the exact pin for floor (>=50) + contiguity (1..N) --
            b3u is a LIVE sibling now, gaining a file per step BY
            DESIGN; the per-file parametrized round-trips (which
            immediately covered step.51/52.txt, byte-identical, with
            zero parser changes) keep the oracle's teeth. The
            roundtrip guard renamed accordingly
            (..._covers_the_whole_corpus).

## (6) Acceptance

    - [x] app/ccs package: port + errors + replay + client (2.1-2.4);
          config key + requests dep (2.5).
    - [x] 31 new pytest oracles green (2.6): 24 hermetic + 7 live;
          G1/G2 hermetic, G3 live 7/7.
    - [x] scripts/p5_gates.sh ALL PASS first invocation (2.7): boot,
          provision, live legs, honest-skip twin (G4), B-4 grep (G5).
    - [x] Full suite 584 passed + 18 skipped = 602 collected, 0
          failed (G6); corpus tripwire resolved per Q-22-F.
    - [x] Docs moved in the same commit (2.8): design §2.8 addendum,
          end_to_end U5.1 done, testing.md re-measured (G7).

## (7) Hash backfill

    WORK commit (everything above + this file):   (this commit)
    GDIFF commit (step.22.gdiff.txt):             (HEAD)

End of step.
