Skip to content

Wire WASM transaction-policy engine to REST API and re-enable the UI Policies page #23

Description

@stxkxs

Context

The wasm-policy crate ships a working, sandboxed wasmtime policy engine (fuel + memory + epoch/time limits, module compilation + export validation, LRU module cache, optional on-disk persistence). However, none of it is reachable by a client: there are no /policies REST routes, the rest-api AppState has no PolicyEngine, and the UI Policies page was deliberately replaced with a ComingSoon placeholder during the 12-PR quality + hardening pass.

This was an intentional gating decision, not a regression. The gating landed in commit 63f8322 ("feat(rest-api): wire namespace + webhook management; gate hollow UI sections"), which wired up namespaces + webhooks but explicitly stubbed the Policies section rather than ship a half-wired path. This issue tracks completing that deferred work.

The engine, store, and UI client/types already exist and are honestly labelled — the missing piece is the REST backend plus the AppState wiring and re-enabling the page.

Current state (verified against live code)

Engine exists and is fully usablecrates/wasm-policy/src/lib.rs:80-86 re-exports PolicyEngine, PolicyStore, Policy, PolicyMetadata, PolicyId, ResourceLimits, etc.

  • PolicyEngine::with_store(...), register_policy, evaluate, evaluate_policy, simulatecrates/wasm-policy/src/engine.rs:44,90,95,120,424
  • PolicyStore CRUD: register, get, update, remove, list, list_metadata, enable, disablecrates/wasm-policy/src/store.rs:72,106,137,166,185,190,217,234
  • WASM is validated on register: size cap (MAX_POLICY_SIZE = 1 MiB, lib.rs:120), compiled via Module::new, and exports checked via validate_exportscrates/wasm-policy/src/store.rs:76-88
  • PolicyMetadata::new(...) + builder (with_description, for_namespace, etc.) — crates/wasm-policy/src/policy.rs:164-216; Policy::new(metadata, bytecode)policy.rs:292

REST API does not expose any of it.

  • No /policies routes registered. crates/rest-api/src/routes.rs:36-94 nests only /keys, /audit, /auth, /namespaces, /webhooks.
  • AppState has no policy engine field — crates/rest-api/src/middleware.rs:23-45 (fields: sessions, key_manager, rate_limiter, rbac, namespaces, acls, webhooks, audit, start_time).
  • hsm-rest-api's Cargo.toml does not depend on hsm-wasm-policycrates/rest-api/Cargo.toml lists hsm-auth, hsm-audit, hsm-crypto-engine, hsm-key-manager, hsm-webhooks only (lines 22-26).
  • axum is pulled in with features = ["json", "macros"]crates/rest-api/Cargo.toml:11. The multipart feature is not enabled, so multipart WASM upload extraction is not currently available.
  • The server binary builds AppState via AppState::with_key_manager(...).with_audit_logger(...) and calls create_routercrates/hsm-server/src/main.rs:346-350. A live PolicyEngine must be constructed here and threaded into AppState.
  • Namespace handler notes policy attachment is unwired — crates/rest-api/src/handlers.rs:1288 ("policy attachment is not yet wired").

UI is built against routes that 404.

  • ui/app/(protected)/policies/page.tsx:1-11 renders <ComingSoon ... detail="...uploading and managing policy modules over the REST API (multipart WASM upload + a live policy engine) is a future release." />.
  • ui/lib/api.ts:256-295 already implements listPolicies (GET /policies), createPolicy (multipart POST /policies, FormData), getPolicy, deletePolicy, attachPolicy (POST /policies/{id}/attach) — all currently hit a 404.
  • UI types already defined: Policy and PolicyRequest (with wasm: File) — ui/lib/types.ts:225-242.
  • UI proxies /api/hsm/:path*${HSM_API_URL || http://localhost:8443}/:path*ui/next.config.mjs:6-7, so route paths must match the client (/policies, not /api/policies).

What is needed

  1. Add hsm-wasm-policy as a dependency of hsm-rest-api and enable axum's multipart feature (crates/rest-api/Cargo.toml).
  2. Stand up a live PolicyEngine in AppState (crates/rest-api/src/middleware.rs): add a pub policies: Arc<PolicyEngine> field, construct it in new/with_key_manager (a PolicyStore::with_storage for persistence, or in-memory store, with sensible default ResourceLimits). Wire construction in crates/hsm-server/src/main.rs near line 346 so the deployed binary has it.
  3. Add /policies routes to the authenticated router (crates/rest-api/src/routes.rs):
    • GET /policies → list (map PolicyStore::list_metadata → UI Policy shape).
    • POST /policies → multipart upload (fields: name, optional description, fuel_limit, memory_limit, and the wasm file). Build PolicyMetadata + Policy::new, call register_policy (which validates size + compiles + checks exports). Return the created Policy.
    • GET /policies/{id} → get one.
    • DELETE /policies/{id} → remove.
    • POST /policies/{id}/attach → attach to a namespace (body { namespace }); use the metadata namespace filter / PolicyStore::update.
  4. Handlers in crates/rest-api/src/handlers.rs: enforce RBAC (policy management is privileged — gate behind the same permission tier as namespace/webhook management), surface compile/validation failures as 400, map PolicyError::NotFound404. Note multipart bodies bypass the JSON path but are still bounded by the existing DefaultBodyLimit (1 MiB, routes.rs:20) — confirm this is compatible with MAX_POLICY_SIZE (also 1 MiB) or raise the limit for this route.
  5. Map the wire shape to the UI Policy interface (id, name, description?, wasm_hash, fuel_limit, memory_limit, created_at, attached_namespaces[]) — ui/lib/types.ts:225-234. wasm_hashmetadata.bytecode_hash, attached_namespacesmetadata.namespaces.
  6. Re-enable the UI page: replace ui/app/(protected)/policies/page.tsx ComingSoon with a real page that lists policies, uploads a .wasm module via the existing hsmApi.createPolicy(FormData), and supports delete + attach-to-namespace. The api.ts client is already written (ui/lib/api.ts:256-295) — no changes needed there beyond confirming paths.
  7. Optionally wire policy evaluation into the signing path (call PolicyEngine::evaluate before performing a sign for a namespace with attached policies), or leave evaluation enforcement to a follow-up and scope this issue to management + storage only. State the chosen scope in the PR.

Acceptance criteria

  • hsm-rest-api depends on hsm-wasm-policy; axum multipart feature enabled.
  • AppState carries an Arc<PolicyEngine>, constructed in new/with_key_manager and wired through hsm-server main.rs.
  • GET /policies, POST /policies (multipart), GET /policies/{id}, DELETE /policies/{id}, POST /policies/{id}/attach are registered under the authenticated router and protected by RBAC.
  • Uploading a valid .wasm module that exports evaluate succeeds and the policy appears in GET /policies; an invalid/non-compiling module returns 400 with a clear message; over-size returns 413/400.
  • Response bodies match the UI Policy type; hsmApi.listPolicies/createPolicy/getPolicy/deletePolicy/attachPolicy all return non-404.
  • ui/app/(protected)/policies/page.tsx no longer renders ComingSoon; it lists, uploads, deletes, and attaches policies against the live API.
  • Tests: route-level integration tests for register (valid + invalid WASM), list, get, delete, attach; preserve the existing body-size-limit test.
  • cargo clippy --all -- -D warnings, cargo test --all (skipping slow tests per CLAUDE.md), and cargo doc --no-deps --all are clean.

Related (do not duplicate)

Distinct from #3 (threshold-ECDSA), #4 (SNIP-12), #5 (Cosmos SignDoc), #6 (sound ZK Lasso verifier), #8 (PQC migration). This issue is solely the WASM policy REST surface + AppState wiring + UI re-enable; the wasm-policy engine itself already works.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions