docs: add MODULE_SPEC.md with permission model and capabilities
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fa4898ca9f
commit
e4bcc3f04a
|
|
@ -0,0 +1,150 @@
|
|||
# rSpace — Collaborative Canvas Platform
|
||||
|
||||
**Module ID:** `rspace`
|
||||
**Domain:** `rspace.online`
|
||||
**Version:** 0.1.0
|
||||
**Framework:** Bun / Hono / Automerge CRDT / Vite
|
||||
**Status:** Active
|
||||
|
||||
## Purpose
|
||||
|
||||
Real-time collaborative canvas where groups can create, arrange, and interact with shapes representing different tools — notes, polls, maps, budgets, itineraries. The foundation layer for the r*.online ecosystem. Subdomain-based routing creates ephemeral or persistent community spaces.
|
||||
|
||||
## Data Model
|
||||
|
||||
### CRDT Document (Automerge)
|
||||
|
||||
```typescript
|
||||
interface CommunityDoc {
|
||||
meta: CommunityMeta;
|
||||
shapes: { [id: string]: ShapeData };
|
||||
}
|
||||
|
||||
interface CommunityMeta {
|
||||
name: string;
|
||||
slug: string;
|
||||
createdAt: string;
|
||||
visibility: SpaceVisibility;
|
||||
ownerDID: string | null;
|
||||
}
|
||||
|
||||
interface ShapeData {
|
||||
type: string; // 'folk-note', 'folk-itinerary', 'demo-poll', etc.
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
rotation: number;
|
||||
content?: string;
|
||||
sourceId?: string; // For arrows/connections
|
||||
targetId?: string;
|
||||
[key: string]: unknown; // Shape-specific properties
|
||||
}
|
||||
```
|
||||
|
||||
### Storage
|
||||
|
||||
- Primary: Automerge binary files at `data/communities/{slug}.automerge`
|
||||
- Fallback: JSON at `data/communities/{slug}.json` (auto-migrated)
|
||||
- In-memory cache with debounced saves (2s)
|
||||
|
||||
## Permission Model
|
||||
|
||||
### Space Integration
|
||||
|
||||
- **SpaceVisibility:** All four levels (public, public_read, authenticated, members_only)
|
||||
- **Default role for open spaces:** PARTICIPANT (everyone can add shapes)
|
||||
|
||||
### Capabilities
|
||||
|
||||
| Capability | Required SpaceRole | AuthLevel | Description |
|
||||
|-----------|-------------------|-----------|-------------|
|
||||
| `view_canvas` | VIEWER | BASIC | See canvas shapes and layout |
|
||||
| `add_shapes` | PARTICIPANT | STANDARD | Create new shapes on canvas |
|
||||
| `edit_own_shapes` | PARTICIPANT | STANDARD | Modify/delete own shapes |
|
||||
| `edit_any_shape` | MODERATOR | STANDARD | Modify/delete any user's shapes |
|
||||
| `delete_any_shape` | MODERATOR | STANDARD | Remove any shape |
|
||||
| `configure_space` | ADMIN | ELEVATED | Change visibility, name, settings |
|
||||
|
||||
### Module-Specific Overrides
|
||||
|
||||
Currently uses a boolean `readOnly` flag on WebSocket connections:
|
||||
- `readOnly=true` if visibility is `public_read` and user is unauthenticated
|
||||
- No per-shape ownership tracking yet (all authenticated users can edit all shapes)
|
||||
|
||||
### Current Auth Implementation
|
||||
|
||||
- EncryptID token verification via `verifyEncryptIDToken()`
|
||||
- `evaluateSpaceAccess()` at WebSocket upgrade and HTTP routes
|
||||
- `extractToken()` from Authorization header
|
||||
- `authenticateWSUpgrade()` for WebSocket connections
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### HTTP Routes
|
||||
|
||||
| Method | Path | Auth Required | Capability | Description |
|
||||
|--------|------|---------------|------------|-------------|
|
||||
| POST | /api/communities | Yes | configure_space | Create community |
|
||||
| GET | /api/communities/:slug | Depends | view_canvas | Get community metadata |
|
||||
| GET | /api/communities/:slug/shapes | Depends | view_canvas | Export shapes as JSON |
|
||||
| POST | /api/communities/:slug/shapes | Yes | add_shapes | Add shapes via API |
|
||||
| POST | /api/communities/demo/reset | — | — | Reset demo data (rate-limited) |
|
||||
|
||||
### WebSocket Protocol
|
||||
|
||||
| Message Type | Direction | Purpose |
|
||||
|-------------|-----------|---------|
|
||||
| `sync` | Bidirectional | Automerge binary sync messages |
|
||||
| `snapshot` | Server→Client | Full JSON state (JSON mode clients) |
|
||||
| `presence` | Bidirectional | Cursor/user presence updates |
|
||||
| `ping`/`pong` | Bidirectional | Keep-alive heartbeat |
|
||||
| `update` | Client→Server | Legacy shape update (JSON mode) |
|
||||
| `delete` | Client→Server | Legacy shape deletion (JSON mode) |
|
||||
| `error` | Server→Client | Auth/permission errors |
|
||||
|
||||
**Dual sync modes:** Automerge (binary) for rich clients, JSON snapshot for simple clients.
|
||||
|
||||
## Canvas Integration
|
||||
|
||||
rSpace IS the canvas. Other modules embed as shapes:
|
||||
|
||||
| Shape Type | Module | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `demo-poll` | rVote | Embedded voting widget |
|
||||
| `folk-note` | rNotes | Editable note card |
|
||||
| `folk-itinerary` | rTrips | Trip timeline |
|
||||
| `folk-destination` | rMaps | Map location marker |
|
||||
| `folk-budget` | rFunds | Budget tracker |
|
||||
| `demo-expense` | rFunds | Expense with split tracking |
|
||||
| `demo-cart-item` | rCart | Shopping item |
|
||||
| `folk-token-mint` | rWallet | Token issuance |
|
||||
| `folk-token-ledger` | rWallet | Token distribution |
|
||||
| `folk-packing-list` | rTrips | Checklist |
|
||||
| `folk-notebook` | rNotes | Notebook container |
|
||||
| `folk-arrow` | — | Visual connection between shapes |
|
||||
|
||||
## Cross-Module Dependencies
|
||||
|
||||
| Module | Integration |
|
||||
|--------|------------|
|
||||
| **All modules** | Shapes from every module render on the rSpace canvas |
|
||||
| **EncryptID** | Identity, authentication, DID-based ownership |
|
||||
| **rNetwork** | Community membership graph |
|
||||
|
||||
## Local-First / Offline Support
|
||||
|
||||
- **CRDT strategy:** Automerge documents. Full offline editing supported.
|
||||
- **Conflict resolution:** Automerge's built-in LWW semantics. No manual conflict resolution needed.
|
||||
- **Sync protocol:** Custom WebSocket relay. Clients exchange Automerge sync messages via the server. Server maintains per-peer sync state.
|
||||
- **Offline → online:** On reconnect, client sends accumulated changes. Server merges and broadcasts.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Add `members` map to `CommunityMeta` for inline CRDT membership
|
||||
2. Replace boolean `readOnly` with `SpaceRole` in WebSocket connection data
|
||||
3. Use `resolveSpaceRole()` at WS upgrade to determine role
|
||||
4. Add `hasCapability()` checks in WS message handler before accepting writes
|
||||
5. Add per-shape `createdBy` DID tracking for own-vs-any permission distinction
|
||||
6. Add `encryptIDSpaceRoleAuth()` Hono middleware to HTTP API routes
|
||||
Loading…
Reference in New Issue