docs: plan gateway user provisioning

This commit is contained in:
2026-06-15 18:08:04 +08:00
parent 8b57159d46
commit e9e57bdb07

View File

@ -0,0 +1,266 @@
# Memory Gateway User Provisioning Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Move Beaver's Gateway code into `beaver.memory.gateway`, load one shared non-secret Gateway configuration, provision Gateway users during Beaver registration, and resolve per-user credentials for each authenticated chat run.
**Architecture:** `EngineLoader` loads curated memory, a shared Gateway config, and an instance-local credential store. Registration calls Gateway `/users` and atomically stores credentials by Beaver username. REST/WebSocket chat derive a trusted username from the access token and `AgentLoop` creates a run-local Gateway service for that user, leaving unauthenticated or unprovisioned runs on curated memory only.
**Tech Stack:** Python 3.14, dataclasses, FastAPI, httpx, pytest, shell-based Docker instance creation.
---
### Task 1: Move Gateway source and load shared configuration
**Files:**
- Create: `app-instance/backend/beaver/memory/gateway/__init__.py`
- Create: `app-instance/backend/beaver/memory/gateway/config.py`
- Create: `app-instance/backend/beaver/memory/gateway/client.py`
- Create: `app-instance/backend/beaver/memory/gateway/service.py`
- Create: `app-instance/backend/memory/config.json`
- Modify: `app-instance/backend/beaver/foundation/config/schema.py`
- Modify: `app-instance/backend/beaver/foundation/config/loader.py`
- Modify: `app-instance/backend/beaver/foundation/config/__init__.py`
- Delete: `app-instance/backend/beaver/integrations/memory_gateway/`
- Delete: `app-instance/backend/beaver/services/memory_gateway_service.py`
- Modify: `app-instance/backend/beaver/services/__init__.py`
- Test: `app-instance/backend/tests/unit/test_config_loader.py`
- Test: `app-instance/backend/tests/unit/test_memory_gateway_service.py`
- [ ] **Step 1: Write failing shared-config and import tests**
Set `BEAVER_MEMORY_CONFIG_PATH` to a temporary JSON file and assert `load_config()` obtains `memory.mode`, URL, and all three scopes from that file. Change all Gateway tests to import from `beaver.memory.gateway`.
- [ ] **Step 2: Run tests and verify RED**
```bash
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_config_loader.py tests/unit/test_memory_gateway_service.py
```
Expected: failures because the new package and shared config loading do not exist.
- [ ] **Step 3: Implement package migration and shared config parsing**
Move existing client/service behavior without changing payloads. Define `MemoryConfig` and `MemoryGatewayConfig` in `beaver.memory.gateway.config`, without `userId/userKey`. Add `default_memory_config_path()` using `BEAVER_MEMORY_CONFIG_PATH` then `<backend-root>/memory/config.json`. Instance config parsing remains responsible for non-memory settings; shared config supplies `BeaverConfig.memory`.
Create tracked `memory/config.json` with `http://172.19.207.37:8010`, scopes `current_chat`, `resources`, `all_user_memory`, top K 8, and timeout 10.
- [ ] **Step 4: Run targeted tests and verify GREEN**
Run the command from Step 2. Expected: selected tests pass.
- [ ] **Step 5: Commit**
```bash
git add app-instance/backend/beaver/memory/gateway app-instance/backend/memory/config.json app-instance/backend/beaver/foundation/config app-instance/backend/beaver/services app-instance/backend/tests/unit/test_config_loader.py app-instance/backend/tests/unit/test_memory_gateway_service.py
git commit -m "refactor(memory): move gateway into memory domain"
```
### Task 2: Add per-instance Gateway credential storage
**Files:**
- Create: `app-instance/backend/beaver/memory/gateway/credentials.py`
- Modify: `app-instance/backend/beaver/memory/gateway/__init__.py`
- Create: `app-instance/backend/tests/unit/test_memory_gateway_credentials.py`
- Modify: `app-instance/create-instance.sh`
- Modify: `app-instance/entrypoint.sh`
- Modify: `app-instance/README.md`
- [ ] **Step 1: Write failing credential-store tests**
Cover missing files, multi-user round trips, updates preserving other users, secret-free repr, atomic replace, and mode `0600`.
- [ ] **Step 2: Run test and verify RED**
```bash
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_credentials.py
```
Expected: import failure because the credential store does not exist.
- [ ] **Step 3: Implement atomic credential persistence**
Implement `MemoryGatewayUserCredential(user_id, user_key)` and `MemoryGatewayCredentialStore.get/save`. Use JSON shape `{"users": {username: {"userId": ..., "userKey": ...}}}`, sibling temporary file, `os.replace`, and `chmod(0o600)`.
Update `create-instance.sh` to create `$BEAVER_HOME/memory_gateway_users.json` as `{"users": {}}`, chmod it `0600`, and pass `BEAVER_MEMORY_GATEWAY_USERS_PATH=/root/.beaver/memory_gateway_users.json`. `entrypoint.sh` exports the same default.
- [ ] **Step 4: Run credential and shell syntax tests**
```bash
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_credentials.py
cd ../..
bash -n app-instance/create-instance.sh app-instance/entrypoint.sh
```
Expected: tests pass and shell syntax exits zero.
- [ ] **Step 5: Commit**
```bash
git add app-instance/backend/beaver/memory/gateway app-instance/backend/tests/unit/test_memory_gateway_credentials.py app-instance/create-instance.sh app-instance/entrypoint.sh app-instance/README.md
git commit -m "feat(memory): persist gateway user credentials"
```
### Task 3: Provision Gateway identities during frontend registration
**Files:**
- Modify: `app-instance/backend/beaver/memory/gateway/client.py`
- Modify: `app-instance/backend/beaver/interfaces/web/app.py`
- Create: `app-instance/backend/tests/unit/test_memory_gateway_registration.py`
- [ ] **Step 1: Write failing registration tests**
Use a temporary auth file and fake Gateway client. Assert registration sends `{"user_id": "tom"}`, stores the returned key, never returns the key to the browser, and still registers the Beaver user without a partial credential when Gateway provisioning fails.
- [ ] **Step 2: Run tests and verify RED**
```bash
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_registration.py
```
Expected: failures because `/users` provisioning is not connected.
- [ ] **Step 3: Implement provisioning**
Add `MemoryGatewayClient.create_user(user_id)`, validating non-empty response `user_id/user_key`. During `/api/auth/register`, after local/AuthZ registration succeeds, call it with the Beaver username and save through the credential store. Catch sanitized Gateway failures without retrying or rolling back Beaver registration. Never include the Gateway credential in the response.
- [ ] **Step 4: Run registration tests and verify GREEN**
Run the command from Step 2. Expected: all registration tests pass.
- [ ] **Step 5: Commit**
```bash
git add app-instance/backend/beaver/memory/gateway/client.py app-instance/backend/beaver/interfaces/web/app.py app-instance/backend/tests/unit/test_memory_gateway_registration.py
git commit -m "feat(memory): provision gateway users on registration"
```
### Task 4: Pass trusted authenticated identity into chat runs
**Files:**
- Modify: `app-instance/backend/beaver/interfaces/web/app.py`
- Modify: `app-instance/backend/beaver/engine/loop.py`
- Modify: `app-instance/backend/beaver/services/agent_service.py`
- Modify: `app-instance/backend/tests/unit/test_websocket_chat.py`
- [ ] **Step 1: Write failing REST/WebSocket identity tests**
Issue a web token for `tom`. Assert REST and WebSocket calls pass `gateway_user_id="tom"`. Send a conflicting client `user_id="other"` and assert the trusted identity remains `tom`. Unauthenticated calls pass `gateway_user_id=None`.
- [ ] **Step 2: Run tests and verify RED**
```bash
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_websocket_chat.py
```
Expected: identity assertions fail because chat does not pass a trusted Gateway principal.
- [ ] **Step 3: Implement optional trusted identity resolution**
Add `gateway_user_id: str | None` to AgentLoop direct-run kwargs. REST reads the optional bearer token from `Authorization`; WebSocket reads the existing `?token=` parameter. Both resolve only through `app.state.auth_tokens`. Request `user_id` remains session metadata and never selects Gateway credentials.
- [ ] **Step 4: Run identity tests and verify GREEN**
Run the command from Step 2. Expected: tests pass.
- [ ] **Step 5: Commit**
```bash
git add app-instance/backend/beaver/interfaces/web/app.py app-instance/backend/beaver/engine/loop.py app-instance/backend/beaver/services/agent_service.py app-instance/backend/tests/unit/test_websocket_chat.py
git commit -m "feat(memory): bind gateway runs to authenticated users"
```
### Task 5: Resolve a run-local Gateway service per user
**Files:**
- Modify: `app-instance/backend/beaver/engine/loader.py`
- Modify: `app-instance/backend/beaver/engine/loop.py`
- Modify: `app-instance/backend/beaver/memory/gateway/service.py`
- Modify: `app-instance/backend/tests/unit/test_memory_gateway_loader.py`
- Modify: `app-instance/backend/tests/unit/test_memory_gateway_agent_loop.py`
- [ ] **Step 1: Write failing loader and AgentLoop tests**
Assert loader exposes shared config, credential store, and service factory instead of a fixed-user service. Add two users with different keys and verify each run constructs a service from only the selected credential. Missing identity or credential performs no Gateway calls while curated memory remains present.
- [ ] **Step 2: Run tests and verify RED**
```bash
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_loader.py tests/unit/test_memory_gateway_agent_loop.py
```
Expected: failures because loader still creates one fixed-user service.
- [ ] **Step 3: Implement per-run service resolution**
Expose `memory_gateway_config`, `memory_gateway_credentials`, and a service factory on `EngineLoadResult`. At run start, resolve the credential by `gateway_user_id`; construct a fresh service only in hybrid mode when a credential exists. Pass shared config and credential separately to the service and preserve current recall/add/flush/audit behavior.
- [ ] **Step 4: Run Gateway runtime tests and verify GREEN**
```bash
cd app-instance/backend
.venv/bin/pytest -q tests/unit/test_memory_gateway_loader.py tests/unit/test_memory_gateway_agent_loop.py tests/unit/test_memory_gateway_service.py tests/unit/test_context_builder.py
```
Expected: all selected tests pass.
- [ ] **Step 5: Commit**
```bash
git add app-instance/backend/beaver/engine app-instance/backend/beaver/memory/gateway app-instance/backend/tests/unit/test_memory_gateway_loader.py app-instance/backend/tests/unit/test_memory_gateway_agent_loop.py
git commit -m "feat(memory): resolve gateway service per user"
```
### Task 6: Update documentation and perform final verification
**Files:**
- Modify: `app-instance/backend/README.md`
- Modify: `app-instance/README.md`
- Modify: `docs/superpowers/plans/2026-06-15-hybrid-memory-gateway.md`
- [ ] **Step 1: Update operational documentation**
Document the shared config path, instance credential path, registration provisioning, token-based identity, secret handling, and rebuild/restart requirements. Remove examples that place `userId/userKey` in instance `config.json`.
- [ ] **Step 2: Verify removed source imports**
```bash
rg -n "beaver\.integrations\.memory_gateway|beaver\.services\.memory_gateway_service" app-instance/backend/beaver app-instance/backend/tests
```
Expected: no matches.
- [ ] **Step 3: Run full verification**
```bash
cd app-instance/backend
.venv/bin/python -m compileall -q beaver
.venv/bin/pytest -q
cd ../..
bash -n app-instance/create-instance.sh app-instance/entrypoint.sh
git diff --check
```
Expected: compile and shell checks exit zero, all tests pass, and diff check is clean.
- [ ] **Step 4: Scan tracked content for credentials**
```bash
git grep -nE 'uk_[A-Za-z0-9]{8,}' -- ':!docs/superpowers/specs/*' ':!docs/superpowers/plans/*'
```
Expected: no real Gateway key in tracked source or runtime files; obvious test placeholders are reviewed manually.
- [ ] **Step 5: Commit**
```bash
git add app-instance/backend/README.md app-instance/README.md docs/superpowers/plans/2026-06-15-hybrid-memory-gateway.md
git commit -m "docs(memory): document gateway user provisioning"
```