diff --git a/AGENTS.md b/AGENTS.md index 6bc71ee..5148908 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t Arbiter is a **permissioned signing service** for cryptocurrency wallets. It consists of: - **`server/`** — Rust gRPC daemon that holds encrypted keys and enforces policies -- **`useragent/`** — Flutter desktop app (macOS/Windows) with a Rust backend via Rinf +- **`operator/`** — Flutter desktop app (macOS/Windows) with a Rust backend via Rinf - **`protobufs/`** — Protocol Buffer definitions shared between server and client The vault never exposes key material; it only produces signatures when requests satisfy configured policies. @@ -28,7 +28,7 @@ Key versions: Rust 1.93.0 (with clippy), Flutter 3.38.9-stable, protoc 29.6, die |---|---| | `arbiter-proto` | Generated gRPC stubs + protobuf types; compiled from `protobufs/*.proto` via `tonic-prost-build` | | `arbiter-server` | Main daemon — actors, DB, EVM policy engine, gRPC service implementation | -| `arbiter-useragent` | Rust client library for the user agent side of the gRPC protocol | +| `arbiter-operator` | Rust client library for the operator side of the gRPC protocol | | `arbiter-client` | Rust client library for SDK clients | ### Common Commands @@ -67,10 +67,10 @@ The server is actor-based using the **kameo** crate. All long-lived state lives - **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run. - **`Vault`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell. -- **`FlowCoordinator`** — Coordinates cross-connection flow between user agents and SDK clients. +- **`FlowCoordinator`** — Coordinates cross-connection flow between operators and SDK clients. - **`EvmActor`** — Handles EVM transaction policy enforcement and signing. -Per-connection actors live under `actors/user_agent/` and `actors/client/`, each with `auth` (challenge-response authentication) and `session` (post-auth operations) sub-modules. +Per-connection actors live under `actors/operator/` and `actors/client/`, each with `auth` (challenge-response authentication) and `session` (post-auth operations) sub-modules. **Database:** SQLite via `diesel-async` + `bb8` connection pool. Schema managed by embedded Diesel migrations in `crates/arbiter-server/migrations/`. DB file lives at `~/.arbiter/arbiter.sqlite`. Tests use a temp-file DB via `db::create_test_pool()`. @@ -121,20 +121,20 @@ pub fn verify(&self, nonce: i32, context: &[u8], signature: &Signature) -> bool This forces callers to either use the return value or explicitly ignore it with `let _ = ...;`, preventing silent failures. -## User Agent (Flutter + Rinf at `useragent/`) +## Operator (Flutter + Rinf at `operator/`) -The Flutter app uses [Rinf](https://rinf.cunarist.org) to call Rust code. The Rust logic lives in `useragent/native/hub/` as a separate crate that uses `arbiter-useragent` for the gRPC client. +The Flutter app uses [Rinf](https://rinf.cunarist.org) to call Rust code. The Rust logic lives in `operator/native/hub/` as a separate crate that uses `arbiter-operator` for the gRPC client. -Communication between Dart and Rust uses typed **signals** defined in `useragent/native/hub/src/signals/`. After modifying signal structs, regenerate Dart bindings: +Communication between Dart and Rust uses typed **signals** defined in `operator/native/hub/src/signals/`. After modifying signal structs, regenerate Dart bindings: ```sh -cd useragent && rinf gen +cd operator && rinf gen ``` ### Common Commands ```sh -cd useragent +cd operator # Run the app (macOS or Windows) flutter run @@ -146,4 +146,4 @@ rinf gen flutter analyze ``` -The Rinf Rust entry point is `useragent/native/hub/src/lib.rs`. It spawns actors defined in `useragent/native/hub/src/actors/` which handle Dart↔server communication via signals. +The Rinf Rust entry point is `operator/native/hub/src/lib.rs`. It spawns actors defined in `operator/native/hub/src/actors/` which handle Dart↔server communication via signals. diff --git a/README.md b/README.md index 6298151..389aac0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Security warning Arbiter can't meaningfully protect against host compromise. Potential attack flow: - Attacker steals TLS keys from database -- Pretends to be server; just accepts user agent challenge solutions +- Pretends to be server; just accepts operator challenge solutions - Pretend to be in sealed state and performing DH with client - Steals user password and derives seal key diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cbf78d9..c4f4c8f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -9,7 +9,7 @@ Arbiter is a permissioned signing service for cryptocurrency wallets. It runs as Arbiter distinguishes two kinds of peers: -- **User Agent** — A client application used by the owner to manage the vault (create wallets, approve SDK clients, configure policies). +- **Operator** — A client application used by the owner to manage the vault (create wallets, approve SDK clients, configure policies). - **SDK Client** — A consumer of signing capabilities, typically an automation tool. In the future, this could include a browser-based wallet. - **Recovery Operator** — A dormant recovery participant with narrowly scoped authority used only for custody recovery and operator replacement. @@ -30,24 +30,24 @@ All peers authenticate via public-key cryptography using a challenge-response pr Authentication challenges are per-connection, ephemeral values. They are not persisted in the peer tables, and peer records store no challenge state. -### 2.2 User Agent Bootstrap +### 2.2 Operator Bootstrap -On first run — when no User Agents are registered — the server generates a one-time bootstrap token. It is made available in two ways: +On first run — when no Operators are registered — the server generates a one-time bootstrap token. It is made available in two ways: -- **Local setup:** Written to `~/.arbiter/bootstrap_token` for automatic discovery by a co-located User Agent. +- **Local setup:** Written to `~/.arbiter/bootstrap_token` for automatic discovery by a co-located Operator. - **Remote setup:** Printed to the server's console output. -The first User Agent must present this token alongside the standard challenge-response to complete registration. +The first Operator must present this token alongside the standard challenge-response to complete registration. ### 2.3 SDK Client Registration -There is no bootstrap mechanism for SDK clients. They must be explicitly approved by an already-registered User Agent. +There is no bootstrap mechanism for SDK clients. They must be explicitly approved by an already-registered Operator. --- ## 3. Multi-Operator Governance -When more than one User Agent is registered, the vault is treated as having multiple operators. In that mode, sensitive actions are governed by voting rather than by a single operator decision. +When more than one Operator is registered, the vault is treated as having multiple operators. In that mode, sensitive actions are governed by voting rather than by a single operator decision. ### 3.1 Voting Rules @@ -165,13 +165,13 @@ In both cases, committee formation is a coordinated process. Arbiter does not al When an unbootstrapped vault is initialized as a multi-operator vault, the setup proceeds as follows: -1. An operator connects to the unbootstrapped vault using a User Agent and the bootstrap token. +1. An operator connects to the unbootstrapped vault using an Operator and the bootstrap token. 2. During bootstrap setup, that operator declares: - the total number of ordinary operators - the total number of Recovery Operators 3. The vault enters **multi-bootstrap mode**. 4. While in multi-bootstrap mode: - - every ordinary operator must connect with a User Agent using the bootstrap token + - every ordinary operator must connect with an Operator using the bootstrap token - every Recovery Operator must also connect using the bootstrap token - each participant is registered individually - each participant's share is created and protected with that participant's credentials @@ -193,8 +193,8 @@ The server proves its identity using TLS with a self-signed certificate. The TLS Peers verify the server by its **public key fingerprint**: -- **User Agent (local):** Receives the fingerprint automatically through the bootstrap token. -- **User Agent (remote) / SDK Client:** Must receive the fingerprint out-of-band. +- **Operator (local):** Receives the fingerprint automatically through the bootstrap token. +- **Operator (remote) / SDK Client:** Must receive the fingerprint out-of-band. > A streamlined setup mechanism using a single connection string is planned but not yet implemented. @@ -231,11 +231,11 @@ On boot, the root key is encrypted and the server cannot perform any signing ope ### 6.2 Unseal Flow -To transition to the **Unsealed** state, a User Agent must provide the password: +To transition to the **Unsealed** state, an Operator must provide the password: -1. The User Agent initiates an unseal request. +1. The Operator initiates an unseal request. 2. The server generates a one-time key pair and returns the public key. -3. The User Agent encrypts the user's password with this one-time public key and sends the ciphertext to the server. +3. The Operator encrypts the user's password with this one-time public key and sends the ciphertext to the server. 4. The server decrypts and verifies the password: - **Success:** The root key is decrypted and placed into a hardened memory cell. The server transitions to `Unsealed`. Any entries pending encryption scheme migration are re-encrypted. - **Failure:** The server returns an error indicating the password is incorrect. @@ -257,7 +257,7 @@ See [IMPLEMENTATION.md](IMPLEMENTATION.md) for the current and planned memory pr ### 7.1 Fundamental Rules - SDK clients have **no access by default**. -- Access is granted **explicitly** by a User Agent. +- Access is granted **explicitly** by an Operator. - Grants are scoped to **specific wallets** and governed by **policies**. Each blockchain requires its own policy system due to differences in static transaction analysis. Currently, only EVM is supported; Solana support is planned. @@ -277,19 +277,19 @@ sequenceDiagram autonumber actor SDK as SDK Client participant Server - participant UA as User Agent + participant operator as Operator SDK->>Server: SignTransactionRequest Server->>Server: Resolve wallet and wallet visibility alt Visibility approval required - Server->>UA: Ask for wallet visibility approval - UA-->>Server: Vote result + Server->>operator: Ask for wallet visibility approval + operator-->>Server: Vote result end Server->>Server: Evaluate transaction Server->>Server: Load grant and limits context alt Grant approval required - Server->>UA: Ask for execution / grant approval - UA-->>Server: Vote result + Server->>operator: Ask for execution / grant approval + operator-->>Server: Vote result opt Create persistent grant Server->>Server: Create and store grant end diff --git a/docs/IMPLEMENTATION.md b/docs/IMPLEMENTATION.md index 57d38b4..b55f9bd 100644 --- a/docs/IMPLEMENTATION.md +++ b/docs/IMPLEMENTATION.md @@ -8,10 +8,10 @@ This document covers concrete technology choices and dependencies. For the archi ### Authentication Result Semantics -Authentication no longer uses an implicit success-only response shape. Both `client` and `user-agent` return explicit auth status enums over the wire. +Authentication no longer uses an implicit success-only response shape. Both `client` and `operator` return explicit auth status enums over the wire. -- **Client:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `APPROVAL_DENIED`, `NO_USER_AGENTS_ONLINE`, or `INTERNAL` -- **User-agent:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `BOOTSTRAP_REQUIRED`, `TOKEN_INVALID`, or `INTERNAL` +- **Client:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `APPROVAL_DENIED`, `NO_OPERATORS_ONLINE`, or `INTERNAL` +- **Operator:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `BOOTSTRAP_REQUIRED`, `TOKEN_INVALID`, or `INTERNAL` This makes transport-level failures and actor/domain-level auth failures distinct: @@ -22,7 +22,7 @@ Clients are expected to handle these status codes directly and present the concr ### New Client Approval -When a client whose public key is not yet in the database connects, all connected user agents are asked to approve the connection. The first agent to respond determines the outcome; remaining requests are cancelled via a watch channel. +When a client whose public key is not yet in the database connects, all connected operators are asked to approve the connection. The first operator to respond determines the outcome; remaining requests are cancelled via a watch channel. ```mermaid flowchart TD @@ -31,10 +31,10 @@ flowchart TD C -- yes --> G[Generate AuthChallenge] - C -- no --> E[Ask all UserAgents:\nClientConnectionRequest] + C -- no --> E[Ask all Operators:\nClientConnectionRequest] E --> F{First response} F -- denied --> Z([Reject connection]) - F -- approved --> F2[Cancel remaining\nUserAgent requests] + F -- approved --> F2[Cancel remaining\nOperator requests] F2 --> F3[INSERT client] F3 --> G @@ -50,7 +50,7 @@ Auth challenges are generated from fresh random bytes plus a nanosecond timestam The authentication schema stores peer identity, not replay counters: - `program_client` stores the SDK client's public key, metadata binding, and timestamps. -- `useragent_client` stores the User Agent public key and timestamps. +- `operator_client` stores the Operator public key and timestamps. - Neither table stores an authentication nonce, and challenge generation does not update either table. --- @@ -62,7 +62,7 @@ The authentication schema stores peer identity, not replay counters: ### User-Agent Authentication -User-agent authentication supports multiple signature schemes because platform-provided "hardware-bound" keys do not expose a uniform algorithm across operating systems and hardware. +Operator authentication supports multiple signature schemes because platform-provided "hardware-bound" keys do not expose a uniform algorithm across operating systems and hardware. - **Supported schemes:** ML-DSA - **Why:** Secure Enclave (MacOS) support them natively, on other platforms we could emulate while they roll-out @@ -86,7 +86,7 @@ User-agent authentication supports multiple signature schemes because platform-p ### Request Multiplexing -Both `client` and `user-agent` connections support multiple in-flight requests over one gRPC bidi stream. +Both `client` and `operator` connections support multiple in-flight requests over one gRPC bidi stream. - Every request carries a monotonically increasing request ID - Every normal response echoes the request ID it corresponds to @@ -141,7 +141,7 @@ flowchart TD L -- Yes --> M[Check grant limits] L -- No --> N[Start execution or grant voting flow] - N --> O{User-agent decision} + N --> O{Operator decision} O -- Reject --> Z4[Return no matching grant error] O -- Allow once --> M O -- Create grant --> P[Create grant with user-selected limits] diff --git a/docs/superpowers/plans/2026-03-28-grant-creation-refactor.md b/docs/superpowers/plans/2026-03-28-grant-creation-refactor.md index 11f1837..6783949 100644 --- a/docs/superpowers/plans/2026-03-28-grant-creation-refactor.md +++ b/docs/superpowers/plans/2026-03-28-grant-creation-refactor.md @@ -111,7 +111,7 @@ String shortAddress(List bytes) { - [ ] **Step 2: Verify** ```sh -cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/utils.dart +cd operator && dart analyze lib/screens/dashboard/evm/grants/create/utils.dart ``` Expected: no errors. @@ -168,7 +168,7 @@ class GrantCreation extends _$GrantCreation { - [ ] **Step 2: Run code generator** ```sh -cd useragent && dart run build_runner build --delete-conflicting-outputs +cd operator && dart run build_runner build --delete-conflicting-outputs ``` Expected: generates `provider.freezed.dart` and `provider.g.dart`, no errors. @@ -176,7 +176,7 @@ Expected: generates `provider.freezed.dart` and `provider.g.dart`, no errors. - [ ] **Step 3: Verify** ```sh -cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/provider.dart +cd operator && dart analyze lib/screens/dashboard/evm/grants/create/provider.dart ``` Expected: no errors. @@ -204,7 +204,7 @@ jj describe -m "feat(grants): add GrantCreation provider (client selection + gra ```dart // lib/screens/dashboard/evm/grants/create/fields/client_picker_field.dart -import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:arbiter/proto/operator.pb.dart'; import 'package:arbiter/providers/sdk_clients/list.dart'; import 'package:arbiter/screens/dashboard/evm/grants/create/provider.dart'; import 'package:flutter/material.dart'; @@ -246,7 +246,7 @@ class ClientPickerField extends ConsumerWidget { ```dart // lib/screens/dashboard/evm/grants/create/fields/wallet_access_picker_field.dart import 'package:arbiter/proto/evm.pb.dart'; -import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:arbiter/proto/operator.pb.dart'; import 'package:arbiter/providers/evm/evm.dart'; import 'package:arbiter/providers/sdk_clients/wallet_access_list.dart'; import 'package:arbiter/screens/dashboard/evm/grants/create/provider.dart'; @@ -522,7 +522,7 @@ class TransactionRateLimitField extends StatelessWidget { - [ ] **Step 8: Verify all field widgets** ```sh -cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/fields/ +cd operator && dart analyze lib/screens/dashboard/evm/grants/create/fields/ ``` Expected: no errors. @@ -585,7 +585,7 @@ class SharedGrantFields extends StatelessWidget { - [ ] **Step 2: Verify** ```sh -cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/shared_grant_fields.dart +cd operator && dart analyze lib/screens/dashboard/evm/grants/create/shared_grant_fields.dart ``` Expected: no errors. @@ -978,7 +978,7 @@ class _TokenVolumeLimitRow extends HookWidget { - [ ] **Step 4: Run code generator for token_transfer_grant.g.dart** ```sh -cd useragent && dart run build_runner build --delete-conflicting-outputs +cd operator && dart run build_runner build --delete-conflicting-outputs ``` Expected: generates `token_transfer_grant.g.dart`, no errors. @@ -986,7 +986,7 @@ Expected: generates `token_transfer_grant.g.dart`, no errors. - [ ] **Step 5: Verify** ```sh -cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/grants/ +cd operator && dart analyze lib/screens/dashboard/evm/grants/create/grants/ ``` Expected: no errors. @@ -1265,7 +1265,7 @@ String _formatError(Object error) { - [ ] **Step 2: Verify the full create/ directory** ```sh -cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/ +cd operator && dart analyze lib/screens/dashboard/evm/grants/create/ ``` Expected: no errors. diff --git a/docs/superpowers/plans/2026-03-28-grant-grid-view.md b/docs/superpowers/plans/2026-03-28-grant-grid-view.md index 859971a..babe563 100644 --- a/docs/superpowers/plans/2026-03-28-grant-grid-view.md +++ b/docs/superpowers/plans/2026-03-28-grant-grid-view.md @@ -14,24 +14,24 @@ | File | Action | Responsibility | |---|---|---| -| `useragent/lib/theme/palette.dart` | Modify | Add `Palette.token` (indigo accent for token-transfer cards) | -| `useragent/lib/features/connection/evm/wallet_access.dart` | Modify | Add `listAllWalletAccesses()` function | -| `useragent/lib/providers/sdk_clients/wallet_access_list.dart` | Create | `WalletAccessListProvider` — fetches full wallet access list with IDs | -| `useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart` | Create | `GrantCard` widget — watches enrichment providers + revoke mutation; one card per grant | -| `useragent/lib/screens/dashboard/evm/grants/grants.dart` | Create | `EvmGrantsScreen` — watches `evmGrantsProvider`; handles loading/error/empty/data states; renders `GrantCard` list | -| `useragent/lib/router.dart` | Modify | Register `EvmGrantsRoute` in dashboard children | -| `useragent/lib/screens/dashboard.dart` | Modify | Add Grants entry to `routes` list and `NavigationDestination` list | +| `operator/lib/theme/palette.dart` | Modify | Add `Palette.token` (indigo accent for token-transfer cards) | +| `operator/lib/features/connection/evm/wallet_access.dart` | Modify | Add `listAllWalletAccesses()` function | +| `operator/lib/providers/sdk_clients/wallet_access_list.dart` | Create | `WalletAccessListProvider` — fetches full wallet access list with IDs | +| `operator/lib/screens/dashboard/evm/grants/widgets/grant_card.dart` | Create | `GrantCard` widget — watches enrichment providers + revoke mutation; one card per grant | +| `operator/lib/screens/dashboard/evm/grants/grants.dart` | Create | `EvmGrantsScreen` — watches `evmGrantsProvider`; handles loading/error/empty/data states; renders `GrantCard` list | +| `operator/lib/router.dart` | Modify | Register `EvmGrantsRoute` in dashboard children | +| `operator/lib/screens/dashboard.dart` | Modify | Add Grants entry to `routes` list and `NavigationDestination` list | --- ## Task 1: Add `Palette.token` **Files:** -- Modify: `useragent/lib/theme/palette.dart` +- Modify: `operator/lib/theme/palette.dart` - [ ] **Step 1: Add the color** -Replace the contents of `useragent/lib/theme/palette.dart` with: +Replace the contents of `operator/lib/theme/palette.dart` with: ```dart import 'package:flutter/material.dart'; @@ -48,7 +48,7 @@ class Palette { - [ ] **Step 2: Verify** ```sh -cd useragent && flutter analyze lib/theme/palette.dart +cd operator && flutter analyze lib/theme/palette.dart ``` Expected: no issues. @@ -65,20 +65,20 @@ jj new ## Task 2: Add `listAllWalletAccesses` feature function **Files:** -- Modify: `useragent/lib/features/connection/evm/wallet_access.dart` +- Modify: `operator/lib/features/connection/evm/wallet_access.dart` `readClientWalletAccess` (existing) filters the list to one client's wallet IDs and returns `Set`. This new function returns the complete unfiltered list with row IDs so the grant cards can resolve wallet_access_id → wallet + client. - [ ] **Step 1: Append function** -Add at the bottom of `useragent/lib/features/connection/evm/wallet_access.dart`: +Add at the bottom of `operator/lib/features/connection/evm/wallet_access.dart`: ```dart Future> listAllWalletAccesses( Connection connection, ) async { final response = await connection.ask( - UserAgentRequest(listWalletAccess: Empty()), + OperatorRequest(listWalletAccess: Empty()), ); if (!response.hasListWalletAccessResponse()) { throw Exception( @@ -97,7 +97,7 @@ Each returned `SdkClientWalletAccess` has: - [ ] **Step 2: Verify** ```sh -cd useragent && flutter analyze lib/features/connection/evm/wallet_access.dart +cd operator && flutter analyze lib/features/connection/evm/wallet_access.dart ``` Expected: no issues. @@ -114,18 +114,18 @@ jj new ## Task 3: Create `WalletAccessListProvider` **Files:** -- Create: `useragent/lib/providers/sdk_clients/wallet_access_list.dart` -- Generated: `useragent/lib/providers/sdk_clients/wallet_access_list.g.dart` +- Create: `operator/lib/providers/sdk_clients/wallet_access_list.dart` +- Generated: `operator/lib/providers/sdk_clients/wallet_access_list.g.dart` Mirrors the structure of `EvmGrants` in `providers/evm/evm_grants.dart` — class-based `@riverpod` with a `refresh()` method. - [ ] **Step 1: Write the provider** -Create `useragent/lib/providers/sdk_clients/wallet_access_list.dart`: +Create `operator/lib/providers/sdk_clients/wallet_access_list.dart`: ```dart import 'package:arbiter/features/connection/evm/wallet_access.dart'; -import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:arbiter/proto/operator.pb.dart'; import 'package:arbiter/providers/connection/connection_manager.dart'; import 'package:mtcore/markettakers.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -165,15 +165,15 @@ class WalletAccessList extends _$WalletAccessList { - [ ] **Step 2: Run code generation** ```sh -cd useragent && dart run build_runner build --delete-conflicting-outputs +cd operator && dart run build_runner build --delete-conflicting-outputs ``` -Expected: `useragent/lib/providers/sdk_clients/wallet_access_list.g.dart` created. No errors. +Expected: `operator/lib/providers/sdk_clients/wallet_access_list.g.dart` created. No errors. - [ ] **Step 3: Verify** ```sh -cd useragent && flutter analyze lib/providers/sdk_clients/ +cd operator && flutter analyze lib/providers/sdk_clients/ ``` Expected: no issues. @@ -190,26 +190,26 @@ jj new ## Task 4: Create `GrantCard` widget **Files:** -- Create: `useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart` +- Create: `operator/lib/screens/dashboard/evm/grants/widgets/grant_card.dart` This widget owns all per-card logic: enrichment lookups, revoke action, and rebuild scope. The screen only passes it a `GrantEntry` — the card fetches everything else itself. **Key types:** - `GrantEntry` (from `proto/evm.pb.dart`): `.id`, `.shared.walletAccessId`, `.shared.chainId`, `.specific.whichGrant()` - `SpecificGrant_Grant.etherTransfer` / `.tokenTransfer` — enum values for the oneof -- `SdkClientWalletAccess` (from `proto/user_agent.pb.dart`): `.id`, `.access.walletId`, `.access.sdkClientId` +- `SdkClientWalletAccess` (from `proto/operator.pb.dart`): `.id`, `.access.walletId`, `.access.sdkClientId` - `WalletEntry` (from `proto/evm.pb.dart`): `.id`, `.address` (List) -- `SdkClientEntry` (from `proto/user_agent.pb.dart`): `.id`, `.info.name` +- `SdkClientEntry` (from `proto/operator.pb.dart`): `.id`, `.info.name` - `revokeEvmGrantMutation` — `Mutation` (global; all revoke buttons disable together while any revoke is in flight) - `executeRevokeEvmGrant(ref, grantId: int)` — `Future` - [ ] **Step 1: Write the widget** -Create `useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart`: +Create `operator/lib/screens/dashboard/evm/grants/widgets/grant_card.dart`: ```dart import 'package:arbiter/proto/evm.pb.dart'; -import 'package:arbiter/proto/user_agent.pb.dart'; +import 'package:arbiter/proto/operator.pb.dart'; import 'package:arbiter/providers/evm/evm.dart'; import 'package:arbiter/providers/evm/evm_grants.dart'; import 'package:arbiter/providers/sdk_clients/list.dart'; @@ -438,7 +438,7 @@ class GrantCard extends ConsumerWidget { - [ ] **Step 2: Verify** ```sh -cd useragent && flutter analyze lib/screens/dashboard/evm/grants/widgets/grant_card.dart +cd operator && flutter analyze lib/screens/dashboard/evm/grants/widgets/grant_card.dart ``` Expected: no issues. @@ -455,13 +455,13 @@ jj new ## Task 5: Create `EvmGrantsScreen` **Files:** -- Create: `useragent/lib/screens/dashboard/evm/grants/grants.dart` +- Create: `operator/lib/screens/dashboard/evm/grants/grants.dart` The screen watches only `evmGrantsProvider` for top-level state (loading / error / no connection / empty / data). When there is data it renders a list of `GrantCard` widgets — each card manages its own enrichment subscriptions. - [ ] **Step 1: Write the screen** -Create `useragent/lib/screens/dashboard/evm/grants/grants.dart`: +Create `operator/lib/screens/dashboard/evm/grants/grants.dart`: ```dart import 'package:arbiter/proto/evm.pb.dart'; @@ -702,7 +702,7 @@ class EvmGrantsScreen extends ConsumerWidget { - [ ] **Step 2: Verify** ```sh -cd useragent && flutter analyze lib/screens/dashboard/evm/grants/ +cd operator && flutter analyze lib/screens/dashboard/evm/grants/ ``` Expected: no issues. @@ -719,13 +719,13 @@ jj new ## Task 6: Wire router and dashboard tab **Files:** -- Modify: `useragent/lib/router.dart` -- Modify: `useragent/lib/screens/dashboard.dart` -- Regenerated: `useragent/lib/router.gr.dart` +- Modify: `operator/lib/router.dart` +- Modify: `operator/lib/screens/dashboard.dart` +- Regenerated: `operator/lib/router.gr.dart` - [ ] **Step 1: Add route to `router.dart`** -Replace the contents of `useragent/lib/router.dart` with: +Replace the contents of `operator/lib/router.dart` with: ```dart import 'package:auto_route/auto_route.dart'; @@ -759,7 +759,7 @@ class Router extends RootStackRouter { - [ ] **Step 2: Update `dashboard.dart`** -In `useragent/lib/screens/dashboard.dart`, replace the `routes` constant: +In `operator/lib/screens/dashboard.dart`, replace the `routes` constant: ```dart final routes = [ @@ -800,7 +800,7 @@ destinations: const [ - [ ] **Step 3: Regenerate router** ```sh -cd useragent && dart run build_runner build --delete-conflicting-outputs +cd operator && dart run build_runner build --delete-conflicting-outputs ``` Expected: `lib/router.gr.dart` updated, `EvmGrantsRoute` now available, no errors. @@ -808,7 +808,7 @@ Expected: `lib/router.gr.dart` updated, `EvmGrantsRoute` now available, no error - [ ] **Step 4: Full project verify** ```sh -cd useragent && flutter analyze +cd operator && flutter analyze ``` Expected: no issues. diff --git a/docs/superpowers/specs/2026-03-28-grant-grid-view-design.md b/docs/superpowers/specs/2026-03-28-grant-grid-view-design.md index f7094c2..17c116d 100644 --- a/docs/superpowers/specs/2026-03-28-grant-grid-view-design.md +++ b/docs/superpowers/specs/2026-03-28-grant-grid-view-design.md @@ -4,7 +4,7 @@ ## Overview -Add a "Grants" dashboard tab to the Flutter user-agent app that displays all EVM grants as a card-based grid. Each card shows a compact summary (type, chain, wallet address, client name) with a revoke action. The tab integrates into the existing `AdaptiveScaffold` navigation alongside Wallets, Clients, and About. +Add a "Grants" dashboard tab to the Flutter operator app that displays all EVM grants as a card-based grid. Each card shows a compact summary (type, chain, wallet address, client name) with a revoke action. The tab integrates into the existing `AdaptiveScaffold` navigation alongside Wallets, Clients, and About. ## Scope @@ -23,7 +23,7 @@ Add a "Grants" dashboard tab to the Flutter user-agent app that displays all EVM ### `walletAccessListProvider` -**File:** `useragent/lib/providers/sdk_clients/wallet_access_list.dart` +**File:** `operator/lib/providers/sdk_clients/wallet_access_list.dart` - `@riverpod` class, watches `connectionManagerProvider.future` - Returns `List?` (null when not connected) @@ -85,7 +85,7 @@ NavigationDestination( ## Screen: `EvmGrantsScreen` -**File:** `useragent/lib/screens/dashboard/evm/grants/grants.dart` +**File:** `operator/lib/screens/dashboard/evm/grants/grants.dart` ``` Scaffold diff --git a/mise.lock b/mise.lock index 08d8cb1..346f56d 100644 --- a/mise.lock +++ b/mise.lock @@ -1,51 +1,51 @@ # @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html [[tools.ast-grep]] -version = "0.42.0" +version = "0.42.1" backend = "aqua:ast-grep/ast-grep" [tools.ast-grep."platforms.linux-arm64"] -checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836" -url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip" +checksum = "sha256:3ba383839044cf9817929435f5ce0027f91d06931e8efb32d942e58d73d92be5" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-aarch64-unknown-linux-gnu.zip" [tools.ast-grep."platforms.linux-arm64-musl"] -checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836" -url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip" +checksum = "sha256:3ba383839044cf9817929435f5ce0027f91d06931e8efb32d942e58d73d92be5" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-aarch64-unknown-linux-gnu.zip" [tools.ast-grep."platforms.linux-x64"] -checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651" -url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip" +checksum = "sha256:5de8b87cba67fc8dc3e239d54b6484802ad745a7ae3de76be4fe89661dc52657" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-unknown-linux-gnu.zip" [tools.ast-grep."platforms.linux-x64-musl"] -checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651" -url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip" +checksum = "sha256:5de8b87cba67fc8dc3e239d54b6484802ad745a7ae3de76be4fe89661dc52657" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-unknown-linux-gnu.zip" [tools.ast-grep."platforms.macos-arm64"] -checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620" -url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip" +checksum = "sha256:c3961d8e8a4ee0ce2d0d98c7beeb168bb331cdc766b53630118a7b6c4fd39015" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-aarch64-apple-darwin.zip" [tools.ast-grep."platforms.macos-x64"] -checksum = "sha256:979ffe611327056f4730a1ae71b0209b3b830f58b22c6ed194cda34f55400db2" -url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-apple-darwin.zip" +checksum = "sha256:a038965bfd7fe44257c771cdf8918dc3467dd8ec0eef673b8b14f639b144cdbd" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-apple-darwin.zip" [tools.ast-grep."platforms.windows-x64"] -checksum = "sha256:55836fa1b2c65dc7d61615a4d9368622a0d2371a76d28b9a165e5a3ab6ae32a4" -url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-pc-windows-msvc.zip" +checksum = "sha256:fe34f631bb24c08ad146f92ca2a92971a53d179461b509fd8d32dc863bff9f83" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-pc-windows-msvc.zip" [[tools."cargo:cargo-audit"]] version = "0.22.1" backend = "cargo:cargo-audit" [[tools."cargo:cargo-edit"]] -version = "0.13.9" +version = "0.13.10" backend = "cargo:cargo-edit" [[tools."cargo:cargo-features-manager"]] -version = "0.11.1" +version = "0.12.0" backend = "cargo:cargo-features-manager" [[tools."cargo:cargo-insta"]] -version = "1.46.3" +version = "1.47.2" backend = "cargo:cargo-insta" [[tools."cargo:cargo-mutants"]] @@ -53,7 +53,7 @@ version = "27.0.0" backend = "cargo:cargo-mutants" [[tools."cargo:cargo-nextest"]] -version = "0.9.126" +version = "0.9.133" backend = "cargo:cargo-nextest" [[tools."cargo:cargo-shear"]] @@ -65,7 +65,7 @@ version = "0.10.2" backend = "cargo:cargo-vet" [[tools."cargo:diesel_cli"]] -version = "2.3.6" +version = "2.3.7" backend = "cargo:diesel_cli" [tools."cargo:diesel_cli".options] @@ -77,7 +77,7 @@ version = "2.12.0" backend = "cargo:flutter_rust_bridge_codegen" [[tools.flutter]] -version = "3.38.9-stable" +version = "3.41.7-stable" backend = "asdf:flutter" [[tools.protoc]] @@ -113,44 +113,44 @@ checksum = "sha256:1ebd7c87baffb9f1c47169b640872bf5fb1e4408079c691af527be9561d8f url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-win64.zip" [[tools.python]] -version = "3.14.3" +version = "3.14.4" backend = "core:python" [tools.python."platforms.linux-arm64"] -checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" +checksum = "sha256:b8b597fdb2f8dccdc502c11947b60a4b65eb6bce79cfa60c7ccf9b6e8352c60a" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.linux-arm64-musl"] -checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" +checksum = "sha256:b8b597fdb2f8dccdc502c11947b60a4b65eb6bce79cfa60c7ccf9b6e8352c60a" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.linux-x64"] -checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" +checksum = "sha256:fe9a9c32d13870af632cbac3dfc7528ae53597e94472aa4c7d6a42e8166136cd" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.linux-x64-musl"] -checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" +checksum = "sha256:fe9a9c32d13870af632cbac3dfc7528ae53597e94472aa4c7d6a42e8166136cd" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.macos-arm64"] -checksum = "sha256:c43aecde4a663aebff99b9b83da0efec506479f1c3f98331442f33d2c43501f9" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-apple-darwin-install_only_stripped.tar.gz" +checksum = "blake3:0314ec66e0f33ec04959583b5900bc8edae371a396aa96b8874e750d1fe936e6" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-aarch64-apple-darwin-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.macos-x64"] -checksum = "sha256:9ab41dbc2f100a2a45d1833b9c11165f51051c558b5213eda9a9731d5948a0c0" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-apple-darwin-install_only_stripped.tar.gz" +checksum = "sha256:d51250a32fa5d9f0799c7bcb71720c27b10a3afd4a7de288120f96085d508a5a" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-apple-darwin-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.windows-x64"] -checksum = "sha256:bbe19034b35b0267176a7442575ae7dc6343480fd4d35598cb7700173d431e09" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" +checksum = "sha256:a976991dcd085c1bb5d9a8084823a6bc8b7f9b079d8c432574a6ddd68c3a6fe1" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" provenance = "github-attestations" [[tools.rust]] -version = "1.93.0" +version = "1.95.0" backend = "core:rust" diff --git a/mise.toml b/mise.toml index 4ca43db..f0da5e6 100644 --- a/mise.toml +++ b/mise.toml @@ -1,17 +1,17 @@ [tools] -"cargo:diesel_cli" = { version = "2.3.6", features = "sqlite,sqlite-bundled", default-features = false } +"cargo:diesel_cli" = { version = "2.3.7", features = "sqlite,sqlite-bundled", default-features = "false" } "cargo:cargo-audit" = "0.22.1" "cargo:cargo-vet" = "0.10.2" -flutter = "3.38.9-stable" +flutter = "3.41.7-stable" protoc = "29.6" -"rust" = {version = "1.93.0", components = "clippy,rust-analyzer"} -"cargo:cargo-features-manager" = "0.11.1" -"cargo:cargo-nextest" = "0.9.126" +rust = { version = "1.95.0", components = "clippy,rust-analyzer" } +"cargo:cargo-features-manager" = "0.12.0" +"cargo:cargo-nextest" = "0.9.133" "cargo:cargo-shear" = "latest" -"cargo:cargo-insta" = "1.46.3" -python = "3.14.3" -ast-grep = "0.42.0" -"cargo:cargo-edit" = "0.13.9" +"cargo:cargo-insta" = "1.47.2" +python = "3.14.4" +ast-grep = "0.42.1" +"cargo:cargo-edit" = "0.13.10" "cargo:cargo-mutants" = "27.0.0" "cargo:flutter_rust_bridge_codegen" = "2.12.0" diff --git a/protobufs/arbiter.proto b/protobufs/arbiter.proto index cc1d7c3..ba24941 100644 --- a/protobufs/arbiter.proto +++ b/protobufs/arbiter.proto @@ -3,7 +3,7 @@ syntax = "proto3"; package arbiter; import "client.proto"; -import "user_agent.proto"; +import "operator.proto"; message ServerInfo { string version = 1; @@ -12,5 +12,5 @@ message ServerInfo { service ArbiterService { rpc Client(stream arbiter.client.ClientRequest) returns (stream arbiter.client.ClientResponse); - rpc UserAgent(stream arbiter.user_agent.UserAgentRequest) returns (stream arbiter.user_agent.UserAgentResponse); + rpc Operator(stream arbiter.operator.OperatorRequest) returns (stream arbiter.operator.OperatorResponse); } diff --git a/protobufs/client/auth.proto b/protobufs/client/auth.proto index 382cd23..3a081d2 100644 --- a/protobufs/client/auth.proto +++ b/protobufs/client/auth.proto @@ -24,7 +24,7 @@ enum AuthResult { AUTH_RESULT_INVALID_KEY = 2; AUTH_RESULT_INVALID_SIGNATURE = 3; AUTH_RESULT_APPROVAL_DENIED = 4; - AUTH_RESULT_NO_USER_AGENTS_ONLINE = 5; + AUTH_RESULT_NO_OPERATORS_ONLINE = 5; AUTH_RESULT_INTERNAL = 6; } diff --git a/protobufs/evm.proto b/protobufs/evm.proto index 4f7f910..316217d 100644 --- a/protobufs/evm.proto +++ b/protobufs/evm.proto @@ -75,7 +75,7 @@ message SpecificGrant { } } -// --- UserAgent grant management --- +// --- Operator grant management --- message EvmGrantCreateRequest { SharedSettings shared = 1; SpecificGrant specific = 2; diff --git a/protobufs/user_agent.proto b/protobufs/operator.proto similarity index 61% rename from protobufs/user_agent.proto rename to protobufs/operator.proto index 20fe81c..f780ae3 100644 --- a/protobufs/user_agent.proto +++ b/protobufs/operator.proto @@ -1,13 +1,13 @@ syntax = "proto3"; -package arbiter.user_agent; +package arbiter.operator; -import "user_agent/auth.proto"; -import "user_agent/evm.proto"; -import "user_agent/sdk_client.proto"; -import "user_agent/vault/vault.proto"; +import "operator/auth.proto"; +import "operator/evm.proto"; +import "operator/sdk_client.proto"; +import "operator/vault/vault.proto"; -message UserAgentRequest { +message OperatorRequest { int32 id = 16; oneof payload { auth.Request auth = 1; @@ -17,7 +17,7 @@ message UserAgentRequest { } } -message UserAgentResponse { +message OperatorResponse { optional int32 id = 16; oneof payload { auth.Response auth = 1; diff --git a/protobufs/user_agent/auth.proto b/protobufs/operator/auth.proto similarity index 95% rename from protobufs/user_agent/auth.proto rename to protobufs/operator/auth.proto index 291084e..61a8085 100644 --- a/protobufs/user_agent/auth.proto +++ b/protobufs/operator/auth.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package arbiter.user_agent.auth; +package arbiter.operator.auth; message AuthChallengeRequest { bytes pubkey = 1; diff --git a/protobufs/user_agent/evm.proto b/protobufs/operator/evm.proto similarity index 96% rename from protobufs/user_agent/evm.proto rename to protobufs/operator/evm.proto index 459c152..b92c119 100644 --- a/protobufs/user_agent/evm.proto +++ b/protobufs/operator/evm.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package arbiter.user_agent.evm; +package arbiter.operator.evm; import "evm.proto"; import "google/protobuf/empty.proto"; diff --git a/protobufs/user_agent/sdk_client.proto b/protobufs/operator/sdk_client.proto similarity index 97% rename from protobufs/user_agent/sdk_client.proto rename to protobufs/operator/sdk_client.proto index 30cfe5b..f59721f 100644 --- a/protobufs/user_agent/sdk_client.proto +++ b/protobufs/operator/sdk_client.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package arbiter.user_agent.sdk_client; +package arbiter.operator.sdk_client; import "shared/client.proto"; import "google/protobuf/empty.proto"; diff --git a/protobufs/user_agent/vault/bootstrap.proto b/protobufs/operator/vault/bootstrap.proto similarity index 90% rename from protobufs/user_agent/vault/bootstrap.proto rename to protobufs/operator/vault/bootstrap.proto index 8a009cf..97e376d 100644 --- a/protobufs/user_agent/vault/bootstrap.proto +++ b/protobufs/operator/vault/bootstrap.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package arbiter.user_agent.vault.bootstrap; +package arbiter.operator.vault.bootstrap; message BootstrapEncryptedKey { bytes nonce = 1; diff --git a/protobufs/user_agent/vault/unseal.proto b/protobufs/operator/vault/unseal.proto similarity index 93% rename from protobufs/user_agent/vault/unseal.proto rename to protobufs/operator/vault/unseal.proto index 8d0c378..9378d5d 100644 --- a/protobufs/user_agent/vault/unseal.proto +++ b/protobufs/operator/vault/unseal.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package arbiter.user_agent.vault.unseal; +package arbiter.operator.vault.unseal; message UnsealStart { bytes client_pubkey = 1; diff --git a/protobufs/user_agent/vault/vault.proto b/protobufs/operator/vault/vault.proto similarity index 77% rename from protobufs/user_agent/vault/vault.proto rename to protobufs/operator/vault/vault.proto index 832f13c..72bd940 100644 --- a/protobufs/user_agent/vault/vault.proto +++ b/protobufs/operator/vault/vault.proto @@ -1,11 +1,11 @@ syntax = "proto3"; -package arbiter.user_agent.vault; +package arbiter.operator.vault; import "google/protobuf/empty.proto"; import "shared/vault.proto"; -import "user_agent/vault/bootstrap.proto"; -import "user_agent/vault/unseal.proto"; +import "operator/vault/bootstrap.proto"; +import "operator/vault/unseal.proto"; message Request { oneof payload { diff --git a/server/crates/arbiter-client/src/auth.rs b/server/crates/arbiter-client/src/auth.rs index 7585e02..eae51e9 100644 --- a/server/crates/arbiter-client/src/auth.rs +++ b/server/crates/arbiter-client/src/auth.rs @@ -26,13 +26,13 @@ use chrono::DateTime; pub enum AuthError { #[error("Server sent invalid auth challenge")] InvalidChallenge, - #[error("Client approval denied by User Agent")] + #[error("Client approval denied by Operator")] ApprovalDenied, #[error("Auth challenge was not returned by server")] MissingAuthChallenge, - #[error("No User Agents online to approve client")] - NoUserAgentsOnline, + #[error("No Operators online to approve client")] + NoOperatorsOnline, #[error("Signing key storage error")] Storage(#[from] StorageError), @@ -44,7 +44,7 @@ pub enum AuthError { fn map_auth_result(code: i32) -> AuthError { match AuthResult::try_from(code).unwrap_or(AuthResult::Unspecified) { AuthResult::ApprovalDenied => AuthError::ApprovalDenied, - AuthResult::NoUserAgentsOnline => AuthError::NoUserAgentsOnline, + AuthResult::NoOperatorsOnline => AuthError::NoOperatorsOnline, AuthResult::Unspecified | AuthResult::Success | AuthResult::InvalidKey diff --git a/server/crates/arbiter-crypto/src/authn/v1.rs b/server/crates/arbiter-crypto/src/authn/v1.rs index 153cb14..6f38e44 100644 --- a/server/crates/arbiter-crypto/src/authn/v1.rs +++ b/server/crates/arbiter-crypto/src/authn/v1.rs @@ -7,7 +7,7 @@ use ml_dsa::{ use rand::RngExt; pub static CLIENT_CONTEXT: &[u8] = b"arbiter_client"; -pub static USERAGENT_CONTEXT: &[u8] = b"arbiter_user_agent"; +pub static OPERATOR_CONTEXT: &[u8] = b"arbiter_operator"; const NONCE_SIZE: usize = 32; @@ -192,7 +192,7 @@ mod tests { use crate::authn::AuthChallenge; - use super::{CLIENT_CONTEXT, PublicKey, Signature, SigningKey, USERAGENT_CONTEXT}; + use super::{CLIENT_CONTEXT, PublicKey, Signature, SigningKey, OPERATOR_CONTEXT}; #[test] fn public_key_round_trip_decodes() { @@ -227,7 +227,7 @@ mod tests { .expect("signature should be created"); assert!(public_key.verify(&challenge, CLIENT_CONTEXT, &signature)); - assert!(!public_key.verify(&challenge, USERAGENT_CONTEXT, &signature)); + assert!(!public_key.verify(&challenge, OPERATOR_CONTEXT, &signature)); } #[test] diff --git a/server/crates/arbiter-proto/build.rs b/server/crates/arbiter-proto/build.rs index 657066c..a9250d3 100644 --- a/server/crates/arbiter-proto/build.rs +++ b/server/crates/arbiter-proto/build.rs @@ -10,7 +10,7 @@ fn main() -> Result<(), Box> { .compile_protos( &[ format!("{}/arbiter.proto", PROTOBUF_DIR), - format!("{}/user_agent.proto", PROTOBUF_DIR), + format!("{}/operator.proto", PROTOBUF_DIR), format!("{}/client.proto", PROTOBUF_DIR), format!("{}/evm.proto", PROTOBUF_DIR), ], diff --git a/server/crates/arbiter-proto/src/lib.rs b/server/crates/arbiter-proto/src/lib.rs index 5f63aa1..0b91e11 100644 --- a/server/crates/arbiter-proto/src/lib.rs +++ b/server/crates/arbiter-proto/src/lib.rs @@ -12,30 +12,30 @@ pub mod proto { } } - pub mod user_agent { - tonic::include_proto!("arbiter.user_agent"); + pub mod operator { + tonic::include_proto!("arbiter.operator"); pub mod auth { - tonic::include_proto!("arbiter.user_agent.auth"); + tonic::include_proto!("arbiter.operator.auth"); } pub mod evm { - tonic::include_proto!("arbiter.user_agent.evm"); + tonic::include_proto!("arbiter.operator.evm"); } pub mod sdk_client { - tonic::include_proto!("arbiter.user_agent.sdk_client"); + tonic::include_proto!("arbiter.operator.sdk_client"); } pub mod vault { - tonic::include_proto!("arbiter.user_agent.vault"); + tonic::include_proto!("arbiter.operator.vault"); pub mod bootstrap { - tonic::include_proto!("arbiter.user_agent.vault.bootstrap"); + tonic::include_proto!("arbiter.operator.vault.bootstrap"); } pub mod unseal { - tonic::include_proto!("arbiter.user_agent.vault.unseal"); + tonic::include_proto!("arbiter.operator.vault.unseal"); } } } diff --git a/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql b/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql index ac40bce..0ab9a3f 100644 --- a/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql +++ b/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql @@ -43,13 +43,13 @@ create table if not exists arbiter_settings ( insert into arbiter_settings (id) values (1) on conflict do nothing; -- ensure singleton row exists -create table if not exists useragent_client ( +create table if not exists operator_client ( id integer not null primary key, public_key blob not null, created_at integer not null default(unixepoch ('now')), updated_at integer not null default(unixepoch ('now')) ) STRICT; -create unique index if not exists uniq_useragent_client_public_key on useragent_client (public_key); +create unique index if not exists uniq_operator_client_public_key on operator_client (public_key); create table if not exists client_metadata ( id integer not null primary key, diff --git a/server/crates/arbiter-server/src/actors/bootstrap.rs b/server/crates/arbiter-server/src/actors/bootstrap.rs index 03703f6..8ec0059 100644 --- a/server/crates/arbiter-server/src/actors/bootstrap.rs +++ b/server/crates/arbiter-server/src/actors/bootstrap.rs @@ -48,7 +48,7 @@ impl Bootstrapper { let row_count: i64 = { let mut conn = db.get().await?; - schema::useragent_client::table + schema::operator_client::table .count() .get_result(&mut conn) .await? diff --git a/server/crates/arbiter-server/src/actors/evm/mod.rs b/server/crates/arbiter-server/src/actors/evm/mod.rs index dfac716..11392ec 100644 --- a/server/crates/arbiter-server/src/actors/evm/mod.rs +++ b/server/crates/arbiter-server/src/actors/evm/mod.rs @@ -134,7 +134,7 @@ impl EvmActor { #[messages] impl EvmActor { #[message] - pub async fn useragent_create_grant( + pub async fn operator_create_grant( &mut self, basic: SharedGrantSettings, grant: SpecificGrant, @@ -161,7 +161,7 @@ impl EvmActor { #[message] #[expect(clippy::unused_async, reason = "reserved for impl")] - pub async fn useragent_delete_grant(&mut self, _grant_id: i32) -> Result<(), Error> { + pub async fn operator_delete_grant(&mut self, _grant_id: i32) -> Result<(), Error> { // let mut conn = self.db.get().await.map_err(DatabaseError::from)?; // let vault = self.vault.clone(); @@ -186,7 +186,7 @@ impl EvmActor { } #[message] - pub async fn useragent_list_grants(&mut self) -> Result>, Error> { + pub async fn operator_list_grants(&mut self) -> Result>, Error> { match self.engine.list_all_grants().await { Ok(grants) => Ok(grants), Err(ListError::Database(db_err)) => Err(Error::Database(db_err)), diff --git a/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs b/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs index 379c015..e33cff2 100644 --- a/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs +++ b/server/crates/arbiter-server/src/actors/flow_coordinator/client_connect_approval.rs @@ -2,7 +2,7 @@ use crate::{ actors::flow_coordinator::ApprovalError, peers::{ client::ClientProfile, - user_agent::{UserAgentSession, session::BeginNewClientApproval}, + operator::{OperatorSession, session::BeginNewClientApproval}, }, }; @@ -15,12 +15,12 @@ use std::ops::ControlFlow; pub struct Args { pub client: ClientProfile, - pub user_agents: Vec>, + pub operators: Vec>, pub reply: ReplySender>, } pub struct ClientApprovalController { - /// Number of UAs that have not yet responded (approval or denial) or died. + /// Number of operators that have not yet responded (approval or denial) or died. pending: usize, /// Number of approvals received so far. approved: usize, @@ -42,21 +42,21 @@ impl Actor for ClientApprovalController { async fn on_start( Args { client, - user_agents, + operators, reply, }: Self::Args, actor_ref: ActorRef, ) -> Result { let this = Self { - pending: user_agents.len(), + pending: operators.len(), approved: 0, reply: Some(reply), }; - for user_agent in user_agents { - actor_ref.link(&user_agent).await; + for operator in operators { + actor_ref.link(&operator).await; - let _ = user_agent + let _ = operator .tell(BeginNewClientApproval { client: client.clone(), controller: actor_ref.clone(), @@ -73,10 +73,10 @@ impl Actor for ClientApprovalController { _: ActorId, _: ActorStopReason, ) -> Result, Self::Error> { - // A linked UA died before responding — counts as a non-approval. + // A linked operator died before responding — counts as a non-approval. self.pending = self.pending.saturating_sub(1); if self.pending == 0 { - // At least one UA didn't approve: deny. + // At least one operator didn't approve: deny. self.send_reply(Ok(false)); return Ok(ControlFlow::Break(ActorStopReason::Normal)); } @@ -99,7 +99,7 @@ impl ClientApprovalController { self.pending = self.pending.saturating_sub(1); if self.pending == 0 { - // Every connected UA approved. + // Every connected operator approved. self.send_reply(Ok(true)); ctx.stop(); } diff --git a/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs b/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs index fa100e5..fa31334 100644 --- a/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs +++ b/server/crates/arbiter-server/src/actors/flow_coordinator/mod.rs @@ -1,7 +1,7 @@ use crate::{ actors::{ flow_coordinator::client_connect_approval::ClientApprovalController, - useragent_registry::{GetConnected, UserAgentRegistry}, + operator_registry::{GetConnected, OperatorRegistry}, }, peers::client::{ClientProfile, session::ClientSession}, }; @@ -20,14 +20,14 @@ pub mod client_connect_approval; pub struct FlowCoordinator { pub clients: HashMap>, - useragent_registry: ActorRef, + operator_registry: ActorRef, } impl FlowCoordinator { - pub fn new(useragent_registry: ActorRef) -> Self { + pub fn new(operator_registry: ActorRef) -> Self { Self { clients: HashMap::default(), - useragent_registry, + operator_registry, } } } @@ -66,8 +66,8 @@ impl Actor for FlowCoordinator { #[derive(Debug, thiserror::Error, Clone, PartialEq, Eq, Hash)] pub enum ApprovalError { - #[error("No user agents connected")] - NoUserAgentsConnected, + #[error("No operators connected")] + NoOperatorsConnected, } #[messages] @@ -93,19 +93,19 @@ impl FlowCoordinator { unreachable!("Expected `request_client_approval` to have callback channel"); }; - let Ok(refs) = self.useragent_registry.ask(GetConnected).await else { - reply_sender.send(Err(ApprovalError::NoUserAgentsConnected)); + let Ok(refs) = self.operator_registry.ask(GetConnected).await else { + reply_sender.send(Err(ApprovalError::NoOperatorsConnected)); return reply; }; if refs.is_empty() { - reply_sender.send(Err(ApprovalError::NoUserAgentsConnected)); + reply_sender.send(Err(ApprovalError::NoOperatorsConnected)); return reply; } ClientApprovalController::spawn(client_connect_approval::Args { client, - user_agents: refs, + operators: refs, reply: reply_sender, }); diff --git a/server/crates/arbiter-server/src/actors/mod.rs b/server/crates/arbiter-server/src/actors/mod.rs index e28f12c..e9900ae 100644 --- a/server/crates/arbiter-server/src/actors/mod.rs +++ b/server/crates/arbiter-server/src/actors/mod.rs @@ -1,7 +1,7 @@ use crate::{ actors::{ bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator, - useragent_registry::UserAgentRegistry, vault::Vault, + operator_registry::OperatorRegistry, vault::Vault, }, db, }; @@ -13,7 +13,7 @@ use thiserror::Error; pub mod bootstrap; pub mod evm; pub mod flow_coordinator; -pub mod useragent_registry; +pub mod operator_registry; pub mod vault; #[derive(Error, Debug)] @@ -31,7 +31,7 @@ pub struct GlobalActors { pub vault: ActorRef, pub bootstrapper: ActorRef, pub flow_coordinator: ActorRef, - pub useragent_registry: ActorRef, + pub operator_registry: ActorRef, pub evm: ActorRef, pub events: ActorRef, } @@ -44,15 +44,15 @@ impl GlobalActors { pub async fn spawn(db: db::DatabasePool) -> Result { let message_bus = Self::spawn_message_bus(); let key_holder = Vault::spawn(Vault::new(db.clone(), message_bus.clone()).await?); - let useragent_registry = UserAgentRegistry::spawn(UserAgentRegistry::default()); + let operator_registry = OperatorRegistry::spawn(OperatorRegistry::default()); Ok(Self { bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?), evm: EvmActor::spawn(EvmActor::new(key_holder.clone(), db)), vault: key_holder, flow_coordinator: FlowCoordinator::spawn(FlowCoordinator::new( - useragent_registry.clone(), + operator_registry.clone(), )), - useragent_registry, + operator_registry, events: message_bus, }) } diff --git a/server/crates/arbiter-server/src/actors/useragent_registry.rs b/server/crates/arbiter-server/src/actors/operator_registry.rs similarity index 65% rename from server/crates/arbiter-server/src/actors/useragent_registry.rs rename to server/crates/arbiter-server/src/actors/operator_registry.rs index 15e6080..f25ebbc 100644 --- a/server/crates/arbiter-server/src/actors/useragent_registry.rs +++ b/server/crates/arbiter-server/src/actors/operator_registry.rs @@ -1,4 +1,4 @@ -use crate::peers::user_agent::UserAgentSession; +use crate::peers::operator::OperatorSession; use kameo::{ Actor, @@ -11,11 +11,11 @@ use std::{collections::HashMap, ops::ControlFlow}; use tracing::info; #[derive(Default)] -pub struct UserAgentRegistry { - connected: HashMap>, +pub struct OperatorRegistry { + connected: HashMap>, } -impl Actor for UserAgentRegistry { +impl Actor for OperatorRegistry { type Args = Self; type Error = Infallible; @@ -33,8 +33,8 @@ impl Actor for UserAgentRegistry { if self.connected.remove(&id).is_some() { info!( ?id, - actor = "UserAgentRegistry", - event = "useragent.disconnected" + actor = "OperatorRegistry", + event = "operator.disconnected" ); } Ok(ControlFlow::Continue(())) @@ -42,20 +42,20 @@ impl Actor for UserAgentRegistry { } #[messages] -impl UserAgentRegistry { +impl OperatorRegistry { #[message(ctx)] - pub async fn connect_useragent( + pub async fn connect_operator( &mut self, - actor: ActorRef, + actor: ActorRef, ctx: &mut Context, ) { - info!(id = %actor.id(), actor = "UserAgentRegistry", event = "useragent.connected"); + info!(id = %actor.id(), actor = "OperatorRegistry", event = "operator.connected"); ctx.actor_ref().link(&actor).await; self.connected.insert(actor.id(), actor); } #[message] - pub fn get_connected(&self) -> Vec> { + pub fn get_connected(&self) -> Vec> { self.connected.values().cloned().collect() } } diff --git a/server/crates/arbiter-server/src/db/models.rs b/server/crates/arbiter-server/src/db/models.rs index 66c3d9d..94fdc5b 100644 --- a/server/crates/arbiter-server/src/db/models.rs +++ b/server/crates/arbiter-server/src/db/models.rs @@ -248,8 +248,8 @@ pub struct ProgramClient { } #[derive(Queryable, Debug)] -#[diesel(table_name = schema::useragent_client, check_for_backend(Sqlite))] -pub struct UseragentClient { +#[diesel(table_name = schema::operator_client, check_for_backend(Sqlite))] +pub struct OperatorClient { pub id: i32, pub public_key: Vec, pub created_at: SqliteTimestamp, diff --git a/server/crates/arbiter-server/src/db/schema.rs b/server/crates/arbiter-server/src/db/schema.rs index 6d1c6b2..79d6126 100644 --- a/server/crates/arbiter-server/src/db/schema.rs +++ b/server/crates/arbiter-server/src/db/schema.rs @@ -186,7 +186,7 @@ diesel::table! { } diesel::table! { - useragent_client (id) { + operator_client (id) { id -> Integer, public_key -> Binary, created_at -> Integer, @@ -233,5 +233,5 @@ diesel::allow_tables_to_appear_in_same_query!( program_client, root_key_history, tls_history, - useragent_client, + operator_client, ); diff --git a/server/crates/arbiter-server/src/grpc/client/auth.rs b/server/crates/arbiter-server/src/grpc/client/auth.rs index 7e4dd1c..25399cd 100644 --- a/server/crates/arbiter-server/src/grpc/client/auth.rs +++ b/server/crates/arbiter-server/src/grpc/client/auth.rs @@ -176,8 +176,8 @@ impl Convert for auth::Error { InvalidChallengeSolution => ProtoAuthResult::InvalidSignature, ApproveError(auth::ApproveError::Denied) => ProtoAuthResult::ApprovalDenied, ApproveError(auth::ApproveError::Upstream( - crate::actors::flow_coordinator::ApprovalError::NoUserAgentsConnected, - )) => ProtoAuthResult::NoUserAgentsOnline, + crate::actors::flow_coordinator::ApprovalError::NoOperatorsConnected, + )) => ProtoAuthResult::NoOperatorsOnline, ApproveError(auth::ApproveError::Internal) | DatabasePoolUnavailable | DatabaseOperationFailed diff --git a/server/crates/arbiter-server/src/grpc/mod.rs b/server/crates/arbiter-server/src/grpc/mod.rs index 4e0ed8f..fcd403b 100644 --- a/server/crates/arbiter-server/src/grpc/mod.rs +++ b/server/crates/arbiter-server/src/grpc/mod.rs @@ -1,8 +1,8 @@ -use crate::peers::{client::ClientConnection, user_agent::UserAgentConnection}; +use crate::peers::{client::ClientConnection, operator::OperatorConnection}; use arbiter_proto::{ proto::{ client::{ClientRequest, ClientResponse}, - user_agent::{UserAgentRequest, UserAgentResponse}, + operator::{OperatorRequest, OperatorResponse}, }, transport::grpc::GrpcBi, }; @@ -14,7 +14,7 @@ use tracing::info; mod request_tracker; pub mod client; -pub mod user_agent; +pub mod operator; mod common; @@ -33,7 +33,7 @@ pub trait TryConvert { #[async_trait] impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Server { - type UserAgentStream = ReceiverStream>; + type OperatorStream = ReceiverStream>; type ClientStream = ReceiverStream>; #[tracing::instrument(level = "debug", skip(self))] @@ -52,23 +52,23 @@ impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Ser } #[tracing::instrument(level = "debug", skip(self))] - async fn user_agent( + async fn operator( &self, - request: Request>, - ) -> Result, Status> { + request: Request>, + ) -> Result, Status> { let req_stream = request.into_inner(); let (bi, rx) = GrpcBi::from_bi_stream(req_stream); - tokio::spawn(user_agent::start( - UserAgentConnection { + tokio::spawn(operator::start( + OperatorConnection { db: self.context.db.clone(), actors: self.context.actors.clone(), }, bi, )); - info!(event = "connection established", "grpc.user_agent"); + info!(event = "connection established", "grpc.operator"); Ok(Response::new(rx)) } diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/operator.rs similarity index 65% rename from server/crates/arbiter-server/src/grpc/user_agent.rs rename to server/crates/arbiter-server/src/grpc/operator.rs index fc64017..cdd4e80 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent.rs +++ b/server/crates/arbiter-server/src/grpc/operator.rs @@ -1,12 +1,12 @@ use crate::{ grpc::request_tracker::RequestTracker, - peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession}, + peers::operator::{OutOfBand, OperatorConnection, OperatorSession}, }; use arbiter_proto::{ - proto::user_agent::{ - UserAgentRequest, UserAgentResponse, - user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, + proto::operator::{ + OperatorRequest, OperatorResponse, + operator_request::Payload as OperatorRequestPayload, + operator_response::Payload as OperatorResponsePayload, }, transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi}, }; @@ -38,8 +38,8 @@ impl Sender for OutOfBandAdapter { } async fn dispatch_loop( - mut bi: GrpcBi, - actor: ActorRef, + mut bi: GrpcBi, + actor: ActorRef, mut receiver: mpsc::Receiver, mut request_tracker: RequestTracker, ) { @@ -53,7 +53,7 @@ async fn dispatch_loop( let payload = sdk_client::out_of_band_payload(oob); - if bi.send(Ok(UserAgentResponse { id: None, payload: Some(payload) })).await.is_err() { + if bi.send(Ok(OperatorResponse { id: None, payload: Some(payload) })).await.is_err() { return; } } @@ -64,7 +64,7 @@ async fn dispatch_loop( let conn = match message { Ok(conn) => conn, Err(err) => { - warn!(error = ?err, "Failed to receive user agent request"); + warn!(error = ?err, "Failed to receive operator request"); return; } }; @@ -78,13 +78,13 @@ async fn dispatch_loop( }; let Some(payload) = conn.payload else { - let _ = bi.send(Err(Status::invalid_argument("Missing user-agent request payload"))).await; + let _ = bi.send(Err(Status::invalid_argument("Missing operator request payload"))).await; return; }; match dispatch_inner(&actor, payload).await { Ok(Some(response)) => { - if bi.send(Ok(UserAgentResponse { + if bi.send(Ok(OperatorResponse { id: Some(request_id), payload: Some(response), })).await.is_err() { @@ -93,7 +93,7 @@ async fn dispatch_loop( } Ok(None) => {} Err(status) => { - error!(?status, "Failed to process user agent request"); + error!(?status, "Failed to process operator request"); let _ = bi.send(Err(status)).await; return; } @@ -104,23 +104,23 @@ async fn dispatch_loop( } async fn dispatch_inner( - actor: &ActorRef, - payload: UserAgentRequestPayload, -) -> Result, Status> { + actor: &ActorRef, + payload: OperatorRequestPayload, +) -> Result, Status> { match payload { - UserAgentRequestPayload::Vault(req) => vault::dispatch(actor, req).await, - UserAgentRequestPayload::Evm(req) => evm::dispatch(actor, req).await, - UserAgentRequestPayload::SdkClient(req) => sdk_client::dispatch(actor, req).await, - UserAgentRequestPayload::Auth(..) => { - warn!("Unsupported post-auth user agent auth request"); - Err(Status::invalid_argument("Unsupported user-agent request")) + OperatorRequestPayload::Vault(req) => vault::dispatch(actor, req).await, + OperatorRequestPayload::Evm(req) => evm::dispatch(actor, req).await, + OperatorRequestPayload::SdkClient(req) => sdk_client::dispatch(actor, req).await, + OperatorRequestPayload::Auth(..) => { + warn!("Unsupported post-auth operator auth request"); + Err(Status::invalid_argument("Unsupported operator request")) } } } pub async fn start( - mut conn: UserAgentConnection, - mut bi: GrpcBi, + mut conn: OperatorConnection, + mut bi: GrpcBi, ) { let mut request_tracker = RequestTracker::default(); @@ -129,16 +129,16 @@ pub async fn start( let actor = { let transport = auth::AuthTransportAdapter::new(&mut bi, &mut request_tracker); - match crate::peers::user_agent::start(&mut conn, transport, Box::new(oob_adapter)).await { + match crate::peers::operator::start(&mut conn, transport, Box::new(oob_adapter)).await { Ok(actor) => actor, Err(e) => { - warn!(error = ?e, "User agent connection failed"); + warn!(error = ?e, "Operator connection failed"); return; } } }; - info!("User agent session established"); + info!("Operator session established"); dispatch_loop(bi, actor.clone(), oob_receiver, request_tracker).await; actor.kill(); diff --git a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs b/server/crates/arbiter-server/src/grpc/operator/auth.rs similarity index 81% rename from server/crates/arbiter-server/src/grpc/user_agent/auth.rs rename to server/crates/arbiter-server/src/grpc/operator/auth.rs index 5ecdd30..a900e0b 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/auth.rs +++ b/server/crates/arbiter-server/src/grpc/operator/auth.rs @@ -1,16 +1,16 @@ -use crate::{grpc::request_tracker::RequestTracker, peers::user_agent::auth}; +use crate::{grpc::request_tracker::RequestTracker, peers::operator::auth}; use arbiter_crypto::authn; use arbiter_proto::{ - proto::user_agent::{ - UserAgentRequest, UserAgentResponse, + proto::operator::{ + OperatorRequest, OperatorResponse, auth::{ self as proto_auth, AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest, AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult, request::Payload as AuthRequestPayload, response::Payload as AuthResponsePayload, }, - user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, + operator_request::Payload as OperatorRequestPayload, + operator_response::Payload as OperatorResponsePayload, }, transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi}, }; @@ -20,13 +20,13 @@ use tonic::Status; use tracing::warn; pub(super) struct AuthTransportAdapter<'a> { - pub(super) bi: &'a mut GrpcBi, + pub(super) bi: &'a mut GrpcBi, pub(super) request_tracker: &'a mut RequestTracker, } impl<'a> AuthTransportAdapter<'a> { pub(super) const fn new( - bi: &'a mut GrpcBi, + bi: &'a mut GrpcBi, request_tracker: &'a mut RequestTracker, ) -> Self { Self { @@ -35,7 +35,7 @@ impl<'a> AuthTransportAdapter<'a> { } } - pub(super) const fn bi_mut(&mut self) -> &mut GrpcBi { + pub(super) const fn bi_mut(&mut self) -> &mut GrpcBi { self.bi } @@ -45,21 +45,21 @@ impl<'a> AuthTransportAdapter<'a> { pub(super) async fn send_response_payload( &mut self, - payload: UserAgentResponsePayload, + payload: OperatorResponsePayload, ) -> Result<(), TransportError> { self.bi - .send(Ok(UserAgentResponse { + .send(Ok(OperatorResponse { id: Some(self.request_tracker.current_request_id()), payload: Some(payload), })) .await } - async fn send_user_agent_response( + async fn send_operator_response( &mut self, payload: AuthResponsePayload, ) -> Result<(), TransportError> { - self.send_response_payload(UserAgentResponsePayload::Auth(proto_auth::Response { + self.send_response_payload(OperatorResponsePayload::Auth(proto_auth::Response { payload: Some(payload), })) .await @@ -107,7 +107,7 @@ impl Sender> for AuthTransportAdapter<'_> { } }; - self.send_user_agent_response(payload).await + self.send_operator_response(payload).await } } @@ -117,7 +117,7 @@ impl Receiver for AuthTransportAdapter<'_> { let request = match self.bi.recv().await? { Ok(request) => request, Err(error) => { - warn!(error = ?error, "Failed to receive user agent auth request"); + warn!(error = ?error, "Failed to receive operator auth request"); return None; } }; @@ -133,16 +133,16 @@ impl Receiver for AuthTransportAdapter<'_> { let Some(payload) = request.payload else { warn!( event = "received request with empty payload", - "grpc.useragent.auth_adapter" + "grpc.operator.auth_adapter" ); return None; }; - let UserAgentRequestPayload::Auth(auth_request) = payload else { + let OperatorRequestPayload::Auth(auth_request) = payload else { let _ = self .bi .send(Err(Status::invalid_argument( - "Unsupported user-agent auth request", + "Unsupported operator auth request", ))) .await; return None; @@ -151,7 +151,7 @@ impl Receiver for AuthTransportAdapter<'_> { let Some(payload) = auth_request.payload else { warn!( event = "received auth request with empty payload", - "grpc.useragent.auth_adapter" + "grpc.operator.auth_adapter" ); return None; }; @@ -164,7 +164,7 @@ impl Receiver for AuthTransportAdapter<'_> { let Ok(pubkey) = authn::PublicKey::try_from(pubkey.as_slice()) else { warn!( event = "received request with invalid public key", - "grpc.useragent.auth_adapter" + "grpc.operator.auth_adapter" ); return None; }; diff --git a/server/crates/arbiter-server/src/grpc/user_agent/evm.rs b/server/crates/arbiter-server/src/grpc/operator/evm.rs similarity index 89% rename from server/crates/arbiter-server/src/grpc/user_agent/evm.rs rename to server/crates/arbiter-server/src/grpc/operator/evm.rs index 536940c..0b3ac2c 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/evm.rs +++ b/server/crates/arbiter-server/src/grpc/operator/evm.rs @@ -3,8 +3,8 @@ use crate::{ Convert, TryConvert, common::inbound::{RawEvmAddress, RawEvmTransaction}, }, - peers::user_agent::{ - UserAgentSession, + peers::operator::{ + OperatorSession, session::handlers::{ GrantMutationError, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete, HandleGrantList, HandleSignTransaction, @@ -24,12 +24,12 @@ use arbiter_proto::proto::{ wallet_create_response::Result as WalletCreateResult, wallet_list_response::Result as WalletListResult, }, - user_agent::{ + operator::{ evm::{ self as proto_evm, SignTransactionRequest as ProtoSignTransactionRequest, request::Payload as EvmRequestPayload, response::Payload as EvmResponsePayload, }, - user_agent_response::Payload as UserAgentResponsePayload, + operator_response::Payload as OperatorResponsePayload, }, }; @@ -37,16 +37,16 @@ use kameo::actor::ActorRef; use tonic::Status; use tracing::warn; -const fn wrap_evm_response(payload: EvmResponsePayload) -> UserAgentResponsePayload { - UserAgentResponsePayload::Evm(proto_evm::Response { +const fn wrap_evm_response(payload: EvmResponsePayload) -> OperatorResponsePayload { + OperatorResponsePayload::Evm(proto_evm::Response { payload: Some(payload), }) } pub(super) async fn dispatch( - actor: &ActorRef, + actor: &ActorRef, req: proto_evm::Request, -) -> Result, Status> { +) -> Result, Status> { let Some(payload) = req.payload else { return Err(Status::invalid_argument("Missing EVM request payload")); }; @@ -62,8 +62,8 @@ pub(super) async fn dispatch( } async fn handle_wallet_create( - actor: &ActorRef, -) -> Result, Status> { + actor: &ActorRef, +) -> Result, Status> { let result = match actor.ask(HandleEvmWalletCreate {}).await { Ok((wallet_id, address)) => WalletCreateResult::Wallet(WalletEntry { id: wallet_id, @@ -82,8 +82,8 @@ async fn handle_wallet_create( } async fn handle_wallet_list( - actor: &ActorRef, -) -> Result, Status> { + actor: &ActorRef, +) -> Result, Status> { let result = match actor.ask(HandleEvmWalletList {}).await { Ok(wallets) => WalletListResult::Wallets(WalletList { wallets: wallets @@ -107,8 +107,8 @@ async fn handle_wallet_list( } async fn handle_grant_list( - actor: &ActorRef, -) -> Result, Status> { + actor: &ActorRef, +) -> Result, Status> { let result = match actor.ask(HandleGrantList {}).await { Ok(grants) => EvmGrantListResult::Grants(EvmGrantList { grants: grants @@ -134,9 +134,9 @@ async fn handle_grant_list( } async fn handle_grant_create( - actor: &ActorRef, + actor: &ActorRef, req: EvmGrantCreateRequest, -) -> Result, Status> { +) -> Result, Status> { let basic = req .shared .ok_or_else(|| Status::invalid_argument("Missing shared grant settings"))? @@ -164,9 +164,9 @@ async fn handle_grant_create( } async fn handle_grant_delete( - actor: &ActorRef, + actor: &ActorRef, req: EvmGrantDeleteRequest, -) -> Result, Status> { +) -> Result, Status> { let result = match actor .ask(HandleGrantDelete { grant_id: req.grant_id, @@ -190,9 +190,9 @@ async fn handle_grant_delete( } async fn handle_sign_transaction( - actor: &ActorRef, + actor: &ActorRef, req: ProtoSignTransactionRequest, -) -> Result, Status> { +) -> Result, Status> { let request = req .request .ok_or_else(|| Status::invalid_argument("Missing sign transaction request"))?; diff --git a/server/crates/arbiter-server/src/grpc/user_agent/inbound.rs b/server/crates/arbiter-server/src/grpc/operator/inbound.rs similarity index 98% rename from server/crates/arbiter-server/src/grpc/user_agent/inbound.rs rename to server/crates/arbiter-server/src/grpc/operator/inbound.rs index df555a2..7e2c87e 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/inbound.rs +++ b/server/crates/arbiter-server/src/grpc/operator/inbound.rs @@ -14,7 +14,7 @@ use arbiter_proto::{ TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit, specific_grant::Grant as ProtoSpecificGrantType, }, - proto::user_agent::sdk_client::{WalletAccess, WalletAccessEntry as SdkClientWalletAccess}, + proto::operator::sdk_client::{WalletAccess, WalletAccessEntry as SdkClientWalletAccess}, }; use alloy::primitives::{Address, U256}; diff --git a/server/crates/arbiter-server/src/grpc/user_agent/outbound.rs b/server/crates/arbiter-server/src/grpc/operator/outbound.rs similarity index 97% rename from server/crates/arbiter-server/src/grpc/user_agent/outbound.rs rename to server/crates/arbiter-server/src/grpc/operator/outbound.rs index e171b95..a4f4c29 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/outbound.rs +++ b/server/crates/arbiter-server/src/grpc/operator/outbound.rs @@ -10,7 +10,7 @@ use arbiter_proto::proto::{ TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit, specific_grant::Grant as ProtoSpecificGrantType, }, - user_agent::sdk_client::{WalletAccess, WalletAccessEntry as ProtoSdkClientWalletAccess}, + operator::sdk_client::{WalletAccess, WalletAccessEntry as ProtoSdkClientWalletAccess}, }; use chrono::{DateTime, Utc}; diff --git a/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs b/server/crates/arbiter-server/src/grpc/operator/sdk_client.rs similarity index 87% rename from server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs rename to server/crates/arbiter-server/src/grpc/operator/sdk_client.rs index 951f04c..b88a73e 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs +++ b/server/crates/arbiter-server/src/grpc/operator/sdk_client.rs @@ -1,8 +1,8 @@ use crate::{ db::models::NewEvmWalletAccess, grpc::Convert, - peers::user_agent::{ - OutOfBand, UserAgentSession, + peers::operator::{ + OutOfBand, OperatorSession, session::handlers::{ HandleGrantEvmWalletAccess, HandleListWalletAccess, HandleNewClientApprove, HandleRevokeEvmWalletAccess, HandleSdkClientList, @@ -12,7 +12,7 @@ use crate::{ use arbiter_crypto::authn; use arbiter_proto::proto::{ shared::ClientInfo as ProtoClientMetadata, - user_agent::{ + operator::{ sdk_client::{ self as proto_sdk_client, ConnectionCancel as ProtoSdkClientConnectionCancel, ConnectionRequest as ProtoSdkClientConnectionRequest, @@ -24,7 +24,7 @@ use arbiter_proto::proto::{ request::Payload as SdkClientRequestPayload, response::Payload as SdkClientResponsePayload, }, - user_agent_response::Payload as UserAgentResponsePayload, + operator_response::Payload as OperatorResponsePayload, }, }; @@ -32,13 +32,13 @@ use kameo::actor::ActorRef; use tonic::Status; use tracing::{info, warn}; -const fn wrap_sdk_client_response(payload: SdkClientResponsePayload) -> UserAgentResponsePayload { - UserAgentResponsePayload::SdkClient(proto_sdk_client::Response { +const fn wrap_sdk_client_response(payload: SdkClientResponsePayload) -> OperatorResponsePayload { + OperatorResponsePayload::SdkClient(proto_sdk_client::Response { payload: Some(payload), }) } -pub(super) fn out_of_band_payload(oob: OutOfBand) -> UserAgentResponsePayload { +pub(super) fn out_of_band_payload(oob: OutOfBand) -> OperatorResponsePayload { match oob { OutOfBand::ClientConnectionRequest { profile } => wrap_sdk_client_response( SdkClientResponsePayload::ConnectionRequest(ProtoSdkClientConnectionRequest { @@ -59,9 +59,9 @@ pub(super) fn out_of_band_payload(oob: OutOfBand) -> UserAgentResponsePayload { } pub(super) async fn dispatch( - actor: &ActorRef, + actor: &ActorRef, req: proto_sdk_client::Request, -) -> Result, Status> { +) -> Result, Status> { let Some(payload) = req.payload else { return Err(Status::invalid_argument( "Missing SDK client request payload", @@ -87,9 +87,9 @@ pub(super) async fn dispatch( } async fn handle_connection_response( - actor: &ActorRef, + actor: &ActorRef, resp: ProtoSdkClientConnectionResponse, -) -> Result, Status> { +) -> Result, Status> { let pubkey = authn::PublicKey::try_from(resp.pubkey.as_slice()) .map_err(|()| Status::invalid_argument("Invalid ML-DSA public key"))?; @@ -108,8 +108,8 @@ async fn handle_connection_response( } async fn handle_list( - actor: &ActorRef, -) -> Result, Status> { + actor: &ActorRef, +) -> Result, Status> { let result = match actor.ask(HandleSdkClientList {}).await { Ok(clients) => ProtoSdkClientListResult::Clients(ProtoSdkClientList { clients: clients @@ -144,9 +144,9 @@ async fn handle_list( } async fn handle_grant_wallet_access( - actor: &ActorRef, + actor: &ActorRef, req: ProtoSdkClientGrantWalletAccess, -) -> Result, Status> { +) -> Result, Status> { let entries: Vec = req.accesses.into_iter().map(Convert::convert).collect(); match actor.ask(HandleGrantEvmWalletAccess { entries }).await { Ok(()) => { @@ -161,9 +161,9 @@ async fn handle_grant_wallet_access( } async fn handle_revoke_wallet_access( - actor: &ActorRef, + actor: &ActorRef, req: ProtoSdkClientRevokeWalletAccess, -) -> Result, Status> { +) -> Result, Status> { match actor .ask(HandleRevokeEvmWalletAccess { entries: req.accesses, @@ -182,8 +182,8 @@ async fn handle_revoke_wallet_access( } async fn handle_list_wallet_access( - actor: &ActorRef, -) -> Result, Status> { + actor: &ActorRef, +) -> Result, Status> { match actor.ask(HandleListWalletAccess {}).await { Ok(accesses) => Ok(Some(wrap_sdk_client_response( SdkClientResponsePayload::ListWalletAccess(ListWalletAccessResponse { diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault.rs b/server/crates/arbiter-server/src/grpc/operator/vault.rs similarity index 73% rename from server/crates/arbiter-server/src/grpc/user_agent/vault.rs rename to server/crates/arbiter-server/src/grpc/operator/vault.rs index 042a536..ac1c293 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/vault.rs +++ b/server/crates/arbiter-server/src/grpc/operator/vault.rs @@ -1,11 +1,11 @@ use crate::{ actors::vault::VaultState, - peers::user_agent::{UserAgentSession, session::handlers::HandleQueryVaultState}, + peers::operator::{OperatorSession, session::handlers::HandleQueryVaultState}, }; use arbiter_proto::{ proto::shared::VaultState as ProtoVaultState, - proto::user_agent::{ - user_agent_response::Payload as UserAgentResponsePayload, + proto::operator::{ + operator_response::Payload as OperatorResponsePayload, vault::{ self as proto_vault, request::Payload as VaultRequestPayload, response::Payload as VaultResponsePayload, @@ -17,16 +17,16 @@ use kameo::actor::ActorRef; use tonic::Status; use tracing::warn; -const fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload { - UserAgentResponsePayload::Vault(proto_vault::Response { +const fn wrap_vault_response(payload: VaultResponsePayload) -> OperatorResponsePayload { + OperatorResponsePayload::Vault(proto_vault::Response { payload: Some(payload), }) } pub(super) async fn dispatch( - actor: &ActorRef, + actor: &ActorRef, req: proto_vault::Request, -) -> Result, Status> { +) -> Result, Status> { let Some(payload) = req.payload else { return Err(Status::invalid_argument("Missing vault request payload")); }; @@ -42,8 +42,8 @@ pub(super) async fn dispatch( } async fn handle_query_vault_state( - actor: &ActorRef, -) -> Result, Status> { + actor: &ActorRef, +) -> Result, Status> { let state = match actor.ask(HandleQueryVaultState {}).await { Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped, Ok(VaultState::Sealed) => ProtoVaultState::Sealed, diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs b/server/crates/arbiter-server/src/grpc/operator/vault_gate.rs similarity index 94% rename from server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs rename to server/crates/arbiter-server/src/grpc/operator/vault_gate.rs index bffa5cc..2d5bc6c 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate.rs +++ b/server/crates/arbiter-server/src/grpc/operator/vault_gate.rs @@ -1,7 +1,7 @@ use super::auth::AuthTransportAdapter; use crate::{ grpc::TryConvert, - peers::user_agent::vault_gate::{self as vault_gate}, + peers::operator::vault_gate::{self as vault_gate}, }; use arbiter_proto::transport::{Bi, Error as TransportError, Receiver, Sender}; @@ -20,7 +20,7 @@ impl Receiver for AuthTransportAdapter<'_> { Err(error) => { warn!( ?error, - "Failed to receive user agent request during vault gate" + "Failed to receive operator request during vault gate" ); return None; } diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/inbound.rs b/server/crates/arbiter-server/src/grpc/operator/vault_gate/inbound.rs similarity index 95% rename from server/crates/arbiter-server/src/grpc/user_agent/vault_gate/inbound.rs rename to server/crates/arbiter-server/src/grpc/operator/vault_gate/inbound.rs index 87d2b13..6a08235 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/inbound.rs +++ b/server/crates/arbiter-server/src/grpc/operator/vault_gate/inbound.rs @@ -1,11 +1,11 @@ use crate::{ grpc::{Convert, TryConvert}, - peers::user_agent::vault_gate::{ + peers::operator::vault_gate::{ self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey, }, }; -use arbiter_proto::proto::user_agent::{ - user_agent_request::Payload as UserAgentRequestPayload, +use arbiter_proto::proto::operator::{ + operator_request::Payload as OperatorRequestPayload, vault::{ self as proto_vault, bootstrap::{self as proto_bootstrap}, @@ -16,7 +16,7 @@ use arbiter_proto::proto::user_agent::{ use tonic::Status; -impl TryConvert for UserAgentRequestPayload { +impl TryConvert for OperatorRequestPayload { type Output = vault_gate::Inbound; type Error = Status; diff --git a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/outbound.rs b/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs similarity index 83% rename from server/crates/arbiter-server/src/grpc/user_agent/vault_gate/outbound.rs rename to server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs index 97dbd1b..4a2f072 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent/vault_gate/outbound.rs +++ b/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs @@ -1,12 +1,12 @@ use crate::{ actors::vault::VaultState, grpc::{Convert, TryConvert}, - peers::user_agent::vault_gate::{self as vault_gate}, + peers::operator::vault_gate::{self as vault_gate}, }; use arbiter_proto::proto::{ shared::VaultState as ProtoVaultState, - user_agent::{ - user_agent_response::Payload as UserAgentResponsePayload, + operator::{ + operator_response::Payload as OperatorResponsePayload, vault::{ self as proto_vault, bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult}, @@ -22,28 +22,28 @@ use arbiter_proto::proto::{ use tonic::Status; use tracing::warn; -const fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload { - UserAgentResponsePayload::Vault(proto_vault::Response { +const fn wrap_vault_response(payload: VaultResponsePayload) -> OperatorResponsePayload { + OperatorResponsePayload::Vault(proto_vault::Response { payload: Some(payload), }) } -const fn wrap_unseal_response(payload: UnsealResponsePayload) -> UserAgentResponsePayload { +const fn wrap_unseal_response(payload: UnsealResponsePayload) -> OperatorResponsePayload { wrap_vault_response(VaultResponsePayload::Unseal(proto_unseal::Response { payload: Some(payload), })) } -fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> UserAgentResponsePayload { +fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> OperatorResponsePayload { wrap_vault_response(VaultResponsePayload::Bootstrap(proto_bootstrap::Response { result: result.into(), })) } impl Convert for VaultState { - type Output = UserAgentResponsePayload; + type Output = OperatorResponsePayload; - fn convert(self) -> UserAgentResponsePayload { + fn convert(self) -> OperatorResponsePayload { let proto_state = match self { Self::Unbootstrapped => ProtoVaultState::Unbootstrapped, Self::Sealed => ProtoVaultState::Sealed, @@ -54,9 +54,9 @@ impl Convert for VaultState { } impl Convert for vault_gate::HandshakeResponse { - type Output = UserAgentResponsePayload; + type Output = OperatorResponsePayload; - fn convert(self) -> UserAgentResponsePayload { + fn convert(self) -> OperatorResponsePayload { wrap_unseal_response(UnsealResponsePayload::Start( proto_unseal::UnsealStartResponse { server_pubkey: self.server_pubkey.as_bytes().to_vec(), @@ -66,10 +66,10 @@ impl Convert for vault_gate::HandshakeResponse { } impl TryConvert for vault_gate::Outbound { - type Output = UserAgentResponsePayload; + type Output = OperatorResponsePayload; type Error = Status; - fn try_convert(self) -> Result { + fn try_convert(self) -> Result { match self { Self::HandleVaultState(result) => result .map_err(|err| { diff --git a/server/crates/arbiter-server/src/peers/client/auth.rs b/server/crates/arbiter-server/src/peers/client/auth.rs index ef58dd8..5e2b5b1 100644 --- a/server/crates/arbiter-server/src/peers/client/auth.rs +++ b/server/crates/arbiter-server/src/peers/client/auth.rs @@ -54,7 +54,7 @@ impl From for Error { pub enum ApproveError { #[error("Internal error")] Internal, - #[error("Client connection denied by user agents")] + #[error("Client connection denied by operators")] Denied, #[error("Upstream error: {0}")] Upstream(flow_coordinator::ApprovalError), diff --git a/server/crates/arbiter-server/src/peers/mod.rs b/server/crates/arbiter-server/src/peers/mod.rs index 02b992f..70091d0 100644 --- a/server/crates/arbiter-server/src/peers/mod.rs +++ b/server/crates/arbiter-server/src/peers/mod.rs @@ -1,2 +1,2 @@ pub mod client; -pub mod user_agent; +pub mod operator; diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs b/server/crates/arbiter-server/src/peers/operator/auth/mod.rs similarity index 97% rename from server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs rename to server/crates/arbiter-server/src/peers/operator/auth/mod.rs index 3cb6725..8bea8a0 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth/mod.rs +++ b/server/crates/arbiter-server/src/peers/operator/auth/mod.rs @@ -1,4 +1,4 @@ -use super::{Credentials, UserAgentConnection}; +use super::{Credentials, OperatorConnection}; use arbiter_crypto::authn::{self, AuthChallenge}; use arbiter_proto::transport::Bi; @@ -69,7 +69,7 @@ fn parse_auth_event(payload: Inbound) -> AuthEvents { } pub async fn authenticate( - props: &mut UserAgentConnection, + props: &mut OperatorConnection, transport: &mut T, ) -> Result where diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs b/server/crates/arbiter-server/src/peers/operator/auth/state.rs similarity index 88% rename from server/crates/arbiter-server/src/peers/user_agent/auth/state.rs rename to server/crates/arbiter-server/src/peers/operator/auth/state.rs index fe18f81..d4033f5 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/peers/operator/auth/state.rs @@ -1,13 +1,13 @@ use super::{ - super::{Credentials, UserAgentConnection}, + super::{Credentials, OperatorConnection}, Error, }; use crate::{ actors::bootstrap::ConsumeToken, - db::{DatabasePool, schema::useragent_client}, - peers::user_agent::auth::Outbound, + db::{DatabasePool, schema::operator_client}, + peers::operator::auth::Outbound, }; -use arbiter_crypto::authn::{self, AuthChallenge, USERAGENT_CONTEXT}; +use arbiter_crypto::authn::{self, AuthChallenge, OPERATOR_CONTEXT}; use arbiter_proto::transport::Bi; use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl}; @@ -44,9 +44,9 @@ async fn get_client_id(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result(&mut conn) .await .optional() @@ -63,9 +63,9 @@ async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result Result { - pub(super) conn: &'a mut UserAgentConnection, + pub(super) conn: &'a mut OperatorConnection, pub(super) transport: &'a mut T, } impl<'a, T: ?Sized> AuthContext<'a, T> { - pub(super) const fn new(conn: &'a mut UserAgentConnection, transport: &'a mut T) -> Self { + pub(super) const fn new(conn: &'a mut OperatorConnection, transport: &'a mut T) -> Self { Self { conn, transport } } } @@ -143,7 +143,7 @@ where Error::InvalidChallengeSolution })?; - let valid = pubkey.verify(challenge, USERAGENT_CONTEXT, &signature); + let valid = pubkey.verify(challenge, OPERATOR_CONTEXT, &signature); if !valid { self.transport diff --git a/server/crates/arbiter-server/src/peers/user_agent/mod.rs b/server/crates/arbiter-server/src/peers/operator/mod.rs similarity index 92% rename from server/crates/arbiter-server/src/peers/user_agent/mod.rs rename to server/crates/arbiter-server/src/peers/operator/mod.rs index 3662f95..0869d51 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/mod.rs +++ b/server/crates/arbiter-server/src/peers/operator/mod.rs @@ -17,7 +17,7 @@ use tokio::sync::oneshot; use tracing::{error, warn}; pub use auth::authenticate; -pub use session::UserAgentSession; +pub use session::OperatorSession; pub mod auth; pub mod session; @@ -30,10 +30,10 @@ pub struct Credentials { } impl Integrable for Credentials { - const KIND: &'static str = "useragent_credentials"; + const KIND: &'static str = "operator_credentials"; } -// Messages, sent by user agent to connection client without having a request +// Messages, sent by operator to connection client without having a request #[derive(Debug)] pub enum OutOfBand { ClientConnectionRequest { profile: ClientProfile }, @@ -41,12 +41,12 @@ pub enum OutOfBand { } #[derive(Clone)] -pub struct UserAgentConnection { +pub struct OperatorConnection { pub(crate) db: DatabasePool, pub(crate) actors: GlobalActors, } -impl UserAgentConnection { +impl OperatorConnection { pub const fn new(db: DatabasePool, actors: GlobalActors) -> Self { Self { db, actors } } @@ -106,7 +106,7 @@ async fn should_run_gate(vault: &ActorRef) -> Result { } async fn run_vault_gate( - props: &UserAgentConnection, + props: &OperatorConnection, transport: &mut T, auth_creds: Credentials, ) -> Result<(), Error> @@ -160,10 +160,10 @@ where } pub async fn start( - props: &mut UserAgentConnection, + props: &mut OperatorConnection, mut transport: T, oob_sender: Box>, -) -> Result, Error> +) -> Result, Error> where T: Bi> + Send, T: Bi> + Send, @@ -178,7 +178,7 @@ where // checking the integrity verify_integrity(&props.db, &props.actors.vault, &creds).await?; - Ok(UserAgentSession::spawn(UserAgentSession::new( + Ok(OperatorSession::spawn(OperatorSession::new( props.clone(), oob_sender, ))) diff --git a/server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs b/server/crates/arbiter-server/src/peers/operator/session/handlers.rs similarity index 93% rename from server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs rename to server/crates/arbiter-server/src/peers/operator/session/handlers.rs index b17cedf..86a422e 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/session/handlers.rs +++ b/server/crates/arbiter-server/src/peers/operator/session/handlers.rs @@ -1,8 +1,8 @@ -use super::{Error, UserAgentSession}; +use super::{Error, OperatorSession}; use crate::{ actors::evm::{ ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError, - UseragentCreateGrant, UseragentListGrants, + OperatorCreateGrant, OperatorListGrants, }, actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer, actors::vault::VaultState, @@ -36,7 +36,7 @@ pub enum GrantMutationError { } #[messages] -impl UserAgentSession { +impl OperatorSession { #[message] pub(crate) async fn handle_query_vault_state(&mut self) -> Result { use crate::actors::vault::GetState; @@ -44,7 +44,7 @@ impl UserAgentSession { let vault_state = match self.props.actors.vault.ask(GetState {}).await { Ok(state) => state, Err(err) => { - error!(?err, actor = "useragent", "vault.query.failed"); + error!(?err, actor = "operator", "vault.query.failed"); return Err(Error::internal("Vault is in broken state")); } }; @@ -54,7 +54,7 @@ impl UserAgentSession { } #[messages] -impl UserAgentSession { +impl OperatorSession { #[message] pub(crate) async fn handle_evm_wallet_create(&mut self) -> Result<(i32, Address), Error> { match self.props.actors.evm.ask(Generate {}).await { @@ -82,10 +82,10 @@ impl UserAgentSession { } #[messages] -impl UserAgentSession { +impl OperatorSession { #[message] pub(crate) async fn handle_grant_list(&mut self) -> Result>, Error> { - match self.props.actors.evm.ask(UseragentListGrants {}).await { + match self.props.actors.evm.ask(OperatorListGrants {}).await { Ok(grants) => Ok(grants), Err(err) => { error!(?err, "EVM grant list failed"); @@ -104,7 +104,7 @@ impl UserAgentSession { .props .actors .evm - .ask(UseragentCreateGrant { basic, grant }) + .ask(OperatorCreateGrant { basic, grant }) .await { Ok(grant_id) => Ok(grant_id), @@ -121,7 +121,7 @@ impl UserAgentSession { // .props // .actors // .evm - // .ask(UseragentDeleteGrant { grant_id }) + // .ask(OperatorDeleteGrant { grant_id }) // .await // { // Ok(()) => Ok(()), @@ -157,7 +157,7 @@ impl UserAgentSession { Err(SignTransactionError::Vet(vet_error)) } Err(err) => { - error!(?err, "EVM sign transaction failed in user-agent session"); + error!(?err, "EVM sign transaction failed in operator session"); Err(SignTransactionError::Internal) } } @@ -226,7 +226,7 @@ impl UserAgentSession { } #[messages] -impl UserAgentSession { +impl OperatorSession { #[message(ctx)] pub(crate) async fn handle_new_client_approve( &mut self, diff --git a/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs b/server/crates/arbiter-server/src/peers/operator/session/mod.rs similarity index 84% rename from server/crates/arbiter-server/src/peers/user_agent/session/mod.rs rename to server/crates/arbiter-server/src/peers/operator/session/mod.rs index a905eb6..79281bb 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/session/mod.rs +++ b/server/crates/arbiter-server/src/peers/operator/session/mod.rs @@ -1,8 +1,8 @@ -use super::{OutOfBand, UserAgentConnection}; +use super::{OutOfBand, OperatorConnection}; use crate::{ actors::{ flow_coordinator::client_connect_approval::ClientApprovalController, - useragent_registry::ConnectUseragent, + operator_registry::ConnectOperator, }, peers::client::ClientProfile, }; @@ -49,8 +49,8 @@ pub struct PendingClientApproval { controller: ActorRef, } -pub struct UserAgentSession { - props: UserAgentConnection, +pub struct OperatorSession { + props: OperatorConnection, sender: Box>, pending_client_approvals: HashMap, PendingClientApproval>, @@ -58,8 +58,8 @@ pub struct UserAgentSession { pub mod handlers; -impl UserAgentSession { - pub(crate) fn new(props: UserAgentConnection, sender: Box>) -> Self { +impl OperatorSession { + pub(crate) fn new(props: OperatorConnection, sender: Box>) -> Self { Self { props, sender, @@ -69,7 +69,7 @@ impl UserAgentSession { } #[messages] -impl UserAgentSession { +impl OperatorSession { #[message] pub async fn begin_new_client_approval( &mut self, @@ -85,7 +85,7 @@ impl UserAgentSession { { error!( ?e, - actor = "user_agent", + actor = "operator", event = "failed to announce new client connection" ); return; @@ -101,7 +101,7 @@ impl UserAgentSession { } } -impl Actor for UserAgentSession { +impl Actor for OperatorSession { type Args = Self; type Error = Error; @@ -109,17 +109,17 @@ impl Actor for UserAgentSession { async fn on_start(args: Self::Args, this: ActorRef) -> Result { args.props .actors - .useragent_registry - .ask(ConnectUseragent { + .operator_registry + .ask(ConnectOperator { actor: this.clone(), }) .await .map_err(|err| { error!( ?err, - "Failed to register user agent connection with user agent registry" + "Failed to register operator connection with operator registry" ); - Error::internal("Failed to register user agent connection with user agent registry") + Error::internal("Failed to register operator connection with operator registry") })?; Ok(args) } @@ -149,7 +149,7 @@ impl Actor for UserAgentSession { { error!( ?e, - actor = "user_agent", + actor = "operator", event = "failed to announce client connection cancellation" ); } diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs b/server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs similarity index 100% rename from server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs rename to server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs b/server/crates/arbiter-server/src/peers/operator/vault_gate/state.rs similarity index 100% rename from server/crates/arbiter-server/src/peers/user_agent/vault_gate/state.rs rename to server/crates/arbiter-server/src/peers/operator/vault_gate/state.rs diff --git a/server/crates/arbiter-server/tests/client/auth.rs b/server/crates/arbiter-server/tests/client/auth.rs index 0523578..90763a8 100644 --- a/server/crates/arbiter-server/tests/client/auth.rs +++ b/server/crates/arbiter-server/tests/client/auth.rs @@ -79,22 +79,22 @@ fn sign_client_challenge(key: &SigningKey, challenge: &AuthChallenge) - .into() } -async fn insert_bootstrap_sentinel_useragent(db: &db::DatabasePool) { +async fn insert_bootstrap_sentinel_operator(db: &db::DatabasePool) { let mut conn = db.get().await.unwrap(); let sentinel_key = verifying_key(&MlDsa87::key_gen(&mut rand::rng())) .encode() .0 .to_vec(); - insert_into(schema::useragent_client::table) - .values((schema::useragent_client::public_key.eq(sentinel_key),)) + insert_into(schema::operator_client::table) + .values((schema::operator_client::public_key.eq(sentinel_key),)) .execute(&mut conn) .await .unwrap(); } async fn spawn_test_actors(db: &db::DatabasePool) -> GlobalActors { - insert_bootstrap_sentinel_useragent(db).await; + insert_bootstrap_sentinel_operator(db).await; let actors = GlobalActors::spawn(db.clone()).await.unwrap(); actors diff --git a/server/crates/arbiter-server/tests/operator.rs b/server/crates/arbiter-server/tests/operator.rs new file mode 100644 index 0000000..d6ebe48 --- /dev/null +++ b/server/crates/arbiter-server/tests/operator.rs @@ -0,0 +1,6 @@ +mod common; + +#[path = "operator/auth.rs"] +mod auth; +#[path = "operator/unseal.rs"] +mod unseal; diff --git a/server/crates/arbiter-server/tests/user_agent/auth.rs b/server/crates/arbiter-server/tests/operator/auth.rs similarity index 87% rename from server/crates/arbiter-server/tests/user_agent/auth.rs rename to server/crates/arbiter-server/tests/operator/auth.rs index a0d5192..e9e585d 100644 --- a/server/crates/arbiter-server/tests/user_agent/auth.rs +++ b/server/crates/arbiter-server/tests/operator/auth.rs @@ -1,6 +1,6 @@ use super::common::ChannelTransport; use arbiter_crypto::{ - authn::{self, AuthChallenge, USERAGENT_CONTEXT}, + authn::{self, AuthChallenge, OPERATOR_CONTEXT}, safecell::{SafeCell, SafeCellHandle as _}, }; use arbiter_proto::transport::{Error as TransportError, Receiver, Sender}; @@ -8,7 +8,7 @@ use arbiter_server::{ actors::{GlobalActors, bootstrap::GetToken, vault::Bootstrap}, crypto::integrity, db::{self, schema}, - peers::user_agent::{self, Credentials, UserAgentConnection, auth, vault_gate}, + peers::operator::{self, Credentials, OperatorConnection, auth, vault_gate}, }; use async_trait::async_trait; @@ -21,13 +21,13 @@ fn verifying_key(key: &SigningKey) -> VerifyingKey { as Keypair>::verifying_key(key) } -fn sign_useragent_challenge( +fn sign_operator_challenge( key: &SigningKey, challenge: &AuthChallenge, ) -> authn::Signature { let challenge = challenge.format(); key.signing_key() - .sign_deterministic(&challenge, USERAGENT_CONTEXT) + .sign_deterministic(&challenge, OPERATOR_CONTEXT) .unwrap() .into() } @@ -41,8 +41,8 @@ fn tamper_challenge(challenge: &AuthChallenge) -> AuthChallenge { struct NullOobSender; #[async_trait] -impl Sender for NullOobSender { - async fn send(&mut self, _item: user_agent::OutOfBand) -> Result<(), TransportError> { +impl Sender for NullOobSender { + async fn send(&mut self, _item: operator::OutOfBand) -> Result<(), TransportError> { Ok(()) } } @@ -166,7 +166,7 @@ pub async fn bootstrap_token_auth() { let (mut server_transport, mut test_transport) = ChannelTransport::new(); let db_for_task = db.clone(); let task = tokio::spawn(async move { - let mut props = UserAgentConnection::new(db_for_task, actors); + let mut props = OperatorConnection::new(db_for_task, actors); auth::authenticate(&mut props, &mut server_transport).await }); @@ -188,7 +188,7 @@ pub async fn bootstrap_token_auth() { other => panic!("Expected AuthChallenge, got {other:?}"), }; - let signature = sign_useragent_challenge(&new_key, &challenge); + let signature = sign_operator_challenge(&new_key, &challenge); test_transport .send(auth::Inbound::AuthChallengeSolution { @@ -206,8 +206,8 @@ pub async fn bootstrap_token_auth() { task.await.unwrap().unwrap(); let mut conn = db.get().await.unwrap(); - let stored_pubkey: Vec = schema::useragent_client::table - .select(schema::useragent_client::public_key) + let stored_pubkey: Vec = schema::operator_client::table + .select(schema::operator_client::public_key) .first::>(&mut conn) .await .unwrap(); @@ -223,7 +223,7 @@ pub async fn bootstrap_invalid_token_auth() { let (mut server_transport, mut test_transport) = ChannelTransport::new(); let db_for_task = db.clone(); let task = tokio::spawn(async move { - let mut props = UserAgentConnection::new(db_for_task, actors); + let mut props = OperatorConnection::new(db_for_task, actors); auth::authenticate(&mut props, &mut server_transport).await }); @@ -245,7 +245,7 @@ pub async fn bootstrap_invalid_token_auth() { other => panic!("Expected AuthChallenge, got {other:?}"), }; - let signature = sign_useragent_challenge(&new_key, &challenge); + let signature = sign_operator_challenge(&new_key, &challenge); test_transport .send(auth::Inbound::AuthChallengeSolution { signature: signature.to_bytes(), @@ -259,7 +259,7 @@ pub async fn bootstrap_invalid_token_auth() { )); let mut conn = db.get().await.unwrap(); - let count: i64 = schema::useragent_client::table + let count: i64 = schema::operator_client::table .count() .get_result::(&mut conn) .await @@ -285,9 +285,9 @@ pub async fn challenge_auth() { { let mut conn = db.get().await.unwrap(); - let id: i32 = insert_into(schema::useragent_client::table) - .values((schema::useragent_client::public_key.eq(pubkey_bytes.clone()),)) - .returning(schema::useragent_client::id) + let id: i32 = insert_into(schema::operator_client::table) + .values((schema::operator_client::public_key.eq(pubkey_bytes.clone()),)) + .returning(schema::operator_client::id) .get_result(&mut conn) .await .unwrap(); @@ -307,7 +307,7 @@ pub async fn challenge_auth() { let (mut server_transport, mut test_transport) = ChannelTransport::new(); let db_for_task = db.clone(); let task = tokio::spawn(async move { - let mut props = UserAgentConnection::new(db_for_task, actors); + let mut props = OperatorConnection::new(db_for_task, actors); auth::authenticate(&mut props, &mut server_transport).await }); @@ -331,7 +331,7 @@ pub async fn challenge_auth() { Err(err) => panic!("Expected Ok response, got Err({err:?})"), }; - let signature = sign_useragent_challenge(&new_key, &challenge); + let signature = sign_operator_challenge(&new_key, &challenge); test_transport .send(auth::Inbound::AuthChallengeSolution { @@ -371,8 +371,8 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() { { let mut conn = db.get().await.unwrap(); - insert_into(schema::useragent_client::table) - .values((schema::useragent_client::public_key.eq(pubkey_bytes.clone()),)) + insert_into(schema::operator_client::table) + .values((schema::operator_client::public_key.eq(pubkey_bytes.clone()),)) .execute(&mut conn) .await .unwrap(); @@ -381,8 +381,8 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() { let (server_transport, mut test_transport) = start_transport_pair(); let db_for_task = db.clone(); let task = tokio::spawn(async move { - let mut props = UserAgentConnection::new(db_for_task, actors); - user_agent::start(&mut props, server_transport, Box::new(NullOobSender)).await + let mut props = OperatorConnection::new(db_for_task, actors); + operator::start(&mut props, server_transport, Box::new(NullOobSender)).await }); test_transport @@ -405,7 +405,7 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() { Err(err) => panic!("Expected Ok response, got Err({err:?})"), }; - let signature = sign_useragent_challenge(&new_key, &challenge); + let signature = sign_operator_challenge(&new_key, &challenge); test_transport .send(auth::Inbound::AuthChallengeSolution { @@ -422,7 +422,7 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() { assert!(matches!( task.await.unwrap(), - Err(user_agent::Error::Internal(_)) + Err(operator::Error::Internal(_)) )); } @@ -444,9 +444,9 @@ pub async fn challenge_auth_rejects_invalid_signature() { { let mut conn = db.get().await.unwrap(); - let id: i32 = insert_into(schema::useragent_client::table) - .values((schema::useragent_client::public_key.eq(pubkey_bytes.clone()),)) - .returning(schema::useragent_client::id) + let id: i32 = insert_into(schema::operator_client::table) + .values((schema::operator_client::public_key.eq(pubkey_bytes.clone()),)) + .returning(schema::operator_client::id) .get_result(&mut conn) .await .unwrap(); @@ -466,7 +466,7 @@ pub async fn challenge_auth_rejects_invalid_signature() { let (mut server_transport, mut test_transport) = ChannelTransport::new(); let db_for_task = db.clone(); let task = tokio::spawn(async move { - let mut props = UserAgentConnection::new(db_for_task, actors); + let mut props = OperatorConnection::new(db_for_task, actors); auth::authenticate(&mut props, &mut server_transport).await }); @@ -490,7 +490,7 @@ pub async fn challenge_auth_rejects_invalid_signature() { Err(err) => panic!("Expected Ok response, got Err({err:?})"), }; - let signature = sign_useragent_challenge(&new_key, &tamper_challenge(&challenge)); + let signature = sign_operator_challenge(&new_key, &tamper_challenge(&challenge)); test_transport .send(auth::Inbound::AuthChallengeSolution { diff --git a/server/crates/arbiter-server/tests/user_agent/unseal.rs b/server/crates/arbiter-server/tests/operator/unseal.rs similarity index 99% rename from server/crates/arbiter-server/tests/user_agent/unseal.rs rename to server/crates/arbiter-server/tests/operator/unseal.rs index a679bf4..365e6ec 100644 --- a/server/crates/arbiter-server/tests/user_agent/unseal.rs +++ b/server/crates/arbiter-server/tests/operator/unseal.rs @@ -8,7 +8,7 @@ use arbiter_server::{ vault::{Bootstrap, Seal}, }, db, - peers::user_agent::{ + peers::operator::{ Credentials, vault_gate::{ Error as VaultGateError, HandleHandshake, HandleUnsealEncryptedKey, VaultGate, diff --git a/server/crates/arbiter-server/tests/user_agent.rs b/server/crates/arbiter-server/tests/user_agent.rs deleted file mode 100644 index dcd9789..0000000 --- a/server/crates/arbiter-server/tests/user_agent.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod common; - -#[path = "user_agent/auth.rs"] -mod auth; -#[path = "user_agent/unseal.rs"] -mod unseal;