Goal
Fold the master device on-chain registration into the web onboarding flow so that every master, on login, gets its K10 device registered on SidecarRegistry with the CAP_MINT role — and un-stub chain_tx_hash. After this, login → (register happens) → the existing memory plant button works end-to-end with no manual bootstrap.
Builds on (merge first): #191 (daemon real memory chain) + #195 (master-self scope skip). With both merged, on-chain master-device registration is the only remaining gap to a live web plant→S3 demo.
Why it's required (irreducible — it is NOT the scope check #195 removed)
Real cap-mint has two separate on-chain checks; #195 only removed the scope one:
Both read the on-chain device by device_key_hash. No on-chain device → rejected at both layers. So the master device MUST be registered on-chain for real memory/credential ops. #195 removed the scope requirement (operator==actor skip); it did not remove the device requirement — they're different checks.
The one open design decision: msg.sender / key custody
registerFirstMasterDevice sets operatorMasterWallet[omni] = msg.sender. Under the W1 managed-wallet model the daemon does not hold the master's EVM key (the signer derives K4 = HKDF(K3, O_master)), so msg.sender is ambiguous:
- (α) old-model / dev — RECOMMENDED for this issue.
OPERATOR_KEY_FILE is msg.sender (exactly what scripts/heima-register-first-master.sh + the wire demo do). Works today. operatorMasterWallet[omni] = a local key, not the managed wallet — fine for cap-mint (which checks the device, not msg.sender); the divergence only matters for future master mutations (scope grants check msg.sender == operatorMasterWallet).
- (β) managed-wallet. The signer signs the tx — but its surface is
/sign/siwe-style payloads, not arbitrary EVM txs; likely needs new signer work.
- (γ) ERC-4337 (clean end-state). This is W2 / chain-plan E7 — currently cutover-blocked (the thinned account-auth registry isn't deployed to mainnet). Out of scope here; this issue is the old-model CLI interim path.
Point-1 subsidy (folded in — local, NOT broker)
The register tx needs gas. Add a local operator-run script scripts/heima-fund-master.sh: deployer → master account, ~0.2 HEI, idempotent (skip if balance ≥ threshold). Deliberately NOT a broker endpoint and NOT auto-on-login — broker auto-funding every login is a Sybil drain on the deployer. The operator runs it once.
device_key_hash plumbing
The daemon must register its own K10 (the same hash cap-mint will send) — see docs/plan/web-flow/w3-real-memory.md §3.5. #191 added --master-device-key-hash as an explicit stopgap; this issue should make the daemon register + use its own K10 consistently (derive the hash from the daemon's K10, register that, cap-mint with that).
Implementation pointers
- The stub to replace:
crates/agentkeys-daemon/src/ui_bridge.rs — the K11-finish handler's chain_tx_hash TODO (search chain_tx_hash / register_master_device; currently returns None).
- Onboarding flow:
ui_bridge.rs onboarding_session + GET /v1/onboarding/state (extend to report chain: master-registered).
- Shell-out pattern (sanctioned):
wire-real-paths.md §4.2 — the daemon master plane shells out to scripts/heima-*.sh. Use scripts/heima-register-first-master.sh (old-model cast).
- Plans:
docs/plan/web-flow/wire-real-paths.md (W2 = §6), docs/plan/web-flow/w3-real-memory.md (§3.5 device-key constraint, §4 bootstrap), docs/plan/chain/erc4337-master-account.md (E7 — the clean end-state + why cutover-blocked).
Acceptance
Out of scope
- ERC-4337 web-native register (W2/E7) — cutover-blocked; the eventual clean replacement for the old-model shell-out here.
Goal
Fold the master device on-chain registration into the web onboarding flow so that every master, on login, gets its K10 device registered on
SidecarRegistrywith theCAP_MINTrole — and un-stubchain_tx_hash. After this,login → (register happens) → the existing memory plant button works end-to-endwith no manual bootstrap.Builds on (merge first): #191 (daemon real memory chain) + #195 (master-self scope skip). With both merged, on-chain master-device registration is the only remaining gap to a live web plant→S3 demo.
Why it's required (irreducible — it is NOT the scope check #195 removed)
Real cap-mint has two separate on-chain checks; #195 only removed the scope one:
crates/agentkeys-broker-server/src/handlers/cap.rs:device.actor_omni == req.actor_omnianddevice.roles & CAP_MINT.crates/agentkeys-worker-creds/src/verify.rs::check_chain_device(issue-v2 stage 2 — Hardening: K11 WebAuthn + multi-device recovery + audit/memory/email workers #90 layer-2 defense-in-depth against a compromised broker).Both read the on-chain device by
device_key_hash. No on-chain device → rejected at both layers. So the master device MUST be registered on-chain for real memory/credential ops. #195 removed the scope requirement (operator==actorskip); it did not remove the device requirement — they're different checks.The one open design decision:
msg.sender/ key custodyregisterFirstMasterDevicesetsoperatorMasterWallet[omni] = msg.sender. Under the W1 managed-wallet model the daemon does not hold the master's EVM key (the signer derives K4 =HKDF(K3, O_master)), somsg.senderis ambiguous:OPERATOR_KEY_FILEismsg.sender(exactly whatscripts/heima-register-first-master.sh+ the wire demo do). Works today.operatorMasterWallet[omni]= a local key, not the managed wallet — fine for cap-mint (which checks the device, notmsg.sender); the divergence only matters for future master mutations (scope grants checkmsg.sender == operatorMasterWallet)./sign/siwe-style payloads, not arbitrary EVM txs; likely needs new signer work.Point-1 subsidy (folded in — local, NOT broker)
The register tx needs gas. Add a local operator-run script
scripts/heima-fund-master.sh: deployer → master account, ~0.2 HEI, idempotent (skip if balance ≥ threshold). Deliberately NOT a broker endpoint and NOT auto-on-login — broker auto-funding every login is a Sybil drain on the deployer. The operator runs it once.device_key_hash plumbing
The daemon must register its own K10 (the same hash cap-mint will send) — see
docs/plan/web-flow/w3-real-memory.md§3.5. #191 added--master-device-key-hashas an explicit stopgap; this issue should make the daemon register + use its own K10 consistently (derive the hash from the daemon's K10, register that, cap-mint with that).Implementation pointers
crates/agentkeys-daemon/src/ui_bridge.rs— the K11-finish handler'schain_tx_hashTODO (searchchain_tx_hash/register_master_device; currently returnsNone).ui_bridge.rsonboarding_session+GET /v1/onboarding/state(extend to reportchain: master-registered).wire-real-paths.md§4.2 — the daemon master plane shells out toscripts/heima-*.sh. Usescripts/heima-register-first-master.sh(old-modelcast).docs/plan/web-flow/wire-real-paths.md(W2 = §6),docs/plan/web-flow/w3-real-memory.md(§3.5 device-key constraint, §4 bootstrap),docs/plan/chain/erc4337-master-account.md(E7 — the clean end-state + why cutover-blocked).Acceptance
GET /v1/onboarding/statereportschain: master-registered;chain_tx_hashis a real tx hash (un-stubbed), idempotent (skip-if-already-registered).heima-fund-master.sh, the existing W3: real memory chain in the daemon (cap-mint → STS → worker → S3, master-self) #191 memory plant button writes to S3bots/<O_master>/memory/memory:<ns>.encwith no manual CLI bootstrap.harness/v2-stage3-demo.shto assert (a) a master-self cap mints with no scope grant, (b) a cross-actor cap still returnsServiceNotInScope. The live register makes this runnable.Out of scope