step.29.txt -- b3ubot U5.7: THE MULTI-HOME DISCIPLINE + the second
               real account -- scripts/b3ubot_profile.sh, the
               per-account profile wrapper that binds a data home
               (~/.b3ubot.N via $B3UBOT_HOME) to the credentials
               living INSIDE that home (~/.b3ubot.N/profile.env,
               0600-enforced), so two staging accounts coexist on one
               dev box with NO invocation surface for mis-pairing;
               plus the operator ceremony that creates the plan's A3
               actor (actor@example.com, real web signup + 4242
               checkout) and proves each profile serves its OWN
               account. Closes the lifecycle plan's TC-3 / TC-14 /
               TC-15 / TC-16. (p1.1.4) of the user's (p1.1) plan.

Step:     29 -- promote UoW **U5.7** (added BY AMENDMENT via the
                engine's own `ledger add-row`, the U5.4a/U5.6
                precedent) to execution.
Parent:   design.md §2 P5 (the CCS trinity); b3u
                docs/test_plan_account_lifecycle.md §6 (TC-14..16,
                pre-named at UB.5 for exactly this step) + TC-3.
UoW:      U5.7 -- "Multi-home profiles + the A3 account".
Date:     2026-07-24
Origin:   User directive 2026-07-24: "proceed with (p1.1.4)" -- per
                the (p1.1) plan of 2026-07-23: sign up
                actor@example.com with the same 4242 card; resolve
                multi-account-on-one-host via ~/.b3ubot.1 (alex) and
                ~/.b3ubot.2 (actor).

## (0) Status

DRAFTED for same-session execution. Phase A (wrapper + hermetic
tests + gates) is machine work; Phase B (the A3 browser signup +
checkout, the profile.env files -- they hold the operator's real
passwords -- and the two-profile witness) is an OPERATOR ceremony.
The step stays open until the operator declares it and the gates
agree (C-27-B/C-53-C: a machine may not declare a human-witnessed
gate green).

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

    EXISTS ($B3UBOT_HOME): the engine-wide data-home override --
            app/egress.py:31 `Path(os.environ.get("B3UBOT_HOME",
            "~/.b3ubot")).expanduser()`; persistence.py names the
            same convention for per-project engine state; every
            engine artifact (orchestrator/, runs/, *.jsonl) lives
            under it. ~/.b3ubot.1 EXISTS (the U5.6 remote-ceremony
            home, UC.1 closed inside); ~/.b3ubot.2 ABSENT.
    EXISTS (account identity): B3uDevClient reads $B3U_EMAIL /
            $B3U_PASSWORD (env-only, B-4) and the CF Access pair
            $CF_ACCESS_CLIENT_ID/_SECRET (C-28-B) at __init__; the
            plane serves WHOEVER the creds name -- identity follows
            the environment, NOT the home. That is exactly TC-16's
            hazard: A1's home + A3's creds would silently serve A3.
    EXISTS (creds-file conventions): ~/.b3u_staging_creds.env +
            ~/.b3u_cf_smoke.env (0600, outside all repos, sourced by
            the operator; remote_gates.sh REFUSES naming missing
            vars). The CF service token is PER-HOST wall admission,
            not per-account identity -- shareable across profiles.
    EXISTS (the ceremony surface): `b3ubot ccs status` -- plane
            health + THIS account's entitlement row, no args -- the
            exact per-profile witness op. POST /billing/checkout
            (b3u main.py:522, outside the frozen contract v1) is the
            F-2 shell checkout path used at the UB.4 ceremony:
            login -> checkout -> {url} -> browser -> 4242.
    EXISTS (the plane, per the lifecycle plan's log): 2 accounts --
            operator@example.com (A1, sub active) +
            actor-clock@example.com (A2, canceled/expired);
            actor@example.com (A3) ABSENT. Same-card facts asserted
            by plan §3 (email is the PK; one card backs many
            customers).
    ABSENT: any profile wrapper; any per-home creds file; any test
            touching $B3UBOT_HOME-vs-identity pairing.

## (2) What U5.7 delivers

  A. scripts/b3ubot_profile.sh <N> [command...] -- the wrapper:
       - resolves HOME_DIR = ${B3UBOT_PROFILE_BASE:-$HOME}/.b3ubot.N
         (the PROFILE_BASE override exists for hermetic tests ONLY);
         N is digits-only -- there are NO flags, NO creds arguments
         (C-29-B: mis-pairing gets no invocation surface);
       - REFUSES (exit 2, values never echoed): missing
         profile.env; permissions other than 0600; profile.env
         that sets no B3U_EMAIL or no B3U_PASSWORD (names the KEY);
       - sources ${B3UBOT_CF_ENV:-~/.b3u_cf_smoke.env} if present
         (wall admission, per-host) THEN $HOME_DIR/profile.env
         (identity, per-account -- sourced LAST so the profile wins
         every overlap, including a polluted outer environment);
       - exports B3UBOT_HOME=$HOME_DIR and execs the command; with
         no command prints a non-secret summary (home, email, base
         URL, set-flags -- the password value NEVER).
  B. tests/test_profile_wrapper.py -- hermetic subprocess tests
     (scratch B3UBOT_PROFILE_BASE + B3UBOT_CF_ENV, fake creds):
     refusals (missing file / loose perms / missing key / flag-like
     or non-numeric N), home export, profile-overrides-outer-env,
     two-profile isolation, summary-never-prints-password, CF pair
     sourced when present / absent-file OK.
  C. scripts/multihome_gates.sh -- Phase A: legs U (the refusal
     battery, live), ISO (two scratch profiles -> distinct homes +
     each child sees its OWN identity, outer-env pollution
     overridden), PY (the new test file, hermetic), B4 (no scratch
     secret appears in any gate artifact). Boots nothing, touches
     no real plane (C-28-C); green prints the Phase B ceremony.
  D. Phase B -- the OPERATOR ceremony (TC-3 + TC-15 + TC-16 live):
       1. ~/.b3ubot.1/profile.env (alex creds + staging base), 0600;
       2. browser signup actor@example.com on staging -> gmail
          verify link -> shell checkout THROUGH THE WRAPPER
          (profile 2's env supplies creds + CF; F-2 path) -> 4242;
       3. ~/.b3ubot.2/profile.env (actor creds), 0600 -- written
          BEFORE the checkout so the wrapper itself runs it;
       4. `b3ubot_profile.sh 1 ... ccs status` -> alex's account;
          `b3ubot_profile.sh 2 ... ccs status` -> actor's;
          the polluted leg: alex creds SOURCED in the outer shell,
          profile 2 still serves actor (TC-16 witnessed live).
  E. TC-14 census (machine, ONE ssh -- the step.53 law): droplet
     accounts + modeled clients + Stripe customers/subs + default-pm
     card FINGERPRINTS -- A1/A3 fingerprints EQUAL (same card),
     customers/subs distinct, A1+A2 state untouched by A3's
     arrival. Ad-hoc heredoc this step; promotion candidate named
     (Q-29-C).
  F. Ledger: U5.7 by amendment at SKELETON (engine add-row), done at
     close (engine ledger close). b3u lifecycle plan §6 flips ride
     b3u commits as UB.5 plan execution (the LIVE-doc charter), NOT
     a new b3u step. Zero engine-code changes (C-29-C).

## (3) Verification gates

    G-A (Phase A, machine): multihome_gates.sh ALL PASS; full suite
        green, measured AFTER the last edit; [T1:pytest].
    G-B (Phase B, ceremony): both profiles serve their OWN accounts
        via ccs status (distinct client/email identity), including
        the polluted-outer-env leg; closed by legs + the operator's
        explicit word (C-27-B/C-53-C), never by the machine alone.
    G-C (the plane): TC-14 census -- 3 accounts / 3 customers, A1
        vs A3 same card fingerprint, distinct subscriptions, no
        cross-talk; alex + clockco rows byte-stable vs the UB.5 log.
    G-D (the plan): TC-3/TC-14/TC-15/TC-16 flipped with dated
        execution-log entries in the b3u lifecycle plan, same-commit
        as the runs, citing this step as evidence.

## (4) LOCKs

    C-29-A  profile.env is a CREDENTIAL file: 0600 enforced by the
            wrapper, values never echoed by wrapper/gates/tests;
            real files live under ~/.b3ubot.N/ outside all repos;
            tests use fake creds under scratch dirs only.
    C-29-B  identity comes from INSIDE the home: the wrapper takes
            no creds argument, no env-file argument, no flags --
            the home IS the account selector; profile.env is
            sourced LAST so it beats any outer environment.
    C-29-C  zero engine-code changes: the wrapper CONSUMES the
            existing $B3UBOT_HOME + env-only-creds contracts
            (egress.py, client.py); nothing under app/ changes.
    (inherited) B-4 / C-28-B env-only credentials; C-28-C gates
            boot nothing; C-27-B/C-53-C operator-closed ceremony;
            C-45-B never-push; one-ssh-call law (b3u step.53).

## (5) Open questions

    Q-29-A  A3's client name: the operator picks it at signup (the
            plan's "TBD at (p1.1.4)"). LEAN: uafco. Recorded in the
            plan §2 at ceremony close.
    Q-29-B  Where does B3U_BASE_URL live? LEAN: in profile.env,
            per-profile and explicit -- the wrapper defaults
            NOTHING (absent base = the client's own loopback
            default), so the same wrapper drives local-mode
            profiles unchanged.
    Q-29-C  The TC-14 census: ad-hoc one-ssh heredoc this step;
            promotion candidate `b3u scripts/account_census.sh`
            (purpose: population snapshot as the plane grows;
            rationale: fresh_start_check's one-account claim is
            already superseded) -- deferred to the UB.6-era when
            account management becomes product work.

## (6) Acceptance

    [x] Wrapper + tests + gates landed; suite green measured
        after the last edit                               (G-A)
    [x] Phase A ALL PASS, recorded in §(7)                (G-A)
    [x] Phase B ceremony: A3 live (signup/verify/4242);
        two profiles serve two accounts; polluted-env leg;
        operator's word                                   (G-B)
    [x] TC-14 census: same card, no cross-talk, A1/A2
        untouched                                         (G-C)
    [x] Plan flips TC-3/14/15/16 committed b3u-side       (G-D)
    [x] Ledger U5.7 active at SKELETON, done at close;
        zero engine-code changes                          (F, C-29-C)

## (7) Hash backfill

    SKELETON commit: 66f8d6b
    WORK     commit: 4e92036 -- suite 660+28=688 measured after the
                     last edit (644 baseline + 12 wrapper tests + 4
                     corpus pickups of this file). Two live-fire
                     findings AT BUILD: (1) GNU env's -u flags must
                     precede NAME=VALUE assignments or env execs
                     "-u" as the command; (2) the corpus round-trip
                     test caught this skeleton's INTERNAL banner
                     wrapped off the ledger emitter's canonical
                     form -- the suite guards even the step files.
    Phase A  run:    2026-07-24, ALL PASS first run (commit b8c3797)
                     -- U refusal battery 5/5 (no-arg, flag-like,
                     missing profile.env, 0644 perms, keyless), ISO
                     2/2 (distinct homes/identities; polluted outer
                     env overridden -- TC-15/TC-16 mechanics), PY
                     12/12 hermetic, B4 clean.
    Phase B  close:  2026-07-24, operator-run and operator-reported
                     (the pasted legs ARE the operator's word,
                     C-27-B satisfied). A3 born live: real signup
                     actor@example.com / client uafco + gmail
                     verify; checkout run THROUGH profile 2's own
                     wrapper env (~/a3_checkout.sh one-shot, the
                     Q-28-E no-paste-hazard pattern -- deleted
                     after); Stripe Link intercepted with the
                     test-mode 000000 code; sub_1Two8X.. active.
                     Witness legs: profile 1 -> alex_urakhchin/
                     betthreetesting active; profile 2 -> actor/
                     uafco active; polluted leg (alex creds sourced
                     in the outer shell) STILL served uafco -- TC-16
                     live. TWO ceremony live-fire findings: (a) the
                     chat paste channel injected a newline inside a
                     curl -H header (Q-28-E third edition -- the
                     one-shot script file is now the DEFAULT
                     ceremony vehicle, not the fallback); (b) the
                     helper's f-string \" escapes broke the SYSTEM
                     python3 (pre-3.12 rules) while the venv's 3.12
                     accepted them -- ceremony helpers must target
                     the oldest python on the box ("%s" formatting).
    Census   run:    2026-07-24 (TC-14, one-ssh x2 + cleanup):
                     3 accounts / 3 modeled clients; alex active
                     (period_end 2026-08-23), clockco canceled
                     (2026-09-23), uafco active (2026-08-24);
                     Stripe customers distinct, subs isolated;
                     A1/A2 byte-stable vs the UB.5 log. Nuances:
                     clock-backed customers are EXCLUDED from
                     default Customer.list (A2 visible only via its
                     clock -- census must query per-clock); newer
                     Stripe API has no invoice.charge (use
                     Charge.list(customer=..)); Stripe LINK hides
                     last4/fingerprint on A1+A3 charges -> the
                     same-card claim rests on the operator-witnessed
                     4242 entries, with A2's raw-card charges (4242,
                     one fingerprint) as the control (plan F-6);
                     one ORPHAN 0-customer lifecycle-plan clock
                     (step.54's failed-run leftover -- its recorded
                     deletion evidently hit the wrong twin) found
                     and deleted. b3u plan flips: commit f740edb.
    RETRO    commit: (retro pair -- see step.29.diff.txt)
