Skip to content

feat: #275 — plant parity dissolved into the type system (wasm tier-3) + #215 B1/B2 re-land#293

Merged
hanwencheng merged 3 commits into
mainfrom
claude/trusting-burnell-571e83
Jun 13, 2026
Merged

feat: #275 — plant parity dissolved into the type system (wasm tier-3) + #215 B1/B2 re-land#293
hanwencheng merged 3 commits into
mainfrom
claude/trusting-burnell-571e83

Conversation

@hanwencheng

Copy link
Copy Markdown
Member

Summary

Resolves #275 (the phase-6 web-parity tier-3 move) and supersedes #215 — that PR's B1/B2 are the substrate this builds on, but its branch rotted unmergeable (56 commits behind; protocol.rs +256 lines under the file move, ui_bridge.rs +5403/−873), so the content is re-implemented fresh from current main in three commits. The design decisions from #215's Codex adversarial review survived unchanged.

The arc: push the daemon↔frontend plant contract down the parity ladder (harness/AGENTS.md "Parity/wiring checks evolve down a ladder") until the runtime check is only the irreducible wiring smoke.

Commit 1 — B1 re-land: agentkeys-protocol (wasm-safe wire crate)

Commit 2 — B2 re-land, extended: ts-rs generates the frontend wire types

  • ts-rs derives on the daemon's ui_bridge Api* structs generate apps/parent-control/lib/generated/*.ts; daemon.ts imports them and all 8 hand-declared wire interfaces are deleted. A Rust-side field rename is now a frontend compile error (rung 2 → 3). u64#[ts(type = "number")], skip-serialize Options → #[ts(optional)].
  • Extended past refactor: #203 follow-up — agentkeys-protocol wire crate (wasm-safe) + ts-rs frontend bindings #215's scope (its own flagged follow-up): ProposedScope + the catalog Sensitivity generate too (with a new ScopeGating enum replacing the bare &'static str, so TS gets the "auto" | "k11" union), and the protocol WireUserOp / BuildAcceptUserOpResponse / SubmitAcceptUserOpResponse replace daemon.ts's three inline UserOp build-response types + ApiSubmitResult.
  • Two latent drifts the codegen forced into the open, fixed here:
  • New CI gates (harness-ci rust-checks): git diff --exit-code on the generated dir after cargo test regenerates it, plus a frontend typecheck step (wasm-pack build → npm citsc --noEmit) so the bindings AND their consumer compile on every PR. apps/parent-control/** added to the PR path filter (a frontend-only PR previously skipped the job entirely). npm run typecheck is fully green in CI for the first time — the historical core.ts wasm-artifact failures are gone because CI builds the wasm.

Commit 3 — the #275 delta: dissolve the plant parity check into the type system

  • The plant contract (route + ApiMemoryEntry + request/response bodies) moves to agentkeys-protocol::web_api; ui_bridge re-exports it (daemon-internal code + the fixture-pinning unit test unchanged; the entry helpers ride an extension trait since inherent impls stay in the defining crate).
  • agentkeys-web-core wasm exports masterMemoryPlantRoute() + buildMasterMemoryPlantBody(entries) — the body bytes come from serde_json over the daemon's own types, compiled to wasm.
  • daemon.ts stops hand-building the plant route/body entirely: it calls the wasm builder (typed input via the generated ApiMemoryEntry), posting the returned JSON verbatim. One code path; violating parity is a compile error.
  • The daemon.ts half of scripts/check-web-api-drift.sh is retired (compile-checked now); the harness half (web-parity-demo.sh's hand-curled body) stays fixture-gated. Phase 6 is at its terminal thinness: the irreducible "daemon → cap-mint → STS → worker → S3 reachable on real infra" smoke.
  • A shared memoized wasm-module loader (lib/client/wasm-module.ts) backs both CoreBackend and the plant path.
  • Docs: harness/AGENTS.md ladder section (rung 3 marked done), docs/operator-runbook-harness.md phase 6, root AGENTS.md §203 (split-crate + plant-contract bullets), docs/arch.md crate tree, plan doc docs/plan/frontend-testability-cli-web-parity.md carried over from refactor: #203 follow-up — agentkeys-protocol wire crate (wasm-safe) + ts-rs frontend bindings #215 with updated status.

Verification

  • cargo build/test --workspace ✓ · cargo clippy --workspace --all-targets -- -D warnings ✓ · cargo fmt --check
  • cargo check --target wasm32-unknown-unknown -p agentkeys-web-core (default + --features wasm) ✓
  • dump-protocol-fixtures --check ✓ (cap_mint_request.json byte-unchanged) · check-backend-fixture-drift.sh ✓ · check-web-api-drift.sh ✓ (harness consumer)
  • Generated bindings diff-clean after regeneration ✓ · npm run typecheck ✓ (zero errors, including core.ts)
  • wasm-pack build of the new exports ✓

What landed / did NOT land

Landed: every #275 scope bullet (wasm bindings for the plant types, daemon.ts consumes them, the daemon.ts drift-gate half retired, ladder + runbook docs updated) plus the B1/B2 re-land it depends on.

Did NOT land: nothing from the #275 scope. From the broader plan doc, Part A (frontend Vitest) + B4 remain future work, unchanged in status (documented in the plan doc).

To test this

Surface Action
Local daemon + web app rebuild via dev.sh (it rebuilds the daemon AND the wasm pkg — build_wasm picks up the new exports automatically)
Remote broker host no broker redeploy needed for behavior — the broker/worker code is untouched; agentkeys-backend-client/agentkeys-protocol are internal-refactor-only on the wire (cap-mint body byte-identical). The next routine setup-broker-host.sh --ref main picks it up.
Chain contracts untouched — no redeploy
Cloud (AWS/DNS/IAM) untouched — no action
CI the new gates run on this PR itself (wasm32 check, bindings diff, frontend typecheck)

Supersedes #215. Resolves #275.

🤖 Generated with Claude Code

…, web-core shares the cap-mint body

Re-lands #215's B1 against current main (the original branch rotted 56 commits
behind; protocol.rs had grown +256 lines that the move had to re-port).

Extract the broker/worker wire types (backend-client's protocol module) into a
standalone pure-serde agentkeys-protocol crate (no reqwest/tokio/aws — compiles
to wasm32). agentkeys-backend-client re-exports it as ::protocol, so every
existing agentkeys_backend_client::protocol::* path still resolves.

agentkeys-web-core (browser/wasm) now aliases the SHARED BrokerCapRequest as
CapRequest instead of carrying its own copy. That kills TWO live drifts:
ttl_seconds (required u64 native vs Option<u64> browser — the shared type is
Option + skip, faithful to the broker's serde default; native callers always
send Some, wire byte-identical) and the #76 K10 cap-PoP fields
(client_sig/client_nonce/client_ts), which web-core's copy was missing
entirely (browser sends None — verified-when-present).

web-core must NOT depend on backend-client directly (pulls aws-sdk-sts + tokio
+ native reqwest via the provisioner; breaks the wasm build): new harness-ci
rust-checks gate cargo-checks web-core for wasm32 (default + --features wasm).
The wasm32 target itself is pinned in rust-toolchain.toml (single SoT; rustup
toolchain install picks it up — no dtolnay action, per the #276 pin rule).

Verified: cargo build/test --workspace, clippy -D warnings, fmt, wasm32 checks
(default + wasm), dump-protocol-fixtures --check (cap_mint_request.json
byte-unchanged), check-backend-fixture-drift.sh, check-web-api-drift.sh.
…pes; CI typechecks against them

Re-lands #215's B2 against current main, extended to everything that grew
while that branch rotted.

ts-rs derives on the daemon's 12 ui_bridge Api* structs + MemoryCategory now
generate apps/parent-control/lib/generated/*.ts; daemon.ts imports them and
all 8 hand-declared wire interfaces are deleted. A Rust-side field rename is a
frontend compile error (rung 2 → rung 3). u64 fields pin #[ts(type=number)];
skip-serialize Options pin #[ts(optional)].

Extended past #215's scope (its own flagged follow-up):
- ProposedScope + the catalog Sensitivity generate too; gating becomes a real
  ScopeGating enum (TS union "auto" | "k11" instead of a bare &'static str).
- The protocol crate's WireUserOp / BuildAcceptUserOpResponse /
  SubmitAcceptUserOpResponse generate too — daemon.ts's three inline UserOp
  build-response types and ApiSubmitResult are replaced by the shared shapes.

Two latent drifts the codegen forced into the open, fixed here:
- SubmitAcceptUserOpResponse had drifted from what the broker actually returns
  (#97 user_op_hash + audit_envelope_hashes, #230 pending) — realigned to the
  broker's real serialization; the daemon proxies relay it verbatim.
- ApiActor's account_address/account_type were ad-hoc serde_json inserts in
  enrich_actor_account, invisible to any type contract — folded into the
  struct as proper optional fields.

CI (harness-ci rust-checks): git diff --exit-code on the generated dir after
cargo test regenerates, plus a NEW frontend gate — wasm-pack build + npm ci +
tsc --noEmit — so the bindings AND their consumer typecheck on every PR
(apps/parent-control/** added to the PR path filter; npm run typecheck is now
fully green in CI for the first time, the core.ts wasm-artifact failures are
gone because CI builds the wasm). ts-rs rides the no-serde-warnings feature.

Verified: cargo fmt/test/clippy -D warnings (workspace), generated dir
diff-clean after regeneration, check-web-api-drift.sh, fixture --check,
npm run typecheck green.
…sm tier-3)

The plant contract (MASTER_MEMORY_PLANT_ROUTE + ApiMemoryEntry +
request/response bodies) moves to the wasm-safe agentkeys-protocol::web_api;
ui_bridge re-exports it (daemon-internal code + the fixture-pinning test keep
their names; the entry helpers ride an extension trait since inherent impls
stay in the defining crate; PlantRequest/PlantResponse take their canonical
MasterMemoryPlant* names).

agentkeys-web-core exports masterMemoryPlantRoute() +
buildMasterMemoryPlantBody() under --features wasm: the body bytes come from
serde_json over the daemon's own types compiled into the browser. daemon.ts
stops hand-building the plant route/body entirely — it feeds ts-rs-typed
entries to the wasm builder and posts the returned JSON verbatim
(postJsonBody; re-stringifying would launder the bytes through JS). One code
path; violating parity is a compile error. A shared memoized wasm-module
loader (lib/client/wasm-module.ts) backs both CoreBackend and the plant path.

The daemon.ts half of scripts/check-web-api-drift.sh retires (compile-checked
now); the harness half (web-parity-demo.sh's hand-curled body) stays
fixture-gated, and the fixture stays pinned to the shared types by the
ui_bridge unit test. Phase 6 is at its terminal thinness: the irreducible
'daemon -> cap-mint -> STS -> worker -> S3 reachable on real infra' smoke.

Docs: harness/AGENTS.md ladder (rung 3 done), operator-runbook phase 6, root
AGENTS.md plant-contract bullet, arch.md crate tree (+agentkeys-protocol).

Verified: cargo fmt/test/clippy -D warnings (workspace), wasm32 checks,
wasm-pack build of the new exports, check-web-api-drift.sh (harness consumer),
check-backend-fixture-drift.sh, fixture --check, npm run typecheck green,
generated bindings committed (MasterMemoryPlantRequest/Response.ts).
@hanwencheng hanwencheng merged commit aeebfe0 into main Jun 13, 2026
10 checks passed
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.

Phase 6 web-parity: dissolve the runtime plant-parity check into the type system (wasm tier-3)

1 participant