docs: map existing codebase
This commit is contained in:
parent
bea7f24a4f
commit
9e77a4229c
333
.planning/codebase/ARCHITECTURE.md
Normal file
333
.planning/codebase/ARCHITECTURE.md
Normal file
@ -0,0 +1,333 @@
|
||||
<!-- refreshed: 2026-07-29 -->
|
||||
# Architecture
|
||||
|
||||
**Analysis Date:** 2026-07-29
|
||||
|
||||
## System Overview
|
||||
|
||||
```text
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ Frontend Layer (Vue 3) │
|
||||
│ `neode-ui/src` (TypeScript + SPA) │
|
||||
│ Routes → Views → Components → Composables → RPC Client │
|
||||
└────────────────┬─────────────────────────────────────────────┘
|
||||
│ WebSocket + HTTP(S)
|
||||
│ JSON-RPC 2.0 protocol
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ HTTP Server Layer (Hyper) │
|
||||
│ `core/archipelago/src/server.rs` │
|
||||
│ TCP Listener → Hyper → Router → ApiHandler/RpcHandler │
|
||||
└────────────────┬─────────────────────────────────────────────┘
|
||||
│
|
||||
┌──────────┴──────────┬──────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌──────────┐ ┌────────────┐
|
||||
│ WebSocket │ RPC │ │ Content │
|
||||
│ Handler │ Handler │ │ Proxy │
|
||||
│ (state sync) │ (methods)│ │ (app URIs) │
|
||||
└─────────┘ └──────────┘ └────────────┘
|
||||
│ │
|
||||
└──────────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ Service Layer (Async Tasks) │
|
||||
│ `core/archipelago/src/api/rpc/*` │
|
||||
│ │
|
||||
│ • auth, identity, secrets │
|
||||
│ • container orchestration │
|
||||
│ • bitcoin, lightning, wallet │
|
||||
│ • mesh, federation, FIPS │
|
||||
│ • content, backup, settings │
|
||||
└─────────────┬───────────────────────┘
|
||||
│
|
||||
┌───────────┼───────────┬──────────────┐
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────┐
|
||||
│Container│ │ State │ │BlobStore
|
||||
│Orch. │ │Manager │ │ │ │Identity │
|
||||
│(Podman) │ │(Broadcast
|
||||
│ │ │ channels)│ │ ContentClient Manager │
|
||||
└─────────┘ └──────────┘ └────────┘ └──────────┘
|
||||
│ │ │ │
|
||||
└───────────┼───────────┼─────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ Persistent Storage Layer │
|
||||
│ │
|
||||
│ • Data directory files (YAML/JSON) │
|
||||
│ • SQLite (session store) │
|
||||
│ • Blob store (content-addressed) │
|
||||
│ • Podman container state │
|
||||
│ • Secret vaults (encrypted) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Component Responsibilities
|
||||
|
||||
| Component | Responsibility | File |
|
||||
|-----------|----------------|------|
|
||||
| **Server** | HTTP listener, connection multiplexing, TLS/encryption | `core/archipelago/src/server.rs` |
|
||||
| **ApiHandler** | HTTP request routing, authentication, response formatting | `core/archipelago/src/api/handler/mod.rs` |
|
||||
| **RpcHandler** | JSON-RPC 2.0 dispatch, method registration, rate limiting | `core/archipelago/src/api/rpc/mod.rs` |
|
||||
| **ContainerOrchestrator** | Podman lifecycle, manifest reconciliation, adoption | `core/archipelago/src/container/prod_orchestrator.rs` |
|
||||
| **StateManager** | Central state broadcast channel, revision tracking | `core/archipelago/src/state.rs` |
|
||||
| **AuthManager** | User credentials, session validation, password hashing | `core/archipelago/src/auth.rs` |
|
||||
| **Identity Manager** | Node Ed25519 keys, seed derivation, Tor address | `core/archipelago/src/identity_manager.rs` |
|
||||
| **BootReconciler** | Periodic manifest sync loop, adoption, remediation | `core/archipelago/src/container/boot_reconciler.rs` |
|
||||
| **Frontend Router** | Vue Router, page navigation, deep linking | `neode-ui/src/router/index.ts` |
|
||||
| **Frontend Stores** | Pinia state (apps, settings, user, mesh) | `neode-ui/src/stores/` |
|
||||
| **Frontend Components** | UI elements, modals, cards, layout primitives | `neode-ui/src/components/` |
|
||||
|
||||
## Pattern Overview
|
||||
|
||||
**Overall:** Multi-tier async architecture with centralized request dispatch and broadcast state synchronization.
|
||||
|
||||
**Key Characteristics:**
|
||||
- **Async-first (Tokio)** - All I/O operations are non-blocking; task spawning for background work
|
||||
- **RPC-driven API** - Frontend communicates via JSON-RPC 2.0 (not REST); single `/api/v0` WebSocket + HTTP endpoint
|
||||
- **State as broadcast** - Global state changes flow through Tokio broadcast channels to all connected WebSocket clients
|
||||
- **Manifest-driven containers** - App lifecycle controlled by declarative YAML manifests (Archipelago-specific extensions)
|
||||
- **Plugin architecture** - Apps are isolated Podman containers with declarative interfaces (web UI, ports, secrets)
|
||||
|
||||
## Layers
|
||||
|
||||
**HTTP/Transport Layer:**
|
||||
- Purpose: Accept inbound connections, handle TLS termination, demultiplex HTTP/WebSocket
|
||||
- Location: `core/archipelago/src/server.rs`
|
||||
- Contains: Hyper listener, TCP accept loop, connection state tracking
|
||||
- Depends on: Tokio, Hyper, TLS/mTLS libraries (rustls/openssl)
|
||||
- Used by: All external clients (web UI, companion app, API consumers)
|
||||
|
||||
**Request Routing & Auth Layer:**
|
||||
- Purpose: Dispatch HTTP requests to handlers, validate sessions, enforce CSRF, rate-limit login
|
||||
- Location: `core/archipelago/src/api/` (handler + rpc submodules)
|
||||
- Contains: Route matching, middleware chain, cookie extraction, error formatting
|
||||
- Depends on: Server, StateManager, SessionStore
|
||||
- Used by: All request paths; gates API access
|
||||
|
||||
**RPC Dispatch Layer:**
|
||||
- Purpose: Deserialize JSON-RPC 2.0 requests, call appropriate service method, serialize responses
|
||||
- Location: `core/archipelago/src/api/rpc/mod.rs` + subdirectories (auth.rs, container.rs, bitcoin.rs, etc.)
|
||||
- Contains: Method table, parameter validation, response formatting, rate limit checks
|
||||
- Depends on: All service modules
|
||||
- Used by: Frontend (WebSocket + HTTP POST to /api/v0), internal tools
|
||||
|
||||
**Service Layer:**
|
||||
- Purpose: Implement business logic — container lifecycle, identity, auth, content sync, mesh discovery
|
||||
- Location: `core/archipelago/src/api/rpc/*` (one RPC module per domain), plus `core/archipelago/src/` (background tasks)
|
||||
- Contains: ~40 RPC method modules + 50+ core service modules (bootstrap.rs, health_monitor.rs, crash_recovery.rs, etc.)
|
||||
- Depends on: StateManager, ContainerOrchestrator, config/secrets, external services (Bitcoin, Lightning, FIPS)
|
||||
- Used by: RPC layer; other services for cross-cutting concerns (mesh, federation, webhooks)
|
||||
|
||||
**State Management Layer:**
|
||||
- Purpose: Hold canonical application state, broadcast changes to all connected clients, persist snapshots
|
||||
- Location: `core/archipelago/src/state.rs` (StateManager + data_model.rs)
|
||||
- Contains: RwLock<DataModel>, broadcast channel, revision counter
|
||||
- Depends on: DataModel (serde-serializable struct tree)
|
||||
- Used by: All services that mutate state (container ops, auth, settings)
|
||||
|
||||
**Container Orchestration Layer:**
|
||||
- Purpose: Podman lifecycle management, image verification, secret injection, crash recovery, adoption
|
||||
- Location: `core/archipelago/src/container/prod_orchestrator.rs` (1M+ lines; split across boot_reconciler.rs, quadlet.rs, docker_packages.rs, etc.)
|
||||
- Contains: Manifest parsing, image pull/verify, container create/start/stop, volume mounts, networking
|
||||
- Depends on: Podman CLI + socket, config parser, image registries, local filesystem
|
||||
- Used by: RPC container.* methods, BootReconciler loop, crash recovery
|
||||
|
||||
**Frontend Layer (Vue 3):**
|
||||
- Purpose: Render UI, dispatch RPC calls, maintain local UI state, handle user input
|
||||
- Location: `neode-ui/src/`
|
||||
- Contains: Views (pages), Components (reusable UI), Composables (logic hooks), Stores (Pinia), Router
|
||||
- Depends on: Vue 3, Vue Router, Pinia, RPC client library (custom), D3/Leaflet (charts/maps)
|
||||
- Used by: Browser clients (desktop, mobile, companion app via WebView)
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Primary Request Path (User Action → Backend → State Sync)
|
||||
|
||||
1. **Frontend user interaction** (click button, type input) → Vue component event handler
|
||||
- Location: `neode-ui/src/views/*.vue` or `neode-ui/src/components/*.vue`
|
||||
|
||||
2. **Composable dispatches RPC** (e.g., `useContainerInstall()` calls `rpc.container.install()`)
|
||||
- Location: `neode-ui/src/composables/` (custom or imported from `api/rpc-client.ts`)
|
||||
|
||||
3. **RPC client serializes → HTTP/WebSocket POST to /api/v0**
|
||||
- Location: `neode-ui/src/api/rpc-client.ts`
|
||||
- Payload: `{ jsonrpc: "2.0", method: "container.install", params: {...}, id: ... }`
|
||||
|
||||
4. **HTTP Server receives, routes to ApiHandler**
|
||||
- Location: `core/archipelago/src/server.rs` (listener) → `core/archipelago/src/api/handler/mod.rs` (dispatch)
|
||||
|
||||
5. **ApiHandler checks auth**, extracts body, calls RpcHandler
|
||||
- Location: `core/archipelago/src/api/handler/mod.rs:handle_request()`
|
||||
|
||||
6. **RpcHandler dispatches by method name** to specific RPC module
|
||||
- Location: `core/archipelago/src/api/rpc/mod.rs:call()` → routing to `core/archipelago/src/api/rpc/container.rs:install()`
|
||||
|
||||
7. **Service method executes** (e.g., `container.rs:install()` calls orchestrator, updates state)
|
||||
- Location: `core/archipelago/src/api/rpc/container.rs` (calls methods on ContainerOrchestrator)
|
||||
|
||||
8. **StateManager.update_data()** broadcasts the new state to all WebSocket subscribers
|
||||
- Location: `core/archipelago/src/state.rs:update_data()` → broadcast channel
|
||||
- All connected WebSocket clients receive `{ rev: N, data: {...} }` update
|
||||
|
||||
9. **Frontend receives state update**, updates Pinia stores, re-renders UI
|
||||
- Location: `neode-ui/src/stores/` (Pinia stores mutate) → Vue reactivity chain → DOM update
|
||||
|
||||
**State Management:**
|
||||
- All reads from `StateManager` go through `get_snapshot()` which acquires read-lock
|
||||
- All writes go through `update_data()` which acquires write-lock + increments revision
|
||||
- Broadcast channel has ~100-message buffer; slow subscribers may lose old updates (by design — UI only needs latest)
|
||||
- WebSocket clients re-sync on reconnect via `get_snapshot()` call (full state transfer)
|
||||
|
||||
### Secondary Flow: Scheduled Reconciliation (Convergence Loop)
|
||||
|
||||
1. **BootReconciler spawned at startup** in `main.rs`
|
||||
- Location: `core/archipelago/src/main.rs` (line ~338-348)
|
||||
|
||||
2. **Reconciler runs every `RECONCILER_DEFAULT_INTERVAL`** (~30s typical)
|
||||
- Location: `core/archipelago/src/container/boot_reconciler.rs:run_forever()`
|
||||
|
||||
3. **Compares desired manifests (disk + registry catalog) vs actual Podman state**
|
||||
- Looks for: containers missing, containers orphaned, image updates, secret changes
|
||||
|
||||
4. **Applies remediation** (create, delete, restart containers)
|
||||
- Calls: orchestrator.reconcile_*() methods
|
||||
|
||||
5. **Logs changes, broadcasts state update if anything changed**
|
||||
- Frontend receives update, shows user the reconciled app state
|
||||
|
||||
This ensures apps survive crashes, OTA updates, or manual Podman edits — the desired state always converges.
|
||||
|
||||
## Key Abstractions
|
||||
|
||||
**ContainerOrchestrator trait:**
|
||||
- Purpose: Abstract container lifecycle behind a trait so Prod (Podman-based) and Dev (in-memory) modes can coexist
|
||||
- Examples: `core/archipelago/src/container/prod_orchestrator.rs`, `core/archipelago/src/container/dev_orchestrator.rs`
|
||||
- Pattern: Trait-based strategy; RpcHandler holds `Arc<dyn ContainerOrchestrator>`, switches at runtime
|
||||
- Methods: create, start, stop, delete, adopt, list, reconcile, install, upgrade
|
||||
|
||||
**Manifest (YAML-based declarative app):**
|
||||
- Purpose: Fully describe an app's container, dependencies, secrets, ports, UI in one file
|
||||
- Examples: `/opt/archipelago/apps/*/manifest.yml` (on-disk) or registry-delivered catalogs
|
||||
- Pattern: Custom extensions over OCI/Docker Compose (e.g., `interfaces.main.ui`, `generated_secrets`)
|
||||
- Parsed into: `container::manifest::Manifest` struct, consumed by orchestrator
|
||||
|
||||
**RPC Method Modules:**
|
||||
- Purpose: Group related JSON-RPC methods by domain (auth, container, bitcoin, mesh, etc.)
|
||||
- Examples: `core/archipelago/src/api/rpc/auth.rs`, `core/archipelago/src/api/rpc/bitcoin.rs`
|
||||
- Pattern: Each module exports `pub async fn method_name(handler, params) -> Result<Response>`
|
||||
- Registration: Hardcoded dispatch in `RpcHandler::call()` (no reflection; methods are explicit)
|
||||
|
||||
**BlobStore (Content-Addressed):**
|
||||
- Purpose: Store attachments/files by SHA-256 hash; issue time-limited capability tokens for access
|
||||
- Examples: Used by mesh.send-content, federation attachments, backup archives
|
||||
- Pattern: Capability-based access control (CBAC); tokens scoped to issuer pubkey + hash
|
||||
- Located: `core/archipelago/src/blobs.rs` + `core/archipelago/src/content_server.rs`
|
||||
|
||||
**StateManager + DataModel:**
|
||||
- Purpose: Single source of truth for UI state; broadcast updates to all clients
|
||||
- Pattern: Read-write lock over a serde-serializable struct tree; broadcast channel for efficiency
|
||||
- Persistence: Most state is ephemeral (app listings, UI settings); durable state persists to disk separately
|
||||
- Clients: Frontend (WebSocket subscriber), internal services (read via get_snapshot), monitoring/debug
|
||||
|
||||
**Session Store:**
|
||||
- Purpose: Track authenticated HTTP sessions (cookie → user identity mapping)
|
||||
- Examples: SQLite-backed or in-memory store
|
||||
- Pattern: Session token issued at login, validated on each request, expires after TTL
|
||||
- Used by: ApiHandler auth check, rate limiter (per IP + per user)
|
||||
|
||||
## Entry Points
|
||||
|
||||
**Backend Daemon (Binary):**
|
||||
- Location: `core/archipelago/src/main.rs`
|
||||
- Triggers: `systemd start archipelago.service` or manual `./archipelago` on development node
|
||||
- Responsibilities: Parse config, init tracing, load/reconcile containers, start HTTP server, spawn background tasks
|
||||
- Key setup: Load identity → setup auth → spawn orchestrator → load manifests → start reconciler → start server
|
||||
|
||||
**Frontend SPA:**
|
||||
- Location: `neode-ui/src/main.ts`
|
||||
- Triggers: Browser loads `/index.html` (served by HTTP server from `/opt/archipelago/web-ui/`)
|
||||
- Responsibilities: Boot Vue app, setup Router, setup Pinia stores, establish WebSocket to backend
|
||||
- Key setup: Mount app → router ready → fetch initial state → subscribe to updates
|
||||
|
||||
**RPC Endpoints (HTTP + WebSocket):**
|
||||
- Location: `core/archipelago/src/api/` (handler routes requests here)
|
||||
- Endpoint: `/api/v0` (JSON-RPC 2.0 POST or WebSocket upgrade)
|
||||
- Methods: ~200+ RPCs across domains (auth, container, bitcoin, mesh, federation, etc.)
|
||||
- Example: `POST /api/v0` with body `{"jsonrpc": "2.0", "method": "auth.login", "params": {...}, "id": 1}`
|
||||
|
||||
**Background Tasks (Spawned at startup):**
|
||||
- BootReconciler: Periodic manifest reconciliation loop
|
||||
- Health Monitor: Periodic app health checks + restart
|
||||
- Update Scheduler: Periodic app update checks
|
||||
- Mesh Service: P2P mesh listener + sender (federation, LoRa)
|
||||
- Webhook Relay: Listens for inbound webhooks, broadcasts to subscribers
|
||||
- WebSocket Listener: Upgraded HTTP connections → broadcast state subscriber
|
||||
- See: `core/archipelago/src/main.rs` (lines ~400-450 show the spawned tasks)
|
||||
|
||||
## Architectural Constraints
|
||||
|
||||
- **Single event loop** — All I/O-bound work runs on a single Tokio multi-threaded runtime; no worker threads by default (some container ops are blocking, run in tokio::task::spawn_blocking)
|
||||
- **Global state via broadcast** — StateManager broadcasts to all WebSocket clients; no request-response for state changes (async by design)
|
||||
- **Container state mutability** — Podman state can drift from manifest (manual edits, crashes); reconciler runs periodically to converge
|
||||
- **No in-process data consistency** — Multiple services can mutate StateManager concurrently; last write wins (fine for UI; critical ops use locks)
|
||||
- **Shared blob store** — All services that need to share content use the same BlobStore instance (single cap_key, single root directory)
|
||||
- **Rate limiting per IP + method** — Prevents brute-force login, but shared IPs see shared limits (edge case: family users, proxies)
|
||||
- **Session cookie same-site** — WebSocket + HTTP POST must be same-origin; CORS headers controlled by ApiHandler
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
### Circular RPC Dispatches
|
||||
|
||||
**What happens:** An RPC method calls back into another RPC method, forming a cycle (e.g., auth.login → container.list → auth.check_permission → auth.login)
|
||||
**Why it's wrong:** Deadlocks on RwLocks, infinite loops on state broadcasts, unclear error messages, hard to debug
|
||||
**Do this instead:** Pass check result as a side-effect from the outer method; compute permissions once at the start. Use composable patterns in frontend instead (e.g., `useCanInstall()` checks perms once per component mount).
|
||||
|
||||
### Synchronous blocking in RPC handlers
|
||||
|
||||
**What happens:** RPC method calls `.unwrap()` on Podman command result, blocking the entire event loop
|
||||
**Why it's wrong:** One slow container op (e.g., large image pull) blocks all concurrent users
|
||||
**Do this instead:** Use `tokio::task::spawn_blocking()` for I/O that may take >100ms. See `core/archipelago/src/container/docker_packages.rs` for examples.
|
||||
|
||||
### Hardcoding paths in app RPC modules
|
||||
|
||||
**What happens:** `bitcoin.rs` hardcodes `/opt/archipelago/data/bitcoin.conf` instead of using `config.data_dir`
|
||||
**Why it's wrong:** Dev mode, tests, and alternate installs all fail with "not found"
|
||||
**Do this instead:** Read from `Config` struct, which is passed to every RPC method. See `core/archipelago/src/api/rpc/bitcoin.rs:status()` for correct pattern.
|
||||
|
||||
### Frontend state outside Pinia stores
|
||||
|
||||
**What happens:** Components use component-local ref<> for app list, duplicate the StateManager's data
|
||||
**Why it's wrong:** Stale data after OTA updates, inconsistent with other users on the same node, race conditions on install/uninstall
|
||||
**Do this instead:** Always derive from Pinia stores (e.g., `useAppStore().apps`). Stores subscribe to WebSocket updates. See `neode-ui/src/stores/appStore.ts`.
|
||||
|
||||
### Not handling WebSocket reconnection
|
||||
|
||||
**What happens:** Frontend goes offline for 10s (network glitch), WebSocket closes, frontend doesn't re-sync state
|
||||
**Why it's wrong:** UI shows stale data (app still "installing" when actually done), user clicks again, double-action happens
|
||||
**Do this instead:** WebSocket reconnect handler should re-fetch full state (`node.status`, etc.), re-subscribe. See `neode-ui/src/api/rpc-client.ts` for the reconnect loop.
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Strategy:** Defensive layering — errors are caught at each tier, logged, and converted to user-facing messages.
|
||||
|
||||
**Patterns:**
|
||||
- HTTP layer: 4xx/5xx with JSON error (no 500s for logic errors; only for crashes)
|
||||
- RPC layer: Serialize error as `{ error: { code: N, message: "...", data: {...} } }` per JSON-RPC spec
|
||||
- Service layer: Use `anyhow::Result<T>` + `?` operator for early exit; convert to `RpcError` at handler boundary
|
||||
- Frontend: Catch RPC errors, show toast/modal, log to console (never crash the app)
|
||||
|
||||
**Critical paths:**
|
||||
- Auth failure: 401 Unauthorized + "Invalid password" (no "user not found" to leak usernames)
|
||||
- Container ops: If reconciler sees drift, logs it but continues (never crashes the daemon)
|
||||
- Image pull failure: Fallback to last-cached version if network timeout (user is never blocked on external registries)
|
||||
- Podman socket unavailable: Return 503 Service Unavailable (user sees "Archipelago is starting")
|
||||
|
||||
---
|
||||
|
||||
*Architecture analysis: 2026-07-29*
|
||||
195
.planning/codebase/CONCERNS.md
Normal file
195
.planning/codebase/CONCERNS.md
Normal file
@ -0,0 +1,195 @@
|
||||
# Codebase Concerns
|
||||
|
||||
**Analysis Date:** 2026-07-29
|
||||
|
||||
## Tech Debt
|
||||
|
||||
**Federation node removal tombstone gap:**
|
||||
- Issue: `federation::remove_node()` (`core/archipelago/src/federation/storage.rs:180-197`) calls `tombstone_did()` at line 193 but explicitly drops the error with `let _ = …`. If tombstone write fails (disk I/O, permission, transient), the peer is removed from `nodes.json` but never actually recorded as removed, so the next background sync/notify-join silently re-adds it.
|
||||
- Files: `core/archipelago/src/federation/storage.rs:180-197`, `core/archipelago/src/api/rpc/federation/handlers.rs:272-300`
|
||||
- Impact: Federation peers marked for removal can reappear after the next sync cycle, confusing the operator and potentially re-establishing unwanted connections.
|
||||
- Fix approach: Surface the tombstone-write failure instead of swallowing it; consider retry logic with backoff; add integration test via `tests/multinode/smoke.sh` to verify removal sticks across sync cycles.
|
||||
|
||||
**Container reconciler observability gap:**
|
||||
- Issue: No metrics distinguish "settling after restart" from "flapping" — container thrashing is invisible until anecdotal reports. No per-app restart counter or log line when an app restarts >N times in M minutes.
|
||||
- Files: `core/archipelago/src/container/prod_orchestrator.rs` (reconciler loop), `core/archipelago/src/health_monitor.rs`
|
||||
- Impact: Silent restart storms go unnoticed; users see frequent service interruptions without diagnostics; operator can't distinguish normal convergence from a crash loop.
|
||||
- Fix approach: Add per-app restart counter + log line when threshold exceeded; emit metric on each restart; wire restart count into health/status RPC output.
|
||||
|
||||
**Failed systemd unit self-healing gap:**
|
||||
- Issue: When a Quadlet-backed app's `.service` unit enters `failed` state (e.g., exit 255), the reconciler does not automatically `reset-failed` + `start` it. The unit sits failed until the operator manually intervenes or the service restarts.
|
||||
- Files: `core/archipelago/src/container/prod_orchestrator.rs` (reconcile loop)
|
||||
- Impact: Apps with transient failures go down and stay down; no automatic recovery; operator must manually reset or restart the orchestrator.
|
||||
- Fix approach: Add reconcile step: quadlet-backed app whose `.service` is `failed` and not user-stopped → call `systemctl --user reset-failed <unit>` + `start`; add backoff to avoid busy-loop on persistent failures.
|
||||
|
||||
**Bitcoin RPC credentials not retrieved from config/secrets:**
|
||||
- Issue: `core/container/src/bitcoin_simulator.rs:158` has a TODO marking hardcoded (or missing) RPC credentials in the Bitcoin simulator real-mode path. Credentials should be fetched from the secret store.
|
||||
- Files: `core/container/src/bitcoin_simulator.rs:155-165`
|
||||
- Impact: Bitcoin simulator in real mode (Testnet/Mainnet) cannot authenticate to the node; RPC calls fail.
|
||||
- Fix approach: Inject `SecretsProvider` into `BitcoinSimulator::new()` or pass credentials as constructor args; fetch via `config/secrets` at runtime; handle credential rotation.
|
||||
|
||||
**Container security policies not wired in:**
|
||||
- Issue: `core/security/src/container_policies.rs` generates AppArmor/SELinux profiles but the `apply_profile()` function has a TODO at line 71: "Configure Podman to use the profile" — the profiles are generated but never applied to running containers.
|
||||
- Files: `core/security/src/container_policies.rs:63-75`
|
||||
- Impact: Security profiles exist but provide zero protection; containers run without the intended isolation constraints.
|
||||
- Fix approach: Pass `--security-opt apparmor=<profile>` (or SELinux equivalent) to Podman at container creation; verify profile loads via `apparmor_status`; add CI check that profiles compile cleanly.
|
||||
|
||||
**Dynamic resource adjustment not implemented:**
|
||||
- Issue: `core/performance/src/resource_manager.rs:86` has a TODO for dynamic resource adjustment based on usage. The allocator is static; no adaptive rebalancing when load patterns shift.
|
||||
- Files: `core/performance/src/resource_manager.rs:86-88`
|
||||
- Impact: Resource allocation is rigid; a node with skewed usage (e.g., one app consuming all memory) has no mechanism to rebalance dynamically.
|
||||
- Fix approach: Monitor per-app resource usage via cgroup stats; implement feedback loop to adjust limits; gate on production deployment (likely Phase 3+).
|
||||
|
||||
## Known Bugs
|
||||
|
||||
**Multinode RPC robustness gap:**
|
||||
- Symptoms: The `node_rpc()` function in `tests/multinode/lib/multinode.bash` lacks `--max-time` on curl calls — a slow server-side RPC can hang the test suite indefinitely with zero feedback.
|
||||
- Files: `tests/multinode/lib/multinode.bash` (exact line TBD; see grep for `node_rpc`)
|
||||
- Trigger: Run multinode federation/mesh test against a slow or overloaded node; curl will block forever.
|
||||
- Workaround: Manually kill the test process and diagnose the hanging RPC manually; no automatic timeout recovery.
|
||||
- Fix approach: Add `--max-time 30` to all curl calls in `node_rpc()`; re-run `tests/multinode/smoke.sh` to verify.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**Secrets environment variable exposure risk:**
|
||||
- Risk: Bitcoin and other service credentials are materialized as env vars in `ARCHIPELAGO_*` (e.g., `BITCOIN_RPC_PASSWORD`). Env vars are visible via `/proc/<pid>/environ` and potentially logged.
|
||||
- Files: `core/archipelago/src/container/prod_orchestrator.rs`, `core/container/src/manifest.rs`, `core/archipelago/src/api/rpc/package/config.rs`
|
||||
- Current mitigation: Secrets are declared as `generated_secrets` in manifests and materialized 0600/rootless; the orchestrator avoids logging values.
|
||||
- Recommendations: Audit all env-var passing to containers; consider switching high-sensitivity secrets (bitcoin RPC, LND macaroons) to file-based secrets mounted read-only; add audit logging for secret access.
|
||||
|
||||
**Federation DID validation incomplete:**
|
||||
- Risk: Federation peer DIDs are added via the RPC without cryptographic verification of ownership. A compromised peer could advertise arbitrary DIDs.
|
||||
- Files: `core/archipelago/src/api/rpc/federation/handlers.rs` (add-node path), `core/archipelago/src/federation/storage.rs`
|
||||
- Current mitigation: DIDs are stored locally; transitive federation discovery uses the tombstone list to block removed peers.
|
||||
- Recommendations: Add DID-ownership proof (e.g., signed proof-of-identity) before accepting a peer's advertised DID; document the trust model; consider user warnings when adding peers.
|
||||
|
||||
**AppArmor profiles overly permissive:**
|
||||
- Risk: Generated AppArmor profiles use blanket `network,` instead of per-port/protocol rules. Readonly flag is checkbox only, not enforced per actual app needs.
|
||||
- Files: `core/security/src/container_policies.rs:46-54`
|
||||
- Current mitigation: None (profiles not applied).
|
||||
- Recommendations: Refine per-app capabilities based on manifest's declared needs; add integration test verifying readonly mounts are enforced; apply profiles in development before prod.
|
||||
|
||||
## Performance Bottlenecks
|
||||
|
||||
**Container thrashing during reconcile:**
|
||||
- Problem: Restarting `archipelago.service` SIGKILLs every container, forcing a full rebuild over several minutes. Uninstall + reinstall loops can cascade-trigger restarts.
|
||||
- Files: `core/archipelago/src/container/prod_orchestrator.rs` (the reconciler's desired-state machine)
|
||||
- Cause: Pre-Phase-3 architecture: containers run in systemd cgroup, not as independent Quadlet units.
|
||||
- Improvement path: Phase-3 Quadlet default-flip (`config.rs:256`) — each app becomes an independent `.container` unit; restart only the affected app, not the entire cgroup.
|
||||
|
||||
**Reconciler churn on boot:**
|
||||
- Problem: Boot reconciler makes multiple passes reconciling drift; during each pass, containers may be recreated. Post-OTA health checks deliberately skip per-app container assertions because of restart-storm unpredictability.
|
||||
- Files: `core/archipelago/src/container/prod_orchestrator.rs`, `core/archipelago/src/bootstrap.rs`
|
||||
- Cause: Multi-pass reconciliation + no incremental diff detection.
|
||||
- Improvement path: Consolidate reconciler into single pass for boot; cache manifest/config diffs to avoid redundant comparisons; add boot-only fast-path.
|
||||
|
||||
**Bitcoin IBD on .198 stalled (disk I/O):**
|
||||
- Problem: .198 bitcoin is mid-IBD with only 21% progress; disk is 448GB (below 1TB archival threshold); load is high (~3–5).
|
||||
- Files: `tests/multinode-testing-plan.md` (documented issue)
|
||||
- Cause: Undersized/slow disk; concurrent workload.
|
||||
- Improvement path: User decision required: swap in a different node (already done for gate run, using .5 instead) or add storage + wait for sync. Not a code issue.
|
||||
|
||||
## Fragile Areas
|
||||
|
||||
**Uninstall + reinstall lifecycle:**
|
||||
- Files: `core/archipelago/src/api/rpc/package/install.rs`, `core/archipelago/src/container/quadlet.rs:disable_remove()`, `neode-ui/src/components/AppCard.vue`
|
||||
- Why fragile: Pre-2026-07-26, `quadlet::disable_remove()` called systemd + podman with no timeouts, causing hangs. Fixed by commit `71cc9ac4` (added `QUADLET_STOP_TIMEOUT`, SIGKILL escalation, reset-failed). AppCard was hardcoding uninstall bar to "stuck full-red" (fixed `9f17ba68`). Tests for reinstall/cascade are still opt-in.
|
||||
- Safe modification: Any changes to the uninstall path must be tested via `cascade-uninstall.bats` (7/7 on .228); extend coverage to multi-container stacks (immich, btcpay). Verify on .228 before fleet roll.
|
||||
- Test coverage: `tests/lifecycle/bats/cascade-uninstall.bats` exists but not in canonical gate; must opt-in with `ARCHY_GATE_CASCADE=1`.
|
||||
|
||||
**Production orchestrator state machine:**
|
||||
- Files: `core/archipelago/src/container/prod_orchestrator.rs` (6291 lines)
|
||||
- Why fragile: Largest file in the codebase; owns install/start/stop/restart/remove/upgrade for every app; per-app mutex + RwLock concurrency model; complex dependency resolution, adoption scan, Quadlet rendering, and host-port-wait logic interleaved.
|
||||
- Safe modification: Understand the per-app mutex protocol before touching state mutation; test all changes via the lifecycle gate on .228; use the adoption scan + manifest merge logic for any new manifest evolution.
|
||||
- Test coverage: 667 unit tests green (2026-07-01); lifecycle gate covers ~8 core apps; ~30 apps untested in gate.
|
||||
|
||||
**Mesh radio configuration + boot race:**
|
||||
- Files: `core/archipelago/src/mesh/meshtastic.rs`, `core/archipelago/src/mesh/mod.rs`, tests at `tests/lifecycle/bats/meshtastic.bats`
|
||||
- Why fragile: Radio boot-race fixed (2026-07-28, `a8c4694c`/`3f76b496`); on-air config apply must finish before device is used. Earlier versions had probe-boot-race + live config propagation issues. Must verify on real hardware.
|
||||
- Safe modification: Any mesh changes require E2E test on real LoRa radios (dev-box ↔ x250-dev, or fleet broadcast); unit tests alone won't catch RF timing issues.
|
||||
- Test coverage: 8-stage on-air smoke test in `tests/multinode/meshtastic.sh` (run manually; not in canonical gate).
|
||||
|
||||
**Lightning payment state machine:**
|
||||
- Files: `core/archipelago/src/api/rpc/lnd/wallet.rs:payinvoice()`
|
||||
- Why fragile: Slow multi-hop payments (>15s) previously surfaced as "failed" while settling in background; client-side 15s timeout was aborting the wait. Fixed by commit `614a0f5a` (120s wait, pending status, lnd.paymentstatus poll). Must verify on Framework PT with real multi-hop.
|
||||
- Safe modification: Any lnd state changes must test full payment lifecycle: invoice creation, encoding, send, multi-hop wait, settlement confirmation. Verify on Framework PT before release.
|
||||
- Test coverage: Local LND payinvoice smoke test; no multinode lightning routing test in gate.
|
||||
|
||||
## Scaling Limits
|
||||
|
||||
**Uninstall progress bar truthfulness:**
|
||||
- Current capacity: Uninstall now has timeouts (fixed 2026-07-26) but progress-bar still reports fake stages (full-red full-opacity).
|
||||
- Limit: Long uninstalls (>30s) show no real progress; bar claims "uninstalling" for the full duration.
|
||||
- Scaling path: Backend must emit real progress events (% complete, stage name); UI must poll + display truthfully; integrate into all 5 gate iterations (not just 1 throw-away app).
|
||||
|
||||
**Federation node list deduplication on disk bloat:**
|
||||
- Current capacity: `federation/storage.rs:dedup_nodes_by_onion()` reads entire nodes.json into memory each time a node is added/synced. At N federated peers, O(N) memory + O(N²) comparisons per operation.
|
||||
- Limit: No hard limit measured; scales fine up to hundreds of peers. Beyond 1000+ peers, memory/time may become visible.
|
||||
- Scaling path: Switch to a disk-backed database (e.g., rocksdb) for federation state if peer count grows; or implement incremental dedup on disk writes (preserve dedup state, only recompute on load).
|
||||
|
||||
**Lifecycle gate iteration count:**
|
||||
- Current capacity: `ARCHY_ITERATIONS=5` runs 5 full cycles (stop/start/restart/survive per app). Entire run takes ~8–12 hours on .228.
|
||||
- Limit: Cannot easily scale to 10+ iterations without timeout risks; per-app timeout tuning is manual.
|
||||
- Scaling path: Add per-app timeout tuning (manifest field); parallelize per-app tests where safe (currently serial to avoid contention).
|
||||
|
||||
## Dependencies at Risk
|
||||
|
||||
**Reticulum transport daemon process group:**
|
||||
- Risk: Pre-fix (before `be50c886`), process group wasn't cleaned up on drop. Fork-bombs or dangling processes possible under error conditions.
|
||||
- Impact: Stale reticulum processes accumulating over time; resource leaks on node.
|
||||
- Migration plan: Code fix already deployed (commit `7a7fec21`); no active risk. Monitor fleet for stale python processes post-deployment.
|
||||
|
||||
**Podman socket mount security model:**
|
||||
- Risk: Apps mounting `/run/podman/podman.sock` get full container-management access. Not restricted by the security policy (AppArmor profiles not applied).
|
||||
- Files: `core/archipelago/src/container/prod_orchestrator.rs:135-137` (detection), manifests for apps with podman mounts (e.g., portainer)
|
||||
- Impact: A compromised app with podman socket access can start/stop/delete any container on the node.
|
||||
- Recommendation: Restrict podman socket mounts to admin-only apps (portainer, docker-api tools); document risk; consider socket filtering layer (selinux context, etc.) once AppArmor is wired.
|
||||
|
||||
**Bitcoin version multi-version branch not fleet-wide:**
|
||||
- Risk: Branch `bitcoin-version-bulletproof` (base `095a76cd`) carries multi-version support but hasn't been deployed fleet-wide yet. .228 carries it; others still run single version.
|
||||
- Impact: Users on single-version nodes can't switch versions; version mismatch across fleet breaks federation.
|
||||
- Migration plan: Coordinated OTA + catalog publish + `:latest` repoint sequencing per `docs/bitcoin-version-bulletproof-rollout.md`. Awaiting user decision on timing.
|
||||
|
||||
## Missing Critical Features
|
||||
|
||||
**Developer tooling CLI suite:**
|
||||
- Problem: Third-party developers need `archy app validate/render/local-install/lifecycle-test` tooling before external registry launches.
|
||||
- Blocks: External marketplace (workstream C); external developer onboarding.
|
||||
- Status: Not yet built; documented in APP-PACKAGING-MIGRATION-PLAN.md step 5.
|
||||
|
||||
**Manifest-distributed registry flip:**
|
||||
- Problem: Manifests still travel via OTA disk rsync. The signed catalog currently distributes only image overrides, not full manifests. Workstream B phases 1+2 done; not yet fleet-deployed.
|
||||
- Blocks: Cannot confidently add/bump apps without re-signing the catalog.
|
||||
- Status: Code ready; flip awaits authorization + timing call from user.
|
||||
|
||||
**Phase-3 Quadlet default-flip:**
|
||||
- Problem: Orchestrator still uses legacy cgroup-based container management; Phase-3 `use_quadlet_backends` switch exists but is opt-in only.
|
||||
- Blocks: Resolves container thrashing; unlocks independent app restarts; unblocks lifecycle perfection (workstream F).
|
||||
- Status: Code validated on .228/.198 (commit pending); ready to flip when multinode gate passes.
|
||||
|
||||
## Test Coverage Gaps
|
||||
|
||||
**~30 apps with zero app-specific assertions:**
|
||||
- What's not tested: Apps like grafana, jellyfin, vaultwarden, penpot, nextcloud, photoprism, uptime-kuma, homeassistant, etc. have no app-specific health checks beyond "container running."
|
||||
- Files: `tests/lifecycle/bats/all-apps-matrix.bats`, `tests/lifecycle/bats/all-apps-lifecycle.bats` (generic baseline coverage)
|
||||
- Risk: App-specific bugs (API down, data corruption, dependency failure) go unnoticed until user encounters them.
|
||||
- Priority: Medium — baseline coverage is a real safety net; app-specific assertions are a "nice to harden" backlog item, not a gate blocker.
|
||||
- Approach: Add per-app health RPC endpoints or HTTP probes; wire into the gate as opt-in per-app test suites.
|
||||
|
||||
**Progress UI assertions incomplete:**
|
||||
- What's not tested: Install + uninstall must report monotonic, truthful progress. No stage/percentage assertions in the gate.
|
||||
- Files: `neode-ui/src/components/AppCard.vue`, `core/archipelago/src/api/rpc/package/install.rs` (backend progress events)
|
||||
- Risk: Silent hangs or fake progress bars are invisible to the gate.
|
||||
- Priority: High — immich/grafana uninstall was stuck full-red (fixed); progress truthfulness is part of definition of done for workstream F.
|
||||
- Approach: Backend must emit real progress events; UI must display & test them; integrate into canonical gate (currently opt-in).
|
||||
|
||||
**All-apps matrix in cascade gate:**
|
||||
- What's not tested: `ARCHY_GATE_CASCADE=1` runs ONE throwaway app's uninstall/reinstall. Must extend to multi-container stacks (immich, btcpay, mempool) and all ~40 installed apps.
|
||||
- Files: `tests/lifecycle/bats/cascade-uninstall.bats` (single-app variant)
|
||||
- Risk: Multi-container app uninstall bugs (e.g., orphan postgres container) go undetected.
|
||||
- Priority: High — part of workstream F definition of done.
|
||||
- Approach: Parametrize cascade test over all manifest IDs; run 5 cascades total (not 5 per app to save time); gate-pass requires zero ghost containers post-uninstall.
|
||||
|
||||
---
|
||||
|
||||
*Analysis based on codebase state 2026-07-29. Issues tracked in `docs/UNIFIED-TASK-TRACKER.md` (day-to-day) and `docs/PRODUCTION-MASTER-PLAN.md` (historical narrative).*
|
||||
159
.planning/codebase/CONVENTIONS.md
Normal file
159
.planning/codebase/CONVENTIONS.md
Normal file
@ -0,0 +1,159 @@
|
||||
# Coding Conventions
|
||||
|
||||
**Analysis Date:** 2026-07-29
|
||||
|
||||
## Naming Patterns
|
||||
|
||||
**Files:**
|
||||
- TypeScript/Vue: PascalCase for components (e.g., `ToggleSwitch.vue`, `SendBitcoinModal.vue`), camelCase for composables and stores (e.g., `useFileType.ts`, `controller.ts`)
|
||||
- Rust: snake_case for modules and files (e.g., `bitcoin_rpc.rs`, `storage_crypto.rs`)
|
||||
- Test files: co-located with source in `__tests__/` subdirectories with `.test.ts` or `.spec.ts` suffix for Vitest, `.bats` for shell tests
|
||||
- Constants in TypeScript use UPPER_SNAKE_CASE within modules (e.g., `IMAGE_EXTS`, `CATEGORY_COLORS` in `useFileType.ts`)
|
||||
|
||||
**Functions:**
|
||||
- TypeScript/Vue: camelCase for all functions (e.g., `getFileCategory`, `formatSize`, `useFileType`)
|
||||
- Composables: `use` prefix for Vue composables (e.g., `useFileType`, `useToast`, `useMessageToast`) — exported as named exports or default exports
|
||||
- Store functions (Pinia): defined with snake_case action names, exported from `defineStore` factory
|
||||
- Rust: snake_case for all functions and methods (e.g., `doesnt_reallocate`, following Rust conventions)
|
||||
|
||||
**Variables:**
|
||||
- TypeScript: camelCase for local variables and reactive refs (e.g., `modelValue`, `isActive`, `gamepadCount`)
|
||||
- Refs (Vue 3): prefix not required, but convention is lowercase start (e.g., `const ext = ref('jpg')`)
|
||||
- Computed properties: camelCase, explicit `.value` suffix in templates when needed
|
||||
- Parameters: camelCase, typed explicitly in TypeScript (e.g., `password: string`, `isDir: Ref<boolean>`)
|
||||
|
||||
**Types:**
|
||||
- TypeScript: PascalCase for type aliases and interfaces (e.g., `RPCOptions`, `FileCategory`, `CatalogVersionInfo`)
|
||||
- Union types: PascalCase (e.g., `PendingState = 'pending' | 'sent' | 'approved'`)
|
||||
- Component props: typed with `defineProps<{ ... }>()` syntax in `<script setup>`
|
||||
- Rust: PascalCase for structs and enums, snake_case for fields within them
|
||||
|
||||
## Code Style
|
||||
|
||||
**Formatting:**
|
||||
- No global Prettier config; code style follows project patterns incrementally
|
||||
- Vue components: single-file components (`.vue`) with `<template>`, `<script setup>`, optional `<style scoped>`
|
||||
- TypeScript: indentation is 2 spaces (visible in `vitest.config.ts`, Vue components, test files)
|
||||
- Line width: no strict enforcement observed; pragmatic wrapping around 80–100 characters
|
||||
- Arrow functions preferred for short callbacks: `(x) => x * 2`
|
||||
- Template strings for multi-line formatting
|
||||
|
||||
**Linting:**
|
||||
- No `.eslintrc` detected at repo root or `neode-ui/` level
|
||||
- Rust: Clippy allowances declared at crate level in `main.rs` (`#![allow(...)]`) to suppress stylistic lints and focus CI on correctness issues
|
||||
- Examples of suppressed Clippy lints: `too_many_arguments`, `type_complexity`, `enum_variant_names`, `unused_io_amount`
|
||||
|
||||
## Import Organization
|
||||
|
||||
**Order:**
|
||||
1. Vue framework imports (`import { computed, ref, type Ref } from 'vue'`)
|
||||
2. Library imports (`import { defineStore } from 'pinia'`, `import { format } from 'date-fns'`)
|
||||
3. Local module imports (`import { useFileType } from '../useFileType'`, `import { rpcClient } from '../api/rpc-client'`)
|
||||
4. Types/interfaces (inline in import statements via `type` keyword when needed)
|
||||
5. No blank lines required between groups in practice
|
||||
|
||||
**Path Aliases:**
|
||||
- TypeScript: `@` alias maps to `src/` (configured in `vitest.config.ts` and `tsconfig.json`)
|
||||
- Usage: `import { displayVersion } from '@/utils/version'`
|
||||
- Rust: crate-relative paths (`use crate::module::submodule`) and external crate paths
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Patterns:**
|
||||
- TypeScript: explicit try-catch with error type narrowing (e.g., `if (error instanceof Error) { ... }`)
|
||||
- RPC client (`rpc-client.ts`): catches fetch errors, AbortError, and HTTP errors; distinguishes retryable (502, 503) from permanent (401, 403)
|
||||
- Rust: `anyhow::Result<T>` for fallible operations; `?` operator for error propagation; `.context("message")` for adding context
|
||||
- Backend error responses: JSON-RPC format with `error: { code, message, data? }` structure; UI catches and displays via toast system
|
||||
- Network errors: automatic retry with exponential backoff (600ms × (attempt + 1) with jitter); configurable `maxRetries` per call
|
||||
|
||||
## Logging
|
||||
|
||||
**Framework:** console object for frontend, `tracing` crate for Rust backend
|
||||
|
||||
**Patterns:**
|
||||
- Frontend: `console.warn`, `console.error` used selectively (e.g., `[RPC]` prefixed logs in `rpc-client.ts` for session/CSRF events)
|
||||
- Rust: `tracing::info!`, `tracing::warn!` for structured logging; `println!` avoided in production code
|
||||
- No log levels enforced or documented; pragmatic use based on severity
|
||||
|
||||
## Comments
|
||||
|
||||
**When to Comment:**
|
||||
- Explain non-obvious retry logic, timeout decisions, CSRF handling (see `rpc-client.ts` lines 138–178 for example)
|
||||
- Clarify why a workaround exists (e.g., "Already on the login page: redirecting = a full reload")
|
||||
- Document integration points with backend RPC methods and their expected response shapes
|
||||
- Avoid redundant comments restating what the code obviously does
|
||||
|
||||
**JSDoc/TSDoc:**
|
||||
- Function parameter types documented inline via TypeScript type annotations (e.g., `ext: Ref<string>`)
|
||||
- Minimal use of explicit JSDoc blocks; type signature is the primary documentation
|
||||
- Comments above exports explain purpose in one sentence (e.g., "// RPC Client for connecting to Archipelago backend")
|
||||
- Optional fields in interfaces documented via property-level comments (e.g., `/** Abort the call from the outside … */`)
|
||||
|
||||
## Function Design
|
||||
|
||||
**Size:** Functions are typically 5–50 lines; error-handling paths in `callInner<T>` (`rpc-client.ts`) stretch to 120 lines but remain single-responsibility (retry logic + error classification)
|
||||
|
||||
**Parameters:**
|
||||
- Prefer object parameters for 3+ arguments (e.g., `RPCOptions` object over separate `method, params, timeout`)
|
||||
- Vue composables accept `Ref<T>` types to maintain reactivity (e.g., `useFileType(ext: Ref<string>, isDir: Ref<boolean>)`)
|
||||
- Store action functions accept only necessary parameters; broader state via closure
|
||||
|
||||
**Return Values:**
|
||||
- Composables return object with properties (computed values + reactive refs): `{ category, isImage, isAudio, ... }`
|
||||
- Store actions return `void` or the modified state
|
||||
- Utilities return simple values or objects (e.g., `formatSize` returns string, `formatDate` returns string)
|
||||
- Async functions return `Promise<T>` with explicit type parameters (e.g., `async call<T>(options): Promise<T>`)
|
||||
|
||||
## Module Design
|
||||
|
||||
**Exports:**
|
||||
- Composables export a single named function and helper functions: `export function useFileType(...)`, `export function getFileCategory(...)`
|
||||
- Stores export the Pinia store factory: `export const useControllerStore = defineStore(...)`
|
||||
- RPC client exports as singleton instance: `export const rpcClient = new RPCClient()`
|
||||
- Utilities export multiple helpers from the same file (e.g., `formatSize`, `formatDate` from same module)
|
||||
|
||||
**Barrel Files:**
|
||||
- Not observed as a primary pattern; each file self-documents its exports
|
||||
- Imports use direct paths (e.g., `from '../composables/useFileType'`) rather than barrel `index.ts`
|
||||
- Test files import specific utilities directly to minimize test setup complexity
|
||||
|
||||
## Type Safety
|
||||
|
||||
**Vue 3 with TypeScript:**
|
||||
- Components use `<script setup lang="ts">` with `defineProps<{ ... }>()` and `defineEmits<{ ... }>()`
|
||||
- Props explicitly typed as interfaces/objects with required/optional fields marked
|
||||
- Events typed as call signatures (e.g., `'update:modelValue': [value: boolean]`)
|
||||
- Reactive variables typed at declaration: `const isActive = ref<boolean>(false)`, or via inference when obvious
|
||||
|
||||
**Rust:**
|
||||
- Explicit type annotations on public APIs; inference acceptable inside functions
|
||||
- Generic parameters used to encode interface contracts (e.g., `struct DataUrl<'a>`)
|
||||
- Pattern matching to handle enums and `Option<T>` / `Result<T, E>` types safely
|
||||
|
||||
## Component Architecture (Vue)
|
||||
|
||||
**File structure:**
|
||||
- Single-file components with template → script → (optional) style
|
||||
- Props come first, emits second, internal state/computed/methods follow
|
||||
- One component per file (naming matches the file name)
|
||||
- Slots used minimally; prefer explicit prop configuration over slot forwarding
|
||||
|
||||
**Reactivity:**
|
||||
- `ref()` for primitive/object state; `computed()` for derived values
|
||||
- `watch()` used for side effects on ref changes (not extensively shown in samples but implied)
|
||||
- Pinia stores used for global state (authentication, app list, mesh status, etc.)
|
||||
|
||||
## Constants and Enums
|
||||
|
||||
**Pattern:** Constants are module-level `const` with UPPER_SNAKE_CASE names and immutable type annotations:
|
||||
|
||||
```typescript
|
||||
const IMAGE_EXTS = new Set(['jpg', 'jpeg', ...])
|
||||
const CATEGORY_COLORS: Record<FileCategory, string> = { ... }
|
||||
```
|
||||
|
||||
**Enums:** Type aliases preferred over TypeScript `enum` keyword (e.g., `type FileCategory = 'folder' | 'image' | ...`)
|
||||
|
||||
---
|
||||
|
||||
*Convention analysis: 2026-07-29*
|
||||
235
.planning/codebase/INTEGRATIONS.md
Normal file
235
.planning/codebase/INTEGRATIONS.md
Normal file
@ -0,0 +1,235 @@
|
||||
# External Integrations
|
||||
|
||||
**Analysis Date:** 2026-07-29
|
||||
|
||||
## APIs & External Services
|
||||
|
||||
**Bitcoin Protocol:**
|
||||
- Bitcoin Core RPC endpoint - Primary blockchain interaction
|
||||
- SDK/Client: `bitcoin` crate (v0.32.5), `reqwest` HTTP client
|
||||
- Endpoint: `http://127.0.0.1:8332/` (configurable)
|
||||
- Used for: Transaction broadcasting, UTXO validation, network sync status
|
||||
- Auth: Basic HTTP auth (hardcoded RPC credentials in containers)
|
||||
|
||||
**Lightning Network:**
|
||||
- Lightning Network Daemon (LND) - Layer 2 payments
|
||||
- SDK/Client: Native REST API (`reqwest` + `serde_json`)
|
||||
- REST Endpoint: `http://localhost:8080/` (container network)
|
||||
- gRPC Endpoint: `http://localhost:10009/` (not currently used by backend)
|
||||
- P2P Port: 9735
|
||||
- Used for: Channel management, payment invoicing, routing
|
||||
- Auth: Macaroon-based authentication (stored in `lnd-data:/root/.lnd`)
|
||||
- Proxied: `/proxy/lnd/` → backend RPC auth + CORS handling
|
||||
|
||||
**Nostr Protocol:**
|
||||
- Nostr Relays - Node discovery and encrypted messaging
|
||||
- SDK/Client: `nostr-sdk` crate (v0.44, with NIP-04 and NIP-44 support)
|
||||
- Usage: Optional, opt-in via `NOSTR_DISCOVERY_ENABLED` config
|
||||
- Relays: Configurable via comma-separated `NOSTR_RELAYS` env var
|
||||
- Transport: SOCKS5 Tor proxy optional via `NOSTR_TOR_PROXY` config
|
||||
- Features: Ed25519 node identity publishing, encrypted peer handshake
|
||||
- Related files: `core/archipelago/src/nostr_relays.rs`, `nostr_discovery.rs`, `nostr_handshake.rs`
|
||||
|
||||
**Mesh Networking:**
|
||||
- Reticulum Protocol (RNS v1.3.5) - Local mesh radio coordination
|
||||
- Daemon: `reticulum-daemon/` (Python daemon, RNS 1.3.5 + LXMF 1.0.1)
|
||||
- Interface: Supervised as managed container
|
||||
- LoRa Radio: Serial2 communication over USB (Meshtastic-compatible radios)
|
||||
- P2P Discovery: mDNS (multicast DNS via `mdns-sd` crate)
|
||||
- Mesh Ports: IPv6 dual-stack listeners (port mirroring to containers)
|
||||
- Related files: `core/archipelago/src/mesh/`, `core/archipelago/src/mesh_ports.rs`
|
||||
|
||||
**Tor (Optional):**
|
||||
- Tor SOCKS5 Proxy - Anonymous network routing
|
||||
- Endpoint: `socks5h://127.0.0.1:9050` (default, configurable)
|
||||
- Used for: Nostr relay connections (when routed through Tor)
|
||||
- Client: `reqwest` with SOCKS feature enabled
|
||||
- Config key: `nostr_tor_proxy`
|
||||
|
||||
**Fedimint:**
|
||||
- Federated Custody Chaumian Mint - Alternative payment layer
|
||||
- SDK/Client: JSON-RPC API (`reqwest` + `serde_json`)
|
||||
- Endpoint: `http://localhost:8174/` (container network)
|
||||
- P2P Port: 8173
|
||||
- UI Port: 8175 (guardian management)
|
||||
- Used for: Custody alternatives, blind signatures
|
||||
- Related files: `core/archipelago/src/api/rpc/fedimint.rs`
|
||||
|
||||
**Decentralized Web Node (DWN):**
|
||||
- DWN Health Check - Decentralized identity messaging
|
||||
- Endpoint: `http://127.0.0.1:3100/health`
|
||||
- Purpose: Node provisioning verification
|
||||
- Related files: `core/archipelago/src/constants.rs`
|
||||
|
||||
## Data Storage
|
||||
|
||||
**Databases:**
|
||||
- **Application Databases (optional, app-managed):**
|
||||
- PostgreSQL: Immich, IndeedHub, Penpot, Endurain, Nextcloud
|
||||
- MySQL/MariaDB: Mempool, Nextcloud
|
||||
- Redis/Valkey: Immich, IndeedHub, Penpot (caching/sessions)
|
||||
- SQLite: Optional local state (Cargo.toml comments out `sqlx`)
|
||||
|
||||
Connection: Via container network (not directly accessible from backend)
|
||||
|
||||
- **Key-Value Stores:**
|
||||
- In-memory cache: Tokio sync primitives (Arc<DashMap>, Mutex)
|
||||
- Persistent app state: User credentials, device tokens, manifest cache stored in `$DATA_DIR`
|
||||
|
||||
**File Storage:**
|
||||
- Local filesystem only
|
||||
- User data directory: `$ARCHIPELAGO_DATA_DIR` (default `/var/lib/archipelago/`)
|
||||
- Subdirectories: `apps/`, `secrets/`, `backups/`, `content/`, `catalog/`
|
||||
- App-specific: `/var/lib/archipelago/<app>/` (mounted into containers)
|
||||
- Content sharing: Peer-to-peer via HTTP Range requests (see `content_server.rs`)
|
||||
|
||||
**Caching:**
|
||||
- No external cache service
|
||||
- In-app caching: Tokio-spawned tasks, Arc<DashMap> for concurrent access
|
||||
- Browser caching: Workbox service worker (5-min API cache, 30-day asset cache, 1-year font cache)
|
||||
|
||||
## Authentication & Identity
|
||||
|
||||
**Auth Provider:**
|
||||
- Custom JWT-based (node-local)
|
||||
- Implementation: Ed25519 signing (key in `credentials/` directory)
|
||||
- Session tokens: Stored browser-side via cookies (CSRF token middleware)
|
||||
- Related files: `core/archipelago/src/auth.rs`, `core/archipelago/src/identity_manager.rs`
|
||||
|
||||
**BIP-39 Mnemonic Seed:**
|
||||
- Seed generation: `bip39` crate (v2.1.0)
|
||||
- HD key derivation: `bitcoin` crate (v0.32.5) with BIP-32
|
||||
- Signing: Ed25519 for identity, ECDSA for Bitcoin transactions
|
||||
- Related files: `core/archipelago/src/seed.rs`
|
||||
|
||||
**Identity (Decentralized):**
|
||||
- Nostr npub (from Ed25519 keys)
|
||||
- DID (Decentralized Identifier) via did:dht (BitTorrent DHT)
|
||||
- Related files: `core/archipelago/src/identity.rs`, `core/archipelago/src/nostr_discovery.rs`
|
||||
|
||||
**2FA:**
|
||||
- TOTP (Time-based One-Time Password)
|
||||
- Library: `totp-rs` (v5.7, with otpauth and gen_secret)
|
||||
- QR generation: `qrcode` crate (v0.14)
|
||||
- Encrypted storage: Argon2-derived key + ChaCha20-Poly1305
|
||||
- Related files: `core/archipelago/src/totp.rs`
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
**Error Tracking:**
|
||||
- Not integrated (logging only)
|
||||
|
||||
**Logs:**
|
||||
- Approach: Structured logging via `tracing` crate
|
||||
- Output: stdout (configurable level via `RUST_LOG` or config file)
|
||||
- Subscriber: `tracing-subscriber` with `env-filter`
|
||||
- Related files: `core/archipelago/src/monitoring.rs`, `core/archipelago/src/health_monitor.rs`
|
||||
|
||||
**Health Monitoring:**
|
||||
- Health checks: Container liveness probes (Docker/Podman healthcheck)
|
||||
- System metrics: Disk space, memory, container startup tiers
|
||||
- Related files: `core/archipelago/src/health_monitor.rs`
|
||||
|
||||
## CI/CD & Deployment
|
||||
|
||||
**Hosting:**
|
||||
- Bare metal (Debian Linux) with Podman rootless containers
|
||||
- Fleet nodes: .198, .228, .116, x250-dev, Framework PT (various test/dev targets)
|
||||
|
||||
**CI Pipeline:**
|
||||
- Git-based CI: Gitea (self-hosted at `.160:3000` and `.168:3000`)
|
||||
- Push accounts: `gitea-ai` (for protected main branch)
|
||||
- Build pipeline: Local `cargo build`/`npm run build` (not centralized CI/CD service)
|
||||
- Release: Manual versioning + signed OTA manifests
|
||||
- Docker builds: Local `docker-compose` or Dockerfile.web/backend
|
||||
|
||||
**Deployment:**
|
||||
- Container orchestration: Podman with Quadlet systemd units (Phase 3+)
|
||||
- Legacy fallback: Direct `podman create + systemctl start`
|
||||
- OTA (Over-The-Air): Signed manifest-driven updates (v1.7+)
|
||||
- Sideload: Binary + tarball to `/usr/local/bin/archipelago` and `/opt/archipelago/`
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
**Required env vars:**
|
||||
- `ARCHIPELAGO_DATA_DIR` - Local state directory (default: platform-specific)
|
||||
- `ARCHIPELAGO_LOG_LEVEL` - Logging verbosity (default: `info`)
|
||||
- `CONTAINER_RUNTIME` - `podman` or `docker` (auto-detect by default)
|
||||
- `NOSTR_DISCOVERY_ENABLED` - Enable node publishing to Nostr relays (false by default)
|
||||
- `NOSTR_RELAYS` - Comma-separated relay URLs (if discovery enabled)
|
||||
- `NOSTR_TOR_PROXY` - SOCKS5 proxy address (optional, routes Nostr through Tor)
|
||||
- `VITE_AIUI_URL` - AIUI chat interface URL (frontend, optional)
|
||||
- `BACKEND_URL` - Backend target for dev server (frontend, default: `http://localhost:5959`)
|
||||
|
||||
**Secrets location:**
|
||||
- At-rest: `$DATA_DIR/credentials/` (user.json with encrypted TOTP, session keys)
|
||||
- In-container: Mounted as read-only volumes, never logged
|
||||
- No `.env` file in production (config-driven)
|
||||
|
||||
## Webhooks & Callbacks
|
||||
|
||||
**Incoming:**
|
||||
- Marketplace callbacks - App catalog updates from registry
|
||||
- Peer discovery webhooks (via Nostr relays)
|
||||
- Related files: `core/archipelago/src/webhooks.rs`
|
||||
|
||||
**Outgoing:**
|
||||
- Device token push notifications (not yet implemented)
|
||||
- App install/uninstall event notifications (framework-pt integration)
|
||||
- Related files: `core/archipelago/src/device_tokens.rs`
|
||||
|
||||
## Container-Based Services (Orchestrated by Archipelago)
|
||||
|
||||
**Bitcoin Stack:**
|
||||
- Bitcoin Core (lncm/bitcoind:v27.0 or knots variant)
|
||||
- ElectrumX (via separate image)
|
||||
- Electrs (alternative to ElectrumX)
|
||||
|
||||
**Lightning/Payments:**
|
||||
- LND (lightninglabs/lnd:v0.17.4-beta+)
|
||||
- BTCPay Server (btcpayserver:1.13.5+)
|
||||
- Fedimint (fedimint/fedimintd:v0.10.0+)
|
||||
|
||||
**Media & Content:**
|
||||
- Immich (self-hosted photo/media library)
|
||||
- Nextcloud (cloud storage)
|
||||
- OnlyOffice (document server)
|
||||
- Penpot (design tool)
|
||||
- SearXNG (search engine)
|
||||
- FileBrowser (file management UI)
|
||||
|
||||
**Infrastructure:**
|
||||
- nginx (reverse proxy, CORS, rate limiting)
|
||||
- Home Assistant (home automation hub)
|
||||
- Grafana (metrics dashboard)
|
||||
- ThunderHub (Lightning node UI)
|
||||
- Mempool Explorer (blockchain monitor)
|
||||
- Endurain (fitness tracking)
|
||||
- Morphos (file converter)
|
||||
- IndeedHub (job board)
|
||||
- Pine (voice assistant)
|
||||
|
||||
## Integration Points (API Contracts)
|
||||
|
||||
**Backend ↔ Frontend (gRPC-style RPC):**
|
||||
- Endpoint: `/rpc/v1/*` (HTTP/REST)
|
||||
- Related files: `core/archipelago/src/api/rpc/` (all RPC handlers)
|
||||
- Major modules: `auth.rs`, `bitcoin.rs`, `lnd/`, `container.rs`, `marketplace.rs`, `mesh/`
|
||||
|
||||
**Frontend ↔ App Iframes:**
|
||||
- Endpoint: `/app/<app-name>/*` (proxied to container)
|
||||
- Sandbox: Cross-origin iframe isolation
|
||||
|
||||
**Mesh Node ↔ Reticulum:**
|
||||
- Serial: USB LoRa radio (Meshtastic-compatible)
|
||||
- Protocol: Binary Meshcore format
|
||||
- Related files: `core/archipelago/src/mesh/`
|
||||
|
||||
**Catalog ↔ App Registry:**
|
||||
- Endpoint: `/api/app-catalog` (cached from registry)
|
||||
- Format: Signed YAML manifest + SHA256 verification
|
||||
- Related files: `core/archipelago/src/marketplace.rs`, `app_catalog/`
|
||||
|
||||
---
|
||||
|
||||
*Integration audit: 2026-07-29*
|
||||
176
.planning/codebase/STACK.md
Normal file
176
.planning/codebase/STACK.md
Normal file
@ -0,0 +1,176 @@
|
||||
# Technology Stack
|
||||
|
||||
**Analysis Date:** 2026-07-29
|
||||
|
||||
## Languages
|
||||
|
||||
**Primary:**
|
||||
- Rust 2021 edition - Backend server (Archipelago daemon, container orchestrator)
|
||||
- TypeScript 5.9 - Frontend (Vue components, application code)
|
||||
- Vue 3 (TypeScript) - UI framework and reactive components
|
||||
- Python 3.13 - Reticulum mesh daemon (RNS/LXMF protocols)
|
||||
|
||||
**Secondary:**
|
||||
- JavaScript - Build scripts, mock backend (`neode-ui/mock-backend.js`), test utilities
|
||||
- YAML - Configuration (docker-compose, app manifests)
|
||||
- Shell - Build scripts, deployment helpers
|
||||
|
||||
## Runtime
|
||||
|
||||
**Environment:**
|
||||
- Rust (1.70+) - Native compiled binaries
|
||||
- Node.js 22 (Alpine-based containers) - Frontend dev/build, mock backend
|
||||
- Python 3.13 - Reticulum daemon runtime
|
||||
- Tokio async runtime (v1, full features) - Async server runtime
|
||||
|
||||
**Package Manager:**
|
||||
- npm (v10+) - JavaScript dependencies
|
||||
- Cargo (v1.70+) - Rust dependencies
|
||||
- pip - Python dependencies (Reticulum stack)
|
||||
- Lockfiles: `neode-ui/package-lock.json`, `core/Cargo.lock`
|
||||
|
||||
## Frameworks
|
||||
|
||||
**Core:**
|
||||
- Tokio (v1) - Async Rust runtime, full features (networking, signals, sync primitives)
|
||||
- Vue 3 (v3.5) - Progressive web framework with TypeScript support
|
||||
- Hyper (v0.14) - HTTP/1 server framework with WebSocket support
|
||||
- Reticulum (v1.3.5) - Mesh networking protocol stack
|
||||
- LXMF (v1.0.1) - Lightweight message format protocol
|
||||
|
||||
**HTTP & WebSocket:**
|
||||
- Hyper v0.14 (HTTP/1, full features) - Core HTTP server
|
||||
- Hyper-util v0.1 - HTTP utilities
|
||||
- Tower v0.5 - Middleware and service composing
|
||||
- Tower-http v0.6 - CORS, tracing middleware
|
||||
- Hyper-ws-listener v0.3 - WebSocket upgrade handler
|
||||
- Tokio-tungstenite v0.20 - WebSocket client/server implementation
|
||||
- Reqwest v0.11 - HTTP client (with rustls-tls, SOCKS proxy support, JSON)
|
||||
|
||||
**Frontend Build:**
|
||||
- Vite v7.2 - Build tool and dev server
|
||||
- Vue-tsc v3.1 - TypeScript compilation for Vue
|
||||
- Tailwind CSS v3.4 - Utility-first CSS framework
|
||||
- Autoprefixer v10.4 - CSS vendor prefixes
|
||||
- PostCSS v8.5 - CSS processing
|
||||
|
||||
**Testing:**
|
||||
- Vitest v3.1 - Unit test runner (Vite-native)
|
||||
- Playwright v1.58 - E2E testing (browser automation)
|
||||
- @vue/test-utils v2.4 - Vue component testing
|
||||
- JSDOM v25 - DOM implementation for tests
|
||||
- Tokio-test v0.4 - Async Rust test utilities
|
||||
|
||||
**PWA:**
|
||||
- Vite-plugin-pwa v1.2 - Progressive Web App support
|
||||
- Workbox integration - Service worker caching strategies
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
**Critical:**
|
||||
- bitcoin v0.32.5 - Bitcoin library (with rand-std feature for BIP-39/BIP-32)
|
||||
- bip39 v2.1.0 - Mnemonic seed generation
|
||||
- nostr-sdk v0.44 - Nostr protocol (NIP-04, NIP-44 encrypted messaging)
|
||||
- mainline v2 - BitTorrent DHT (did:dht decentralized identity)
|
||||
- reticulum v1.3.5 - Mesh networking protocol
|
||||
- lxmf v1.0.1 - Lightweight message format
|
||||
|
||||
**Cryptography:**
|
||||
- ed25519-dalek v2.2 - Ed25519 digital signatures (with rand_core)
|
||||
- curve25519-dalek v4.1 - X25519 elliptic curve (key agreement)
|
||||
- blake3 v1 - BLAKE3 hash function
|
||||
- bcrypt v0.15 - Password hashing
|
||||
- sha2 v0.10 - SHA-256 hashing
|
||||
- hmac v0.12 - HMAC authentication
|
||||
- argon2 v0.5 - Argon2 password hashing
|
||||
- chacha20poly1305 v0.10 - AEAD encryption
|
||||
- zeroize v1.8 - Secure memory wiping
|
||||
|
||||
**Authentication & Identity:**
|
||||
- uuid v1.0 - UUID generation (v4)
|
||||
- totp-rs v5.7 - TOTP 2FA (with otpauth, gen_secret)
|
||||
- qrcode v0.14 - QR code generation (server-side)
|
||||
|
||||
**Data Serialization:**
|
||||
- serde v1.0 - Serialization framework (with derive)
|
||||
- serde_json v1.0 - JSON codec
|
||||
- serde_yaml v0.9 - YAML codec
|
||||
- ciborium v0.2 - CBOR encoding/decoding
|
||||
- serde_bytes v0.11 - Efficient byte serialization
|
||||
- toml v0.8 - TOML config parsing
|
||||
|
||||
**Networking & Mesh:**
|
||||
- mdns-sd v0.18 - mDNS service discovery
|
||||
- serial2-tokio v0.1 - Serial port communication (LoRa radios over USB)
|
||||
- socket2 v0.5 - Low-level socket options (IPv6_V6ONLY for dual-stack)
|
||||
- libc v0.2 - Process group signaling
|
||||
|
||||
**Compression & Archives:**
|
||||
- tar v0.4 - TAR archive creation
|
||||
- flate2 v1.0 - gzip compression
|
||||
- zip v2.0 - ZIP archive handling (LoRa firmware flashing)
|
||||
- reed-solomon-erasure v6.0 - Erasure coding (Phase 2 mesh transport)
|
||||
- hkdf v0.12 - HKDF key derivation (Phase 3 encrypted mesh)
|
||||
|
||||
**Utilities:**
|
||||
- anyhow v1.0 - Error handling
|
||||
- thiserror v1.0 - Error types with derive macros
|
||||
- tracing v0.1 - Structured logging
|
||||
- tracing-subscriber v0.3 - Log filtering and formatting
|
||||
- regex v1.10 - Pattern matching
|
||||
- chrono v0.4 - Date/time handling
|
||||
- hex v0.4 - Hex encoding/decoding
|
||||
- bs58 v0.5 - Base58 encoding (Bitcoin addresses)
|
||||
- base64 v0.21 - Base64 encoding
|
||||
- zbase32 v0.1 - Z-base-32 encoding (DHT)
|
||||
- data-encoding v2.6 - Multiple encoding schemes
|
||||
- bytes v1 - Efficient byte buffer
|
||||
- futures-util v0.3 - Async utilities
|
||||
- http-body-util v0.1 - HTTP body utilities
|
||||
- http-body v1.0 - HTTP body abstractions
|
||||
- indexmap v2.0 - Ordered maps
|
||||
- async-trait v0.1 - Async trait methods
|
||||
- sd-notify v0.4 - Systemd watchdog notification
|
||||
|
||||
**Infrastructure (Optional):**
|
||||
- iroh v1 (optional, feature-gated) - QUIC-based peer swarm engine (Phase 2)
|
||||
- iroh-blobs v0.103 (optional, feature-gated) - Content addressable storage provider
|
||||
|
||||
## Configuration
|
||||
|
||||
**Environment:**
|
||||
- Config file: `config/archipelago.toml` or `$DATA_DIR/config.yml`
|
||||
- Runtime options: Docker/Podman selection, dev/prod modes, FIPS anchor selection, Nostr discovery
|
||||
- Key env vars: `ARCHIPELAGO_DATA_DIR`, `ARCHIPELAGO_LOG_LEVEL`, `CONTAINER_RUNTIME`, `NOSTR_DISCOVERY_ENABLED`, `NOSTR_RELAYS`, `NOSTR_TOR_PROXY`
|
||||
|
||||
**Build:**
|
||||
- Cargo workspace at `core/` (5 members: archipelago, container, openwrt, performance, security)
|
||||
- Release profile: opt-level 3
|
||||
- Dev profile: opt-level 0
|
||||
- Test profile: opt-level 3
|
||||
- Cross-compilation: aarch64-unknown-linux-gnu via `.cargo/config.toml`
|
||||
|
||||
**Frontend Build:**
|
||||
- Vite config: `neode-ui/vite.config.ts` (development port 8100, production build to `../web/dist/neode-ui`)
|
||||
- TypeScript config: `neode-ui/tsconfig.json`
|
||||
- Module path alias: `@` → `src/`
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
**Development:**
|
||||
- Rust 1.70+ with Cargo
|
||||
- Node.js 22+ with npm
|
||||
- Python 3.13+ (for Reticulum daemon)
|
||||
- Docker or Podman (for local app testing)
|
||||
- rustup target: `aarch64-unknown-linux-gnu` (for ARM64 cross-compilation)
|
||||
- gcc-aarch64-linux-gnu (for cross-compilation toolchain on Linux hosts)
|
||||
|
||||
**Production:**
|
||||
- Deployment target: Debian/Alpine Linux (rootless Podman)
|
||||
- Binary output: `/usr/local/bin/archipelago` (sideloaded or via Quadlet systemd units)
|
||||
- Frontend served: nginx with Vite-built SPA (PWA manifest, service worker, CORS proxies)
|
||||
- Database support: Optional (SQLx with SQLite driver available but commented out)
|
||||
|
||||
---
|
||||
|
||||
*Stack analysis: 2026-07-29*
|
||||
434
.planning/codebase/STRUCTURE.md
Normal file
434
.planning/codebase/STRUCTURE.md
Normal file
@ -0,0 +1,434 @@
|
||||
# Codebase Structure
|
||||
|
||||
**Analysis Date:** 2026-07-29
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```
|
||||
archipelago-repo/
|
||||
├── core/ # Rust workspace root (Cargo.toml at workspace level)
|
||||
│ ├── archipelago/ # Main daemon binary (backend)
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── main.rs # Entry point, startup, background tasks
|
||||
│ │ │ ├── server.rs # HTTP server (Hyper), listener, connection multiplexing
|
||||
│ │ │ ├── state.rs # StateManager + data_model.rs (central state)
|
||||
│ │ │ ├── auth.rs # User auth, password hashing, session management
|
||||
│ │ │ ├── identity.rs # Node identity, Ed25519 keys, Tor address
|
||||
│ │ │ ├── config.rs # Config loading, data directory setup
|
||||
│ │ │ ├── api/ # HTTP API layer
|
||||
│ │ │ │ ├── handler/ # HTTP request dispatch, WebSocket, content proxy
|
||||
│ │ │ │ └── rpc/ # JSON-RPC 2.0 methods (~40 domain modules)
|
||||
│ │ │ │ ├── auth.rs # auth.login, auth.setup, etc.
|
||||
│ │ │ │ ├── container.rs # container.install, .list, .start, etc.
|
||||
│ │ │ │ ├── bitcoin.rs # bitcoin.status, bitcoin.send, etc.
|
||||
│ │ │ │ ├── mesh.rs # mesh.* (peer discovery, LoRa, federation)
|
||||
│ │ │ │ ├── wallet.rs # wallet.* (lightning, Bitcoin)
|
||||
│ │ │ │ └── [20+ other domains]
|
||||
│ │ │ ├── container/ # Container orchestration (Podman)
|
||||
│ │ │ │ ├── prod_orchestrator.rs # Main Podman lifecycle (>250KB)
|
||||
│ │ │ │ ├── boot_reconciler.rs # Periodic manifest sync loop
|
||||
│ │ │ │ ├── docker_packages.rs # Image registry, image verification
|
||||
│ │ │ │ ├── quadlet.rs # Systemd Quadlet generation
|
||||
│ │ │ │ ├── secrets.rs # Secret injection (0600 files)
|
||||
│ │ │ │ ├── lnd.rs # Lightning Network Daemon container setup
|
||||
│ │ │ │ ├── app_catalog.rs # Signed app catalog, manifest overlay
|
||||
│ │ │ │ └── [data managers, registry, image policy]
|
||||
│ │ │ ├── bootstrap.rs # Post-startup tasks (systemd units, audio stack, gamepad)
|
||||
│ │ │ ├── crash_recovery.rs # Container recovery after crash, PID marker
|
||||
│ │ │ ├── health_monitor.rs # Periodic app health checks, restart
|
||||
│ │ │ ├── mesh.rs # Mesh P2P listener, sender, LoRa radio control
|
||||
│ │ │ ├── federation.rs # Federation (DNS-SD, HTTP API)
|
||||
│ │ │ ├── fips/ # FIPS anchor (Tor bridge to peer)
|
||||
│ │ │ ├── bitcoin_rpc.rs # Bitcoin Core RPC client calls
|
||||
│ │ │ ├── bitcoin_status.rs # Bitcoin sync status polling
|
||||
│ │ │ ├── content_server.rs # Content-addressed blob server (CAP tokens)
|
||||
│ │ │ ├── blobs.rs # BlobStore (hash→file mapping, encryption)
|
||||
│ │ │ ├── wallet.rs # Lightning + Bitcoin wallet logic
|
||||
│ │ │ ├── identity_manager.rs # Seed derivation, key rotation
|
||||
│ │ │ ├── marketplace.rs # Marketplace transaction logic
|
||||
│ │ │ ├── transport.rs # Transport selection (Mesh/FIPS/Tor routing)
|
||||
│ │ │ ├── update.rs # OTA update apply, verification, rollback
|
||||
│ │ │ ├── session.rs # Session store (SQLite or in-memory)
|
||||
│ │ │ ├── rate_limit.rs # Rate limiter (per-IP, per-endpoint, per-user)
|
||||
│ │ │ ├── monitoring.rs # Metrics collection (app count, memory, etc.)
|
||||
│ │ │ ├── data_model.rs # State struct tree (serde Serialize/Deserialize)
|
||||
│ │ │ ├── constants.rs # Global constants (version, defaults)
|
||||
│ │ │ ├── peer*.rs, webhook*.rs, nostr*.rs, vpn.rs, etc.
|
||||
│ │ │ └── seed.rs # Seed storage, backup QR generation
|
||||
│ │ ├── Cargo.toml # Dependencies
|
||||
│ │ └── tests/ # Unit/integration tests
|
||||
│ │
|
||||
│ ├── container/ # Container management library (OCI types, Podman)
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── manifest.rs # Manifest struct (YAML parsing)
|
||||
│ │ │ ├── runtime.rs # Podman CLI calls (create, start, stop, logs)
|
||||
│ │ │ ├── podman_client.rs # Podman socket API client
|
||||
│ │ │ ├── image_verify.rs # Image signature verification (Cosign)
|
||||
│ │ │ └── port_manager.rs # Port allocation, conflict detection
|
||||
│ │ └── Cargo.toml
|
||||
│ │
|
||||
│ ├── security/ # Secrets management library
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── secrets_manager.rs # Secret encryption/decryption (ChaCha20)
|
||||
│ │ │ └── vault.rs # Vault storage, rotation
|
||||
│ │ └── Cargo.toml
|
||||
│ │
|
||||
│ ├── performance/ # Performance monitoring library
|
||||
│ ├── openwrt/ # OpenWrt device integration
|
||||
│ ├── Cargo.toml # Workspace manifest (members: archipelago, container, security, etc.)
|
||||
│ └── Cargo.lock # Locked dependency versions
|
||||
│
|
||||
├── neode-ui/ # Frontend (Vue 3, TypeScript)
|
||||
│ ├── src/
|
||||
│ │ ├── main.ts # Vue app entry point, Router setup, WebSocket init
|
||||
│ │ ├── App.vue # Root component (layout, nav)
|
||||
│ │ ├── router/
|
||||
│ │ │ └── index.ts # Vue Router config (routes, guards)
|
||||
│ │ ├── views/ # Page-level components (one per route)
|
||||
│ │ │ ├── Home.vue # Dashboard
|
||||
│ │ │ ├── Apps.vue # App browser + installer
|
||||
│ │ │ ├── AppDetails.vue # Single app detail + logs
|
||||
│ │ │ ├── AppSession.vue # Iframe container for app content
|
||||
│ │ │ ├── Cloud.vue # File browser (WebDAV/DWN)
|
||||
│ │ │ ├── Mesh.vue # Mesh map, contacts, messages
|
||||
│ │ │ ├── Wallet.vue # Lightning + Bitcoin addresses/sends
|
||||
│ │ │ ├── Marketplace.vue # Paid apps, content marketplace
|
||||
│ │ │ ├── Server.vue # Node status, settings, restart
|
||||
│ │ │ ├── Federation.vue # Federation peers, federation apps
|
||||
│ │ │ ├── Onboarding*/ # Multi-step setup flow
|
||||
│ │ │ └── [15+ other pages]
|
||||
│ │ ├── components/ # Reusable UI components
|
||||
│ │ │ ├── AppCard.vue # App listing card
|
||||
│ │ │ ├── AppInstaller.vue # Install modal
|
||||
│ │ │ ├── Modal.vue # Generic modal (teleported)
|
||||
│ │ │ ├── Toast.vue # Notification toast
|
||||
│ │ │ ├── MeshGraph.vue # Mesh topology graph (D3)
|
||||
│ │ │ ├── MapView.vue # Mesh map (Leaflet)
|
||||
│ │ │ ├── QrScanner.vue # QR code input
|
||||
│ │ │ └── [30+ other components]
|
||||
│ │ ├── composables/ # Logic hooks (Vue composition API)
|
||||
│ │ │ ├── useAuth.ts # Login/logout logic
|
||||
│ │ │ ├── useAppStore.ts # Access app Pinia store
|
||||
│ │ │ ├── useRpc.ts # Make RPC calls
|
||||
│ │ │ ├── useWebSocket.ts # WebSocket connection management
|
||||
│ │ │ ├── useOnboarding.ts # Onboarding flow state
|
||||
│ │ │ ├── useControllerNav.ts # Gamepad controller navigation
|
||||
│ │ │ └── [20+ other composables]
|
||||
│ │ ├── stores/ # Pinia state management (reactive stores)
|
||||
│ │ │ ├── appStore.ts # Apps list, install state
|
||||
│ │ │ ├── walletStore.ts # Lightning/Bitcoin addresses, balance
|
||||
│ │ │ ├── meshStore.ts # Mesh peers, messages
|
||||
│ │ │ ├── settingsStore.ts # User settings, theme, language
|
||||
│ │ │ ├── userStore.ts # Current user identity
|
||||
│ │ │ └── [other stores]
|
||||
│ │ ├── api/
|
||||
│ │ │ └── rpc-client.ts # RPC client library (request, WebSocket, reconnect)
|
||||
│ │ ├── services/ # Business logic (not Vue-dependent)
|
||||
│ │ │ ├── qrScanner.ts # QR scanner initialization
|
||||
│ │ │ └── [other services]
|
||||
│ │ ├── utils/ # Utility functions
|
||||
│ │ │ ├── format.ts # Date, number, currency formatting
|
||||
│ │ │ ├── validate.ts # Input validation (emails, addresses)
|
||||
│ │ │ ├── crypto.ts # Client-side crypto (BIP39, etc.)
|
||||
│ │ │ └── [helpers]
|
||||
│ │ ├── types/ # TypeScript type definitions
|
||||
│ │ │ ├── index.ts # Export all types
|
||||
│ │ │ └── [domain-specific types]
|
||||
│ │ ├── i18n.ts # Internationalization config
|
||||
│ │ ├── locales/ # Translation files
|
||||
│ │ │ ├── en.json # English
|
||||
│ │ │ ├── es.json # Spanish
|
||||
│ │ │ └── [other languages]
|
||||
│ │ ├── assets/ # Static assets
|
||||
│ │ │ └── icon/ # App icons, favicons
|
||||
│ │ ├── style.css # Global CSS (Tailwind + custom)
|
||||
│ │ ├── data/ # Static data (country lists, etc.)
|
||||
│ │ └── e2e/ # Playwright E2E tests
|
||||
│ │ ├── intro-experience.spec.ts
|
||||
│ │ └── app-launch.spec.ts
|
||||
│ │
|
||||
│ ├── public/ # Static web root
|
||||
│ │ ├── index.html # HTML entry point
|
||||
│ │ ├── favicon.ico # Browser tab icon
|
||||
│ │ ├── manifest.json # PWA manifest
|
||||
│ │ └── catalog.json # App catalog (copied from app-catalog/catalog.json)
|
||||
│ │
|
||||
│ ├── package.json # Frontend dependencies + build scripts
|
||||
│ ├── tsconfig.json # TypeScript config
|
||||
│ ├── vite.config.ts # Vite build config
|
||||
│ ├── vitest.config.ts # Vitest test runner config
|
||||
│ ├── tailwind.config.js # Tailwind CSS config
|
||||
│ ├── mock-backend.js # Dev mock server (for `npm run dev:mock`)
|
||||
│ └── [other build configs]
|
||||
│
|
||||
├── apps/ # Containerized applications (app manifests + build scripts)
|
||||
│ ├── bitcoin-core/ # Bitcoin Core container
|
||||
│ │ ├── manifest.yml # Archipelago manifest (interface, ports, secrets, health)
|
||||
│ │ ├── Dockerfile # OCI image definition
|
||||
│ │ ├── bitcoin.conf.template # Config template (secrets injected at runtime)
|
||||
│ │ └── start.sh # Container entrypoint
|
||||
│ │
|
||||
│ ├── lightning-stack/ # Lightning Network stack (LND)
|
||||
│ ├── lnd/ # LND daemon
|
||||
│ ├── immich/ # Photo backup app
|
||||
│ ├── nextcloud/ # Cloud storage
|
||||
│ ├── electrumx/ # Bitcoin block explorer index
|
||||
│ ├── router/ # Mesh router app
|
||||
│ ├── pine/ # Voice assistant (whisper + piper + nginx)
|
||||
│ ├── vaultwarden/ # Password manager
|
||||
│ ├── [40+ other apps]
|
||||
│ ├── QUICKSTART.md # App development guide
|
||||
│ ├── PORTS.md # Port allocation reference
|
||||
│ └── build.sh # App build script (all apps)
|
||||
│
|
||||
├── web/ # Built frontend output
|
||||
│ └── dist/
|
||||
│ └── neode-ui/ # `npm run build` output (served by nginx)
|
||||
│ ├── index.html
|
||||
│ ├── [JS bundles]
|
||||
│ └── [static assets]
|
||||
│
|
||||
├── tests/ # Test suite
|
||||
│ ├── lifecycle/
|
||||
│ │ ├── run-gate.sh # Single-node production gate (5 iterations)
|
||||
│ │ └── TESTING.md # Test plan documentation
|
||||
│ ├── e2e/ # End-to-end tests (Playwright)
|
||||
│ └── [other test directories]
|
||||
│
|
||||
├── docs/ # Documentation (user & developer guides)
|
||||
│ ├── PRODUCTION-MASTER-PLAN.md # North star: manifest-driven, registry-based, decentralized
|
||||
│ ├── UNIFIED-TASK-TRACKER.md # Open tasks (fastest-first)
|
||||
│ ├── APP-PACKAGING-MIGRATION-PLAN.md
|
||||
│ ├── registry-manifest-design.md
|
||||
│ ├── multinode-testing-plan.md
|
||||
│ ├── release-workflow.md # OTA + ISO release process
|
||||
│ ├── app-development.md # Guide for app developers
|
||||
│ ├── api-rpc-reference.md # JSON-RPC 2.0 method documentation
|
||||
│ └── [20+ other docs]
|
||||
│
|
||||
├── image-recipe/ # ISO/image build scripts
|
||||
│ ├── build-debian-iso.sh # Builds bootable Debian ISO
|
||||
│ ├── include/ # Root filesystem overlays
|
||||
│ │ └── opt/archipelago/ # Pre-baked config, scripts, systemd units
|
||||
│ └── [other image components]
|
||||
│
|
||||
├── scripts/ # Utility scripts
|
||||
│ ├── deploy-to-target.sh # Sideload binary to test node (Tailscale SSH + rsync)
|
||||
│ ├── resilience/ # Resilience test scripts
|
||||
│ └── [other scripts]
|
||||
│
|
||||
├── demo/ # Demo deployment (pre-configured node)
|
||||
│ ├── demo-deploy.yml # Docker Compose for vps2 demo
|
||||
│ └── [demo-specific scripts]
|
||||
│
|
||||
├── docker/ # Docker/Podman config
|
||||
│ └── [docker-compose fragments, Dockerfiles]
|
||||
│
|
||||
├── .planning/ # Codebase analysis documents (this is you)
|
||||
│ └── codebase/
|
||||
│ ├── ARCHITECTURE.md
|
||||
│ ├── STRUCTURE.md
|
||||
│ ├── CONVENTIONS.md
|
||||
│ ├── TESTING.md
|
||||
│ ├── STACK.md
|
||||
│ ├── INTEGRATIONS.md
|
||||
│ └── CONCERNS.md
|
||||
│
|
||||
├── .claude/ # Claude Code configuration
|
||||
│ └── skills/ # GSD skills (if any project-specific)
|
||||
│
|
||||
├── .github/ # GitHub Actions CI/CD
|
||||
├── .gitea/ # Gitea CI/CD (local Gitea runner)
|
||||
├── .git/ # Git repository
|
||||
├── .gitignore # Git ignore patterns
|
||||
├── CLAUDE.md # Project instructions (commit rules, invariants, testing)
|
||||
├── Cargo.lock # Locked Rust dependency versions
|
||||
├── CHANGELOG.md # Release notes
|
||||
├── CODE_OF_CONDUCT.md
|
||||
├── CONTRIBUTING.md
|
||||
└── README.md # Project overview
|
||||
```
|
||||
|
||||
## Directory Purposes
|
||||
|
||||
**core/**
|
||||
- Purpose: Rust backend source and dependencies
|
||||
- Contains: Main daemon binary, container orchestration, RPC handlers, business logic
|
||||
- Key files: `archipelago/src/main.rs` (entry point), `archipelago/Cargo.toml` (deps)
|
||||
|
||||
**neode-ui/**
|
||||
- Purpose: Vue 3 frontend SPA source
|
||||
- Contains: Views, components, stores, translations, tests, build config
|
||||
- Key files: `src/main.ts` (entry point), `vite.config.ts` (build), `package.json` (deps)
|
||||
|
||||
**apps/**
|
||||
- Purpose: Containerized application definitions
|
||||
- Contains: Manifests (YAML), Dockerfiles, configs for ~50 apps (Bitcoin, LND, Immich, etc.)
|
||||
- Key files: `*/manifest.yml` (app definition), `*/Dockerfile` (image build)
|
||||
|
||||
**web/dist/neode-ui/**
|
||||
- Purpose: Production frontend output (built by `npm run build`)
|
||||
- Contains: Bundled JS, HTML, static assets
|
||||
- Key files: `index.html` (entry), JS chunks (hashed filenames)
|
||||
- Note: Served by nginx at `/` on node
|
||||
|
||||
**tests/**
|
||||
- Purpose: Test suite (unit, integration, E2E)
|
||||
- Contains: Lifecycle gate (production exit criterion), E2E specs, test utilities
|
||||
- Key files: `lifecycle/run-gate.sh` (the production gate), `e2e/*.spec.ts` (user flows)
|
||||
|
||||
**docs/**
|
||||
- Purpose: User and developer documentation
|
||||
- Contains: Architecture, design decisions, release process, task tracking
|
||||
- Key files: `PRODUCTION-MASTER-PLAN.md` (north star), `UNIFIED-TASK-TRACKER.md` (open tasks)
|
||||
|
||||
**image-recipe/**
|
||||
- Purpose: ISO/image build scripts
|
||||
- Contains: Debian ISO recipe, root filesystem overlays, bootloader config
|
||||
- Key files: `build-debian-iso.sh` (main build), `include/opt/archipelago/` (pre-baked system config)
|
||||
|
||||
**scripts/**
|
||||
- Purpose: Utility and deployment scripts
|
||||
- Contains: Sideload/deploy to test nodes, resilience testing, CI glue
|
||||
- Key files: `deploy-to-target.sh` (ship binary to remote node)
|
||||
|
||||
## Key File Locations
|
||||
|
||||
**Entry Points:**
|
||||
- Backend daemon: `core/archipelago/src/main.rs` (spawns HTTP server, tasks, orchestrator)
|
||||
- Frontend app: `neode-ui/src/main.ts` (Vue app, router, WebSocket init)
|
||||
- App manifest: `apps/*/manifest.yml` (Archipelago-specific; controls container, secrets, UI)
|
||||
- Production gate: `tests/lifecycle/run-gate.sh` (exit criterion for releases)
|
||||
|
||||
**Configuration:**
|
||||
- Backend config: `core/archipelago/src/config.rs` (data dir, bind port, dev mode flag)
|
||||
- Frontend config: `neode-ui/vite.config.ts` (build settings, env vars)
|
||||
- App registries: `/var/lib/archipelago/registries.json` (user-configured Gitea mirrors)
|
||||
- Secrets location: `/var/lib/archipelago/secrets/` (encrypted ChaCha20 files)
|
||||
|
||||
**Core Logic:**
|
||||
- Container orchestration: `core/archipelago/src/container/prod_orchestrator.rs` (300KB+ main logic)
|
||||
- RPC dispatch: `core/archipelago/src/api/rpc/mod.rs` (method routing, ~200 RPCs)
|
||||
- State management: `core/archipelago/src/state.rs` (StateManager) + `core/archipelago/src/data_model.rs` (struct def)
|
||||
- Frontend stores: `neode-ui/src/stores/` (Pinia stores, reactive state)
|
||||
|
||||
**Testing:**
|
||||
- Rust unit tests: Inline in `core/` modules (use `#[test]` and `#[tokio::test]`)
|
||||
- Vitest unit tests: `neode-ui/src/**/__tests__/*.test.ts`
|
||||
- E2E tests: `neode-ui/e2e/*.spec.ts` (Playwright)
|
||||
- Integration tests: `tests/` (shell scripts, node command testing)
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
**Files:**
|
||||
- Rust modules: `snake_case.rs` (e.g., `health_monitor.rs`, `crash_recovery.rs`)
|
||||
- Vue components: `PascalCase.vue` (e.g., `AppCard.vue`, `MeshGraph.vue`)
|
||||
- Composables: `use[Name].ts` (e.g., `useAuth.ts`, `useRpc.ts`)
|
||||
- Stores: `[domain]Store.ts` (e.g., `appStore.ts`, `walletStore.ts`)
|
||||
- Tests: `[name].test.ts` or `[name].spec.ts` (e.g., `rpc-client.test.ts`, `app-launch.spec.ts`)
|
||||
- Scripts: lowercase with hyphens (e.g., `deploy-to-target.sh`, `run-gate.sh`)
|
||||
|
||||
**Directories:**
|
||||
- Rust workspace members: lowercase (e.g., `archipelago`, `container`, `security`)
|
||||
- Feature directories: PascalCase or lowercase depending on context
|
||||
- `neode-ui/src/views/` — page components (mostly PascalCase)
|
||||
- `neode-ui/src/composables/` — logic hooks (lowercase files with `use` prefix)
|
||||
- `core/archipelago/src/api/rpc/` — RPC modules by domain (lowercase: `bitcoin.rs`, `mesh.rs`)
|
||||
|
||||
**Functions/Methods:**
|
||||
- Async functions: `async fn method_name() -> Result<T>` (no special suffix)
|
||||
- Event handlers: `on[Event]` in Vue (e.g., `@click="onInstall"` calls `onInstall()`)
|
||||
- Computed properties: `computed(() => ...)` (no special name)
|
||||
- Public RPC methods: `pub async fn [domain]_[action](...)` (e.g., `container_install`, `bitcoin_send`)
|
||||
|
||||
**Variables & Constants:**
|
||||
- Constants: `UPPER_SNAKE_CASE` (e.g., `RECONCILER_DEFAULT_INTERVAL`, `MAX_FILE_SIZE`)
|
||||
- State variables: `camelCase` (e.g., `appList`, `isLoading`)
|
||||
- Type aliases: `PascalCase` (e.g., `AppId`, `MeshPeer`)
|
||||
|
||||
**Exports & Modules:**
|
||||
- Re-exports barrel files: `mod.rs` exporting `pub use child::*;`
|
||||
- Private internals: `mod private;` (not `pub mod`)
|
||||
- Path aliases (neode-ui): `@/` = `src/`, `@components/` = `src/components/`
|
||||
|
||||
## Where to Add New Code
|
||||
|
||||
**New RPC Method (Backend):**
|
||||
1. Determine domain (auth, container, bitcoin, mesh, wallet, etc.)
|
||||
2. Add async fn to `core/archipelago/src/api/rpc/[domain].rs`
|
||||
3. Function signature: `pub async fn [action](handler: &RpcHandler, params: [ParamType]) -> Result<[ResponseType]>`
|
||||
4. Register in `core/archipelago/src/api/rpc/mod.rs` dispatcher (line ~350+, search for `match method_name`)
|
||||
5. Test: Unit test in same file with `#[tokio::test]`, or E2E in `tests/`
|
||||
|
||||
**New Frontend View (Page):**
|
||||
1. Create `neode-ui/src/views/[ViewName].vue` (PascalCase)
|
||||
2. Import Router in `neode-ui/src/router/index.ts`, add route
|
||||
3. Add navigation link in `neode-ui/src/components/Nav.vue` (if public-facing)
|
||||
4. State: Use or create Pinia store in `neode-ui/src/stores/`
|
||||
5. Test: Add E2E test in `neode-ui/e2e/` if user-facing flow
|
||||
|
||||
**New Container App:**
|
||||
1. Create directory `apps/[app-name]/`
|
||||
2. Write `manifest.yml` (copy structure from existing app; define interfaces.main.ui, health check, secrets)
|
||||
3. Write `Dockerfile` (base image, deps, entrypoint)
|
||||
4. Add to `app-catalog/catalog.json` with entry (id, version, url to manifest)
|
||||
5. Test: `archipelago container.install { manifest_url: "..." }` on dev node
|
||||
|
||||
**New Component (Frontend):**
|
||||
1. Create `neode-ui/src/components/[ComponentName].vue`
|
||||
2. If reusable logic, extract to `neode-ui/src/composables/use[Logic].ts`
|
||||
3. If shared state, use Pinia store (don't create component-local state)
|
||||
4. Example: `components/AppCard.vue` displays one app (reused in Apps.vue listing)
|
||||
|
||||
**New Utility Function:**
|
||||
- Backend service logic: Add to `core/archipelago/src/[domain].rs` or new file if it's cross-cutting
|
||||
- Frontend helper: Add to `neode-ui/src/utils/` (e.g., `format.ts`, `validate.ts`)
|
||||
- Example: Lightning address validation → `neode-ui/src/utils/validate.ts:validateLightningAddress()`
|
||||
|
||||
**New Test:**
|
||||
- Rust unit test: Inline in source file (`#[test]` or `#[tokio::test]`)
|
||||
- Frontend unit test: `neode-ui/src/composables/__tests__/use[Logic].test.ts`
|
||||
- E2E test: `neode-ui/e2e/[feature].spec.ts` (Playwright)
|
||||
- Integration test: `tests/[feature].sh` (shell script running node commands)
|
||||
|
||||
## Special Directories
|
||||
|
||||
**core/target/**
|
||||
- Purpose: Rust build artifacts (generated)
|
||||
- Generated: Yes (by `cargo build`)
|
||||
- Committed: No (in .gitignore)
|
||||
|
||||
**neode-ui/node_modules/**
|
||||
- Purpose: npm dependencies
|
||||
- Generated: Yes (by `npm install` or `pnpm install`)
|
||||
- Committed: No (in .gitignore)
|
||||
|
||||
**web/dist/**
|
||||
- Purpose: Built frontend output (generated)
|
||||
- Generated: Yes (by `npm run build`)
|
||||
- Committed: No (in .gitignore) — distributed via OTA/ISO
|
||||
|
||||
**/var/lib/archipelago/** (at runtime on node)
|
||||
- Purpose: Data directory (user data, settings, secrets, backups)
|
||||
- Generated: Yes (created by daemon on first boot)
|
||||
- Committed: No (runtime data; contains user secrets)
|
||||
- Subdirs:
|
||||
- `identity/` — Node Ed25519 keys
|
||||
- `secrets/` — Encrypted secret vaults (ChaCha20)
|
||||
- `apps/` — App manifests (disk copies or registry overlays)
|
||||
- `backups/` — Encrypted backup archives
|
||||
- `registries.json` — User-configured Gitea mirrors
|
||||
- etc.
|
||||
|
||||
**/opt/archipelago/** (at runtime on node)
|
||||
- Purpose: System-level Archipelago files (read-only on ISO, writable post-install)
|
||||
- Contains:
|
||||
- `web-ui/` — Built frontend (nginx root)
|
||||
- `bin/archipelago` — Daemon binary
|
||||
- `docker/` — Docker Compose or Quadlet files (orchestration)
|
||||
- `scripts/` — System maintenance scripts
|
||||
- Note: OTA updates overwrite `web-ui/` + `bin/` atomically
|
||||
|
||||
---
|
||||
|
||||
*Structure analysis: 2026-07-29*
|
||||
449
.planning/codebase/TESTING.md
Normal file
449
.planning/codebase/TESTING.md
Normal file
@ -0,0 +1,449 @@
|
||||
# Testing Patterns
|
||||
|
||||
**Analysis Date:** 2026-07-29
|
||||
|
||||
## Test Framework
|
||||
|
||||
**Frontend:**
|
||||
- Runner: Vitest 3.1.1
|
||||
- Config: `neode-ui/vitest.config.ts`
|
||||
- Environment: jsdom (DOM testing in Node.js)
|
||||
- Globals: enabled (`globals: true`) — `describe`, `it`, `expect` available without imports
|
||||
- Assertion library: built-in Vitest assertions (compatible with Jest)
|
||||
|
||||
**Backend (Rust):**
|
||||
- Framework: built-in `#[test]` attribute and `cargo test`
|
||||
- Command: `cd core && cargo test --workspace --bins`
|
||||
|
||||
**E2E (Browser):**
|
||||
- Framework: Playwright 1.58.2
|
||||
- Config: implicit (tests in `neode-ui/e2e/` directory)
|
||||
|
||||
**Shell Integration Tests:**
|
||||
- Framework: Bats (Bash Automated Testing System)
|
||||
- Location: `tests/lifecycle/bats/`
|
||||
- Config files: `tests/lifecycle/lib/rpc.bash` (RPC wrapper helpers)
|
||||
|
||||
**Run Commands:**
|
||||
```bash
|
||||
# Unit tests (Vitest)
|
||||
npm run test # Run all tests once
|
||||
npm run test:watch # Watch mode, re-run on file changes
|
||||
|
||||
# Rust tests
|
||||
cd core && cargo test --workspace --bins
|
||||
|
||||
# Specific Vitest suite
|
||||
npm run test -- src/composables/__tests__/useFileType.test.ts
|
||||
|
||||
# Shell lifecycle tests (from repo root)
|
||||
ARCHY_PASSWORD=password123 tests/lifecycle/run.sh # Read-only tests
|
||||
ARCHY_PASSWORD=password123 ARCHY_ALLOW_DESTRUCTIVE=1 tests/lifecycle/run.sh # Include destructive
|
||||
|
||||
# Release gate (5× iterations, must run ON the target node)
|
||||
ARCHY_PASSWORD=password123 ARCHY_ALLOW_DESTRUCTIVE=1 ARCHY_ITERATIONS=5 \
|
||||
tests/lifecycle/run-gate.sh
|
||||
```
|
||||
|
||||
## Test File Organization
|
||||
|
||||
**Location:**
|
||||
- Frontend: co-located with source in `__tests__/` subdirectories
|
||||
- Example: `src/composables/useFileType.ts` → `src/composables/__tests__/useFileType.test.ts`
|
||||
- Example: `src/api/rpc-client.ts` → `src/api/__tests__/rpc-client.test.ts`
|
||||
- E2E: separate `e2e/` directory at root of frontend
|
||||
- Shell: `tests/lifecycle/bats/` directory
|
||||
|
||||
**Naming:**
|
||||
- Vitest: `*.test.ts` or `*.spec.ts` suffix (`.test.ts` preferred)
|
||||
- Playwright: `*.spec.ts` suffix
|
||||
- Bats: `*.bats` suffix
|
||||
- Rust unit: same file with `#[test]` functions at the bottom or in submodules
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
neode-ui/
|
||||
├── src/
|
||||
│ ├── composables/
|
||||
│ │ ├── useFileType.ts
|
||||
│ │ └── __tests__/
|
||||
│ │ ├── useFileType.test.ts
|
||||
│ │ ├── useNavSounds.test.ts
|
||||
│ │ └── ... (other composable tests)
|
||||
│ ├── api/
|
||||
│ │ ├── rpc-client.ts
|
||||
│ │ └── __tests__/
|
||||
│ │ └── rpc-client.test.ts
|
||||
│ └── stores/
|
||||
│ ├── controller.ts
|
||||
│ └── (no tests found for stores in exploration)
|
||||
├── e2e/
|
||||
│ ├── app-launch.spec.ts
|
||||
│ ├── intro-experience.spec.ts
|
||||
│ └── visual-regression.spec.ts
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
**Vitest Suite Organization:**
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
import { getFileCategory, useFileType, formatSize, formatDate } from '../useFileType'
|
||||
|
||||
describe('getFileCategory', () => {
|
||||
it('returns folder for directories', () => {
|
||||
expect(getFileCategory('', true)).toBe('folder')
|
||||
expect(getFileCategory('jpg', true)).toBe('folder')
|
||||
})
|
||||
|
||||
it('identifies image extensions', () => {
|
||||
expect(getFileCategory('jpg', false)).toBe('image')
|
||||
expect(getFileCategory('png', false)).toBe('image')
|
||||
})
|
||||
})
|
||||
|
||||
describe('useFileType', () => {
|
||||
it('returns correct category and computed values for an image', () => {
|
||||
const ext = ref('jpg')
|
||||
const isDir = ref(false)
|
||||
const result = useFileType(ext, isDir)
|
||||
|
||||
expect(result.category.value).toBe('image')
|
||||
expect(result.isImage.value).toBe(true)
|
||||
})
|
||||
|
||||
it('reacts to ref changes', () => {
|
||||
const ext = ref('jpg')
|
||||
const isDir = ref(false)
|
||||
const result = useFileType(ext, isDir)
|
||||
|
||||
expect(result.category.value).toBe('image')
|
||||
ext.value = 'mp3'
|
||||
expect(result.category.value).toBe('audio')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Patterns:**
|
||||
- `describe()` blocks group related tests by function or component
|
||||
- `it()` blocks test a single behavior (flat structure, no nesting of describe blocks observed)
|
||||
- `beforeEach()` / `afterEach()` hooks for setup/teardown per test
|
||||
- `beforeAll()` / `afterAll()` hooks for suite-level setup (e.g., login in bats tests)
|
||||
- Assertions use `expect(actual).toBe(expected)` or `expect(actual).toEqual(object)`
|
||||
|
||||
**Playwright E2E Structure:**
|
||||
|
||||
```typescript
|
||||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
async function login(page: Page) {
|
||||
await page.goto('/login', { waitUntil: 'domcontentloaded' })
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('neode_intro_seen', '1')
|
||||
})
|
||||
// ... fill form, submit
|
||||
await page.waitForURL('**/dashboard**', { timeout: 20_000 })
|
||||
}
|
||||
|
||||
test('installed app launch opens reachable app URL', async ({ page, context, baseURL }) => {
|
||||
test.skip(!EXPECTED_URL, 'Set ARCHY_EXPECTED_LAUNCH_URL for launch qualification')
|
||||
|
||||
await login(page)
|
||||
await page.goto('/dashboard/apps', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
const appCard = page.locator('[data-controller-container]', {
|
||||
has: page.getByRole('heading', { name: APP_CARD_TITLE, exact: true }),
|
||||
}).first()
|
||||
|
||||
await appCard.waitFor({ timeout: 30_000 })
|
||||
await expect(appCard.locator('button')).toBeVisible()
|
||||
})
|
||||
```
|
||||
|
||||
**Bats Shell Test Structure:**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bats
|
||||
# tests/lifecycle/bats/bitcoin-knots.bats
|
||||
|
||||
load '../lib/rpc.bash'
|
||||
|
||||
setup_file() {
|
||||
: "${ARCHY_PASSWORD:?Set ARCHY_PASSWORD env var to the UI password}"
|
||||
export ARCHY_FORCE_LOGIN=1
|
||||
rpc_login
|
||||
unset ARCHY_FORCE_LOGIN
|
||||
}
|
||||
|
||||
teardown_file() {
|
||||
rpc_logout_local
|
||||
}
|
||||
|
||||
@test "container-list includes bitcoin-knots" {
|
||||
run rpc_result container-list
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | jq -e '.[] | select(.name == "bitcoin-knots")' >/dev/null
|
||||
}
|
||||
|
||||
@test "container-status returns a valid status object" {
|
||||
run rpc_call container-status '{"app_id":"bitcoin-knots"}'
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
```
|
||||
|
||||
## Mocking
|
||||
|
||||
**Framework (Vitest):** `vi` from Vitest; global stub support
|
||||
|
||||
**Patterns:**
|
||||
|
||||
```typescript
|
||||
const mockFetch = vi.fn()
|
||||
vi.stubGlobal('fetch', mockFetch)
|
||||
|
||||
// Import after stubbing
|
||||
const { rpcClient } = await import('../rpc-client')
|
||||
|
||||
// In tests:
|
||||
mockFetch.mockResolvedValueOnce(jsonResponse({ result: { did: 'did:key:z123' } }))
|
||||
mockFetch.mockRejectedValueOnce(new Error('fetch failed'))
|
||||
|
||||
// Assertions on mock calls:
|
||||
expect(mockFetch).toHaveBeenCalledOnce()
|
||||
const [url, init] = mockFetch.mock.calls[0]!
|
||||
expect(url).toBe('/rpc/v1')
|
||||
expect(init.method).toBe('POST')
|
||||
```
|
||||
|
||||
**Vue Test Utils:**
|
||||
- Component mounting: `mount(Component, { global: { mocks: { $ver: displayVersion } } })`
|
||||
- Props tested by passing to mount options
|
||||
- Events tested by listening to emitted events
|
||||
|
||||
**What to Mock:**
|
||||
- External HTTP requests (fetch, axios)
|
||||
- Timers (for timeout logic; `vi.useFakeTimers()`)
|
||||
- Global objects (localStorage, console, window.location)
|
||||
|
||||
**What NOT to Mock:**
|
||||
- Vue reactivity (ref, computed) — these are core to component behavior
|
||||
- RPC client methods in component tests — prefer integration-style testing
|
||||
- Built-in assertions (expect) — always available
|
||||
- Pinia stores in unit tests of composables that use them — store directly if needed
|
||||
|
||||
## Fixtures and Factories
|
||||
|
||||
**Test Data:**
|
||||
|
||||
```typescript
|
||||
function jsonResponse(body: unknown, status = 200, statusText = 'OK'): Response {
|
||||
return {
|
||||
ok: status >= 200 && status < 300,
|
||||
status,
|
||||
statusText,
|
||||
json: () => Promise.resolve(body),
|
||||
// ... other Response properties
|
||||
}
|
||||
}
|
||||
|
||||
// Usage:
|
||||
mockFetch.mockResolvedValueOnce(jsonResponse({ result: { did: 'did:key:z123' } }))
|
||||
mockFetch.mockResolvedValueOnce(jsonResponse(null, 502, 'Bad Gateway'))
|
||||
```
|
||||
|
||||
**Location:**
|
||||
- Fixtures (test data, helper functions) defined inline in test files or in small helper modules
|
||||
- No central fixture factory observed; each test file is self-contained
|
||||
- Shell test helpers in `tests/lifecycle/lib/rpc.bash` (RPC wrapper for bats)
|
||||
|
||||
## Coverage
|
||||
|
||||
**Requirements:**
|
||||
- Frontend: 80% branch coverage (set in `vitest.config.ts` thresholds)
|
||||
- Rust: no explicit threshold; pragmatic testing of public APIs
|
||||
- Shell: coverage tracked per app in `tests/lifecycle/TESTING.md` (lifecycle matrix)
|
||||
|
||||
**View Coverage:**
|
||||
|
||||
```bash
|
||||
# Generate coverage report
|
||||
npm run test -- --coverage
|
||||
|
||||
# Output formats: text, text-summary, html
|
||||
# Config in vitest.config.ts: reporter: ['text', 'text-summary']
|
||||
```
|
||||
|
||||
**Coverage Scope (Frontend):**
|
||||
- Included: `src/api/*.ts`, `src/stores/*.ts`, `src/composables/*.ts`, `src/utils/*.ts`, `src/services/*.ts`, `src/router/*.ts`
|
||||
- Excluded: test files (`src/**/__tests__/**`), type definitions (`*.d.ts`), entry point (`src/main.ts`)
|
||||
|
||||
## Test Types
|
||||
|
||||
**Unit Tests (Vitest):**
|
||||
- Scope: individual functions, composables, utility modules
|
||||
- Approach: fast, isolated, mock external dependencies
|
||||
- Example: `useFileType.test.ts` tests `getFileCategory`, `useFileType`, `formatSize`, `formatDate` independently
|
||||
- Latency: ~5s for full suite; individual tests <1s
|
||||
|
||||
**Integration Tests (Vitest + RPCClient):**
|
||||
- Scope: RPC client with mocked fetch, authentication flows, retry logic
|
||||
- Approach: more complex setup, test interactions between layers
|
||||
- Example: `rpc-client.test.ts` tests 70+ scenarios (login, TOTP, federation, package operations)
|
||||
- Latency: ~30s for full suite
|
||||
|
||||
**E2E Tests (Playwright):**
|
||||
- Scope: real browser, real app instance, user journeys (login → navigate → interact)
|
||||
- Approach: full app stack running; no mocks of UI layer
|
||||
- Example: `app-launch.spec.ts` tests app card discovery and launch via button click
|
||||
- Latency: 30–120s per test depending on app startup time
|
||||
|
||||
**Lifecycle Tests (Bats):**
|
||||
- Scope: container operations (install, start, stop, restart, uninstall) on a live node
|
||||
- Approach: RPC calls to backend, shell commands for verification, destructive operations tier-gated
|
||||
- Tiers:
|
||||
- L0 unit: Rust unit tests (cargo test)
|
||||
- L1 RPC: JSON-RPC API responses (bats + rpc.bash)
|
||||
- L2 UI: HTTP probe of app URLs (bats + ui-probes.bash)
|
||||
- L3 lifecycle survival: container restart/reboot survival (bats, gated)
|
||||
- Latency: 30–120s per suite depending on tier and container startup
|
||||
|
||||
## Common Patterns
|
||||
|
||||
**Async Testing (Vitest):**
|
||||
|
||||
```typescript
|
||||
it('makes a successful RPC call and returns the result', async () => {
|
||||
mockFetch.mockResolvedValueOnce(jsonResponse({ result: { did: 'did:key:z123' } }))
|
||||
|
||||
const result = await rpcClient.call<{ did: string }>({
|
||||
method: 'node.did',
|
||||
params: {},
|
||||
})
|
||||
|
||||
expect(result).toEqual({ did: 'did:key:z123' })
|
||||
expect(mockFetch).toHaveBeenCalledOnce()
|
||||
})
|
||||
```
|
||||
|
||||
- `async` keyword on test function
|
||||
- `await` for async operations
|
||||
- No explicit promise handling; expect called after `await` completes
|
||||
- Timeouts set via test config or `{ timeout: N }` in individual tests
|
||||
|
||||
**Error Testing (Vitest):**
|
||||
|
||||
```typescript
|
||||
it('throws after max retries on persistent 502', async () => {
|
||||
mockFetch.mockResolvedValue(jsonResponse(null, 502, 'Bad Gateway'))
|
||||
|
||||
await expect(rpcClient.call({ method: 'test' })).rejects.toThrow('HTTP 502: Bad Gateway')
|
||||
expect(mockFetch).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('throws immediately on non-retryable HTTP errors', async () => {
|
||||
mockFetch.mockResolvedValueOnce(jsonResponse(null, 401, 'Unauthorized'))
|
||||
|
||||
await expect(rpcClient.call({ method: 'test' })).rejects.toThrow('Session expired')
|
||||
expect(mockFetch).toHaveBeenCalledOnce()
|
||||
})
|
||||
```
|
||||
|
||||
- `expect(...).rejects.toThrow(message)` for expected rejections
|
||||
- Mock returns set per-call (`mockResolvedValueOnce`, `mockResolvedValue`)
|
||||
- Retry logic verified via call count assertions (`toHaveBeenCalledTimes`)
|
||||
|
||||
**Timer Mocking (Vitest):**
|
||||
|
||||
```typescript
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers({ shouldAdvanceTime: true })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('retries on 502 Bad Gateway and eventually succeeds', async () => {
|
||||
mockFetch
|
||||
.mockResolvedValueOnce(jsonResponse(null, 502, 'Bad Gateway'))
|
||||
.mockResolvedValueOnce(jsonResponse({ result: 'ok' }))
|
||||
|
||||
const result = await rpcClient.call({ method: 'test' })
|
||||
|
||||
expect(result).toBe('ok')
|
||||
expect(mockFetch).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
```
|
||||
|
||||
- Fake timers enable testing of timeout/retry delays without blocking real time
|
||||
- `shouldAdvanceTime: true` auto-advances clock for non-blocking tests
|
||||
- Clean up with `vi.useRealTimers()` after each test
|
||||
|
||||
**Vue Component Testing (Vue Test Utils):**
|
||||
|
||||
```typescript
|
||||
it('returns correct values for audio', () => {
|
||||
const ext = ref('mp3')
|
||||
const isDir = ref(false)
|
||||
const result = useFileType(ext, isDir)
|
||||
|
||||
expect(result.category.value).toBe('audio')
|
||||
expect(result.isAudio.value).toBe(true)
|
||||
expect(result.isImage.value).toBe(false)
|
||||
expect(result.iconColor.value).toBe('text-orange-400')
|
||||
})
|
||||
```
|
||||
|
||||
- Refs created with `ref()` passed as test inputs
|
||||
- Computed values accessed via `.value`
|
||||
- No mount overhead for pure composable logic
|
||||
|
||||
**Playwright Browser Testing:**
|
||||
|
||||
```typescript
|
||||
test('installed app launch opens reachable app URL', async ({ page, context, baseURL }) => {
|
||||
await login(page)
|
||||
await page.goto('/dashboard/apps', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
const appCard = page.locator('[data-controller-container]', {
|
||||
has: page.getByRole('heading', { name: APP_CARD_TITLE, exact: true }),
|
||||
}).first()
|
||||
|
||||
await appCard.waitFor({ timeout: 30_000 })
|
||||
await expect(appCard.locator('button')).toBeVisible()
|
||||
})
|
||||
```
|
||||
|
||||
- Locators used to find elements (CSS selector, role, text)
|
||||
- Wait timeouts on slow networks (30s for app startup)
|
||||
- `waitUntil: 'domcontentloaded'` or `'networkidle'` for page load
|
||||
- Screenshots and video recording available via config
|
||||
|
||||
## Test Configuration Details
|
||||
|
||||
**Vitest Config (`vitest.config.ts`):**
|
||||
- Environment: jsdom
|
||||
- Globals: enabled (no imports needed)
|
||||
- Setup file: `vitest.setup.ts` (mocks global Vue config like `$ver`)
|
||||
- Coverage provider: v8
|
||||
- Coverage threshold: 80% branches
|
||||
- Excluded from coverage: tests, types, main.ts
|
||||
|
||||
**Playwright Config (implicit, environment variables used):**
|
||||
- Base URL: derived from `VITE_*` env vars in dev
|
||||
- Timeouts: per-test overrides via `{ timeout: N }`
|
||||
- Retry: 0 (no automatic retries; explicit in tests via polling)
|
||||
- Config environment variables: `ARCHY_PASSWORD`, `ARCHY_APP_ID`, `ARCHY_EXPECTED_LAUNCH_URL`
|
||||
|
||||
**Shell Test Config (environment variables):**
|
||||
- `ARCHY_PASSWORD`: login password (required)
|
||||
- `ARCHY_ALLOW_DESTRUCTIVE`: enable stop/start/restart/uninstall tests
|
||||
- `ARCHY_ALLOW_CASCADE_DESTRUCTIVE`: enable uninstall/reinstall on throwaway app
|
||||
- `ARCHY_ITERATIONS`: loop count for release gate (5× for production readiness)
|
||||
- `ARCHY_FORCE_LOGIN`: fresh RPC token per test file
|
||||
|
||||
---
|
||||
|
||||
*Testing analysis: 2026-07-29*
|
||||
Loading…
x
Reference in New Issue
Block a user