agentkeys: §10.2 agent-initiated pairing (method A) — flip from master-issued link codes#159
Merged
Merged
Conversation
Flip the agent bootstrap from master-initiated (link code) to agent- initiated (the agent shows a code, the master claims it — the Matter/ HomeKit IoT model). Replaces #149's master-mint front-half; reuses the on-chain bind + scope tail unchanged. Broker: - NEW storage/pairing_requests.rs — unbound, agent-created request pool (issue/claim/poll/pending_bindings/mark_bound/purge). J1 is NOT stored at rest; minted fresh at poll time on a re-proved pop_sig. - NEW handlers/agent/request.rs (agent, pop_sig-gated) — open an unbound request, return {request_id (secret), pairing_code (display)}. - NEW handlers/agent/claim.rs (master, J1-gated) — claim by code, derive O_agent=HDKD(O_master,//label), record pending binding. - NEW handlers/agent/poll.rs (agent, pop_sig-gated) — once claimed, mint + return J1_agent. - REMOVE handlers/agent/{create,redeem}.rs + storage/link_codes.rs. - Rename link_code_store -> pairing_request_store across state/boot/main. - Rewire routes: /v1/agent/pairing/{request,claim,poll}; keep pending-bindings + /ack (now keyed by request_id). Tests: 14 store unit tests + agent_bootstrap_flow rewritten for the request->claim->poll flow (5 cases incl. cross-device/bad-pop_sig poll rejection). clippy --all-features --all-targets -D warnings clean. Unbind/factory-reset re-pair deferred -> #156; on-chain self-revoke -> #155.
…ost smoke Flip the client + wire harness to agent-initiated pairing (issue #144, method A), matching the broker request/claim/poll endpoints. Daemon (--init-link-code → two one-shots mirroring the two endpoints): - --request-pairing: in-sandbox K10 keygen → POST /v1/agent/pairing/request → print {request_id, pairing_code, …}; persist a 0600 state file so --retrieve-pairing can resolve request_id (--request-id overrides). - --retrieve-pairing: poll /v1/agent/pairing/poll until claimed (bounded by --init-poll-timeout-seconds), mint+persist J1_agent (0600), emit artifact. CLI: agent create → agent claim --pairing-code <code> --label … --services … (POST /v1/agent/pairing/claim). agent pending unchanged (rows now keyed by request_id). Harness phase1-wire-demo.sh Phase P inverts: P.0 agent --request-pairing (shows code) → P.1 master agent claim → P.1b agent --retrieve-pairing (J1) → P.1c pending → P.2 bind + ack-by-request_id → P.3 grant. P.depair unchanged. 404 trap + route names updated. setup-broker-host.sh (runbook-fix-fold-back): nm symbol grep → pairing_{request,claim,poll}|pending_bindings; route smoke → no-bearer POST /v1/agent/pairing/claim must be 401 not 404. cli+daemon: clippy --all-targets -D warnings clean, fmt clean, tests pass (38/38 single-threaded; the 1 parallel-suite failure is a pre-existing k11 enroll test race on a shared HOME path, unrelated). bash -n both scripts OK.
…ooks) Reconcile every doc + code-comment surface with the agent-initiated pairing flip (issue #144, method A). arch.md (single source of truth): - §10.2 ceremony fully rewritten for method A (agent requests → master claims → agent retrieves), incl. the IoT/Sybil-safety rationale + the deferred unbind notes (#155/#156). - §6.2 route list: /v1/agent/pairing/{request,claim,poll} replace create + link-code/redeem; pending-bindings ack now by request_id. - §10.4 re-bootstrap inverted; §5 agent_omni row, §10.6 threat row, trust-boundary + actor-role tables, CLI inventory → pairing terms. Solidity link_code_redemption calldata param kept (contract unchanged). operator-runbook-wire.md: Phase P walkthrough (P.0 request → P.1 claim → P.1b retrieve → P.1c pending → P.2 bind+ack → P.3 grant), 404 trap + route checks, troubleshooting rows. v2-stage1-migration-and-demo.md §7: rewritten for method A (also fixes pre-existing drift — the master, not the broker, submits registerAgentDevice). issue-144 plan: superseded-front-half banner → method-A doc. issue-74 ephemeral-rebootstrap paragraph corrected. Code doc-comments (mcp-server config, core actor_omni, cli device_session) → pairing terms. fmt + clippy --all-targets -D warnings clean on the 3 comment-edited crates.
…owing untracked files)
The --ref deploy path ran a plain `git checkout $PULL_REF`, which ABORTS
when an untracked working-tree file shadows a file the target ref tracks
("untracked working tree files would be overwritten by checkout" — hit on
the broker host when switching to a branch that tracks docs/wiki/*.md the
prior branch did not). The broker host is a deploy target, not a dev
checkout: -f overwrites the colliding files with the tracked version +
discards local edits to TRACKED files, while LEAVING unrelated untracked
files (env, keys, certs — all gitignored) intact. Folds the fix back into
the deploy runbook so the next operator does not hit the same abort.
hanwencheng
added a commit
that referenced
this pull request
Jun 1, 2026
…al memory After merging #159 (§10.2 agent-initiated pairing, method A — which resolves pushback #1 upstream), wire the two genuinely-real pieces the user asked for. Plan + status: docs/plan/web-flow/issue-9step-flow.md. Daemon (ui_bridge.rs) — master memory, real + idempotent: - ApiMemoryEntry + master_memory store; content_hash = sha256(ns ‖ key ‖ body). - GET /v1/master/memory — list (sorted by ns/key). - POST /v1/master/memory/plant — idempotent: dedup by content_hash → {planted, skipped, total}; emits a memory.write audit row when something lands. - dev_seed extended with master_memory (seeds the "already has memory" path). - 3 new unit tests (empty / plant→replant-dedup / changed-body-adds-entry); 23 ui_bridge tests pass. Adds sha2 dep. Client seam (lib/client) — new MasterMemoryEntry/PlantResult + listMasterMemory() + plantMemory(): DaemonBackend hits the real endpoints; EmptyBackend stays disconnected (offline → seed fallback in the UI). UI: - OnboardingScreen (ceremony.tsx): real navigator.credentials.create() via the client (/v1/k11/enroll/{begin,finish}, PR-B) when a daemon is configured — shows a "K11 enrolled · real WebAuthn" chip; narrated fallback offline. No longer a pure setTimeout fake. - MemoryPage wiring (App.tsx): auto-detect existing memory on load (listMasterMemory → hides the plant button); plant calls plantMemory (server dedups) then re-lists; seed fallback when disconnected. The dedup guard is now enforced both client-side AND server-side. - pairing.tsx copy aligned to method A (agent shows a code → master claims it), matching #159. Functional claim-input + daemon pairing-proxy is the next step (needs the broker reachable). Pushback status: #1 resolved by #159; #2 implemented here; #3 (audit decode) remains a mock tracked in #153 (per the user's "just 2" scope). Verified: cargo test -p agentkeys-daemon ui_bridge — 23/23 · npx tsc --noEmit clean · npm run build ok (21.2 kB route) · dev smoke renders onboarding, no runtime errors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Flips the §10.2 agent bootstrap from master-initiated (#149: the master mints a link code, the agent redeems it) to agent-initiated (method A: the agent shows a one-time pairing code, the master claims it). This is the Matter/HomeKit IoT model and the only structurally-correct one for a no-keyboard device — an AI companion in a box can show a QR/setup code but can't accept a master-minted code typed into it.
It stays Sybil-safe: the pairing request is unbound (names no master) and inert until a master deliberately claims the code — the master's claim is the sole binder, exactly as the master's mint was under #149. Reuses #149's on-chain bind + scope tail unchanged (the broker still never writes chain; the master submits
registerAgentDevice).Design doc:
docs/spec/plans/agent-initiated-pairing-method-a.md. Supersedes the front-half of #149 (banner added todocs/spec/plans/issue-144-hdkd-agent-bootstrap.md).What landed
Broker (
crates/agentkeys-broker-server)storage/pairing_requests.rs— unbound, agent-created request pool.J1_agentis not stored at rest; it's minted fresh at poll time on a re-provedpop_sig, so no bearer secret sits in SQLite and the JWT TTL starts at retrieval.handlers/agent/{request,claim,poll}.rs; removed{create,redeem}.rs+storage/link_codes.rs.POST /v1/agent/pairing/{request,claim,poll}replacecreate+link-code/redeem.pending-bindings+/ackkept (now keyed byrequest_id).link_code_store→pairing_request_storeacrossstate.rs/boot.rs/main.rs.Daemon (
crates/agentkeys-daemon/src/main.rs)--init-link-code→ two synchronous one-shots mirroring the two endpoints:--request-pairing(keygen in sandbox → open request → display code; writes a 0600 state file) and--retrieve-pairing(poll-until-claimed, bounded by--init-poll-timeout-seconds; mint + persistJ1_agent0600; emit binding artifact).CLI (
crates/agentkeys-cli)agent create→agent claim --pairing-code <code> --label … --services ….agent pendingunchanged.Harness (
harness/phase1-wire-demo.sh)--request-pairing(shows code) → P.1 masteragent claim→ P.1b agent--retrieve-pairing→ P.1c pending → P.2 bind + ack-by-request_id→ P.3 grant.P.depairunchanged.scripts/setup-broker-host.sh: updated both deploy smokes (nm symbol grep + the no-bearer-401 route probe →/v1/agent/pairing/claim).Docs —
arch.md§10.2 rewritten (+ §6.2 routes, §10.4, §5/§10.6 tables, trust boundaries, CLI inventory);operator-runbook-wire.mdPhase P walkthrough;v2-stage1-migration-and-demo.md§7 (also fixed pre-existing "broker submits register" drift). The Soliditylink_code_redemptioncalldata param is kept (contract unchanged).Out of scope (filed)
Verification
agent_bootstrap_flow, incl. cross-device / bad-pop_sig poll rejection).cargo fmt --all --checkclean;cargo clippy --workspace --all-targets --all-features -- -D warningsclean.bash -nonphase1-wire-demo.sh+setup-broker-host.sh.phase1-wire-demo.sh --real --webauthn(can't drive sandbox+broker locally) — that's the behavioral gate after deploy.Deploy note
The broker binary changed (new routes), so the broker host must be redeployed before the wire demo:
sudo bash scripts/setup-broker-host.sh --ref claude/agent-initiated-pairing(no AWS/setup-cloud.shchange — no infra touched).🤖 Generated with Claude Code