Skip to content

fix(cli)(#48): wrapper field-trial fixes — 3-shot bootstrap, UNICITY_ env strip, identical-path bind, post-spawn SET_STRATEGY#50

Open
vrogojin wants to merge 1 commit into
mainfrom
fix/issue-48-followups
Open

fix(cli)(#48): wrapper field-trial fixes — 3-shot bootstrap, UNICITY_ env strip, identical-path bind, post-spawn SET_STRATEGY#50
vrogojin wants to merge 1 commit into
mainfrom
fix/issue-48-followups

Conversation

@vrogojin

Copy link
Copy Markdown
Contributor

Summary

Field-trial fixes for the `sphere trader spawn` wrapper that landed in #49 (closes #48). Three real bugs surfaced when running the trader-roundtrip soak end-to-end:

  1. Two-shot bootstrap was a 3-shot dance. `parseDriftError` derived `managerDirectAddress` from `loadedPubkey` assuming `DIRECT://` — but sphere wallets compute a 36-byte `DIRECT://0000…` address that's NOT the pubkey. Boot 2 failed with MANAGER_DIRECT_ADDRESS mismatch. Fix: loop up to 3 boots, reading whichever mismatch the HM surfaces each time (pubkey on boot 1, direct-address on boot 2), succeed on boot 3.

  2. HM blocks the entire `UNICITY_*` prefix in user-supplied env (`agentic_hosting/src/shared/forbidden-env.ts`) to prevent boot-envelope spoofing. The wrapper was sending `UNICITY_CONTROLLER_PUBKEY`, `UNICITY_NETWORK`, `UNICITY_TRUSTED_ESCROWS` — all rejected with `Forbidden env var prefix in payload.env`. Fix:

    • Strip every `UNICITY_*` from `buildTraderEnv`. The HM auto-injects `CONTROLLER_PUBKEY` (from request sender), `NETWORK`, `BOOT_TOKEN`, `INSTANCE_ID/NAME/TEMPLATE_ID`, `DATA_DIR`, `TOKENS_DIR` itself.
    • `--trusted-escrows` chains to a post-spawn `SET_STRATEGY` ACP command. Best-effort: on failure, prints the manual recovery command.
  3. Docker-in-docker bind-mount trap on the tenants dir. The HM bind-mounts `${TENANTS_DIR}//wallet` into each tenant via the shared docker socket. The daemon resolves the source path on the HOST. If `TENANTS_DIR` is a container-only path, the host doesn't find it, docker creates an empty root:root dir at the missing host path, and the tenant's `node` user (uid 1000) hits EACCES. Fix: bind the host tenants dir into the HM at the IDENTICAL host path, set `TENANTS_DIR` env to that same path. Matches the production agentic_hosting/docker/docker-compose.override.yml pattern.

Also

  • Trader template image bumped `v0.1` → `v0.2` (the rebuilt image from vrogojin/agentic_hosting#26 carrying sphere-sdk #456/#457/#464/#447).
  • Spawn payload defaults `nametag: instance_name` — harmless metadata; the trader-service derives its own `t-` nametag from the instance ID and ignores the HM-supplied field.

Test plan

  • `npx tsc --noEmit` — clean
  • `npm test` — 187 tests pass (29 in local-hm.test.ts including drift-error both-shapes assertion + identical-path-bind invariant; 13 in spawn.test.ts including the no-UNICITY-prefix defense-in-depth check)
  • `npm run build` — tsup ESM + CJS, no errors
  • End-to-end against testnet through §5 of `manual-test-trader-roundtrip.sh`:
    • §1 wallets created ✓
    • §2 faucet (sphere-cli#45 local-mint) ✓
    • §2.5 escrow + market-api ping ✓
    • §3 per-user HM + trader tenants ✓ ← this PR's main field test
    • §4 ACP probe ✓
    • §5 controller→tenant deposits confirmed ✓
    • §6 intent post ✗ — market-api HTTP 500 (server-side rejection of trader's `price=1e17` payload; unrelated to the wrapper, tracked separately)

Related

…rip soak

Surfaced by running the trader-roundtrip soak end-to-end. Three real
bugs in the original sphere-cli#48 wrapper that field-testing exposed:

1. Two-shot bootstrap was a 3-shot dance — `parseDriftError` derived
   `managerDirectAddress` from `loadedPubkey` assuming
   `DIRECT://<pubkey>`, but sphere wallets compute a 36-byte
   `DIRECT://0000…` address that's NOT the pubkey. Second boot now
   fails with MANAGER_DIRECT_ADDRESS mismatch. Fix: loop up to 3 boots,
   reading whichever mismatch the HM surfaces (pubkey on boot 1,
   direct-address on boot 2), succeed on boot 3.

2. HM rejects all `UNICITY_*` keys in user-supplied env (HM blocks the
   prefix to prevent boot-envelope spoofing — see
   agentic_hosting/src/shared/forbidden-env.ts). The wrapper was
   sending UNICITY_CONTROLLER_PUBKEY, UNICITY_NETWORK,
   UNICITY_TRUSTED_ESCROWS — all rejected. Fixes:
     * Strip every UNICITY_* from `buildTraderEnv`. The HM auto-injects
       CONTROLLER_PUBKEY (from request sender), NETWORK, BOOT_TOKEN,
       INSTANCE_ID/NAME/TEMPLATE_ID, DATA_DIR, TOKENS_DIR itself.
     * `--trusted-escrows` chains to a post-spawn SET_STRATEGY ACP
       command. Best-effort: on failure, instructs the operator how to
       re-apply manually.

3. Docker-in-docker bind-mount trap on the tenants dir. The HM
   bind-mounts `${TENANTS_DIR}/<instance>/wallet` into each tenant via
   the shared docker socket; the daemon resolves the source path on
   the HOST. If TENANTS_DIR is a container-only path, the host doesn't
   find it, docker creates an empty root:root dir at the missing host
   path, and the tenant's `node` user (uid 1000) hits EACCES. Fix:
   bind the host tenants dir into the HM at the IDENTICAL host path,
   and set `TENANTS_DIR` env to that same path. Matches the production
   `agentic_hosting/docker/docker-compose.override.yml` pattern.

Also:
- Trader template image bumped v0.1 → v0.2 (the rebuilt image that
  carries sphere-sdk#456/#457/#464/#447).
- Spawn payload defaults `nametag: instance_name` — the trader-service
  ignores this and derives its own `t-<hex>` from the instance ID, but
  the field is harmless metadata and matches existing host-spawn UX.

187 unit tests pass (29 in local-hm.test.ts including parse-drift
both-shapes, identical-path-bind assertions; 13 in spawn.test.ts
including the no-UNICITY-prefix defense-in-depth check).

Verified end-to-end against testnet through §5 of
sphere-sdk#475's `manual-test-trader-roundtrip.sh`:
  §1 wallets created           ✓
  §2 faucet (local-mint)       ✓
  §2.5 escrow + market ping    ✓
  §3 per-user HM + tenants     ✓ (3-shot bootstrap + identical-path bind)
  §4 ACP probe                 ✓
  §5 deposits confirmed        ✓
  §6 intent post               ✗ (market-api HTTP 500 — server-side
                                  rejection of trader's price=1e17
                                  payload, unrelated to wrapper)

Follow-up sphere-cli#48 wrapper: defaults templates to trader:v0.2
match agentic_hosting templates.json (PR #27); needs another bump
when v0.3 lands.
vrogojin added a commit to unicity-sphere/sphere-sdk that referenced this pull request Jun 11, 2026
…rough (#482)

Working end-to-end through §5 of the trader-roundtrip soak surfaced
three CLI-form mismatches in the script:

1. `extract_chain_pubkey` looked for `chainPubkey:` (camelCase, the
   `sphere init` output shape) but `sphere status` emits `Chain Pubkey:`
   (space-separated, human-label shape). The regex matched neither, the
   extract function returned empty, and `[[ -n $ALICE_PUBKEY ]]`
   failed the script at line ~580. Fix: case-insensitive regex with
   optional internal whitespace `(chain[[:space:]]*pubkey)` matches
   both output formats.

2. §3 hardcoded the tenant nametag as the instance name
   (`$ALICE_TRADER_TAG = alice-trader-$SUFFIX`). The trader-service
   actually derives its own nametag as `t-<18-hex>` from the instance
   ID (see trader-service/src/trader/main.ts:279). Result: every
   downstream `sphere trader ... --tenant '@$ALICE_TRADER_TAG'`
   landed at the wrong nametag. Fix: parse the live
   `tenant_direct_address` from `sphere trader spawn --json` output
   and re-bind `ALICE_TRADER_TAG` to that value (sans leading `@`).
   All existing downstream usages then resolve correctly.

3. §5 deposit step had the args in the wrong order — `sphere payments
   send 50 UCT --to "@$ALICE_TRADER_TAG"` — but the current CLI
   surface is `sphere payments send <recipient> <amount> <coin>`
   (recipient first, no `--to` flag). Fix: swap args to match CLI.

Verified end-to-end against testnet (with sphere-cli#50 follow-up
wrapper fixes applied):
  §1 wallets created             ✓
  §2 faucet (local-mint)         ✓
  §2.5 escrow + market pre-flight ✓
  §3 per-user HM + trader spawn  ✓
  §4 ACP probe                   ✓
  §5 deposits confirmed          ✓
  §6 intent post                 ✗ (market-api HTTP 500 — out of
                                    scope here; price=1e17 payload
                                    rejected at the api server)

Related: unicity-sphere/sphere-cli#50 (wrapper follow-up fixes
that this soak run surfaced).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sphere trader local-spawn / local-stop: ergonomic launch of per-user local HM + trader tenant

1 participant