Initial Commit
This commit is contained in:
100
openspec/archive/2026-05-05-network-plugin-session/proposal.md
Normal file
100
openspec/archive/2026-05-05-network-plugin-session/proposal.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# OpenSpec Change: network-plugin-session
|
||||
|
||||
- **Change ID**: network-plugin-session
|
||||
- **Package**: `@suaveplan/network-plugin-session` (new)
|
||||
- **Status**: PROPOSED
|
||||
- **Type**: new-package
|
||||
- **Date**: 2026-04-29
|
||||
|
||||
## Why
|
||||
|
||||
The tiered mesh network provides peer-to-peer connectivity, but it has no concept of **logical player groupings** — sessions. A session is a scope within which peers see each other, exchange state, and interact. Sessions can be nested: a parent session (free-roam open world) contains child sessions (missions, heists, races), which may contain further nested sessions (mission phases, instanced interiors). This is the pattern used by GTA V Online, Destiny, The Division, and many other multiplayer games.
|
||||
|
||||
Without session management, every peer in the mesh receives every message from every other peer. Sessions provide:
|
||||
|
||||
- **Visibility isolation** — peers only receive state from peers in their session branch
|
||||
- **Nested hierarchies** — missions inside free-roam, phases inside missions
|
||||
- **Session-scoped host nomination** — each session elects its own host for state authority
|
||||
- **Capacity enforcement** — hard caps per session type (free-roam: 30, mission: 4, race: 8)
|
||||
- **Graceful transitions** — joining/leaving sessions without dropping mesh connection
|
||||
- **Parent state inheritance** — sub-sessions can optionally receive read-only state from parent (weather, time, world events)
|
||||
|
||||
## What changes
|
||||
|
||||
- **ADDED**: `SessionPlugin` class implementing `NetworkPlugin`.
|
||||
- **ADDED**: `SessionManager` — maintains the session tree, handles create/destroy/join/leave/move operations.
|
||||
- **ADDED**: `Session` — represents one session node in the tree: `id`, `parentId`, `hostId`, `peers[]`, `capacity`, `rules`, `state`.
|
||||
- **ADDED**: `SessionRouter` — intercepts outgoing messages and filters delivery to peers in the target session scope only.
|
||||
- **ADDED**: `SessionHostElection` — per-session host nomination, independent of mesh Tier 0 host. A peer can be host of multiple sessions.
|
||||
- **ADDED**: `SessionTransitionManager` — handles join/leave/move with state snapshot transfer and forwarding tunnel.
|
||||
- **ADDED**: `ParentStateBridge` — optionally replicates read-only parent state into child sessions (e.g., world time, weather).
|
||||
- **ADDED**: `SessionCapacityEnforcer` — rejects joins when capacity exceeded; can trigger session split.
|
||||
- **ADDED**: `SessionRegistry` — tracks all known sessions in the mesh, their hosts, and their peer counts.
|
||||
- **ADDED**: `NETWORK_SESSION_CODES` — frozen error registry.
|
||||
|
||||
## Session hierarchy model
|
||||
|
||||
```text
|
||||
Mesh (all connected peers)
|
||||
└── Session: "freeroam-01" (capacity: 30, host: Peer-A)
|
||||
├── Peer-A (host)
|
||||
├── Peer-B
|
||||
├── Peer-C
|
||||
├── Peer-D
|
||||
└── Session: "mission-heist-01" (capacity: 4, host: Peer-B)
|
||||
├── Peer-B (host)
|
||||
├── Peer-C
|
||||
├── Peer-D
|
||||
└── Session: "phase-drill" (capacity: 2, host: Peer-C)
|
||||
├── Peer-C (host)
|
||||
└── Peer-D
|
||||
```
|
||||
|
||||
**Rules**:
|
||||
|
||||
- A peer can be in **exactly one leaf session** at a time.
|
||||
- A peer implicitly belongs to all ancestor sessions (for parent state bridge).
|
||||
- Messages sent within a session are delivered **only to peers in that session or its descendants**.
|
||||
- Messages sent "upward" (to parent) are delivered to the parent session host only.
|
||||
- Each session has its own host, elected independently via the same governance scoring.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Game-specific state replication (entities, physics, AI) → gaming packages (`gaming-ecs`, `gaming-r3f-networking`).
|
||||
- Matchmaking (finding which session to join) → `gaming-lobby`, `gaming-matchmaking`.
|
||||
- Party system (persistent friend group) → `gaming-party`.
|
||||
- Persistence (saving session state to disk) → future `network-plugin-session-persistence`.
|
||||
|
||||
## Source mapping
|
||||
|
||||
This change derives from the session concepts in `../suaveplan/enterprise/game-server.md` (§sessions) and `../suaveplan/enterprise/lobby.md`, but is a **new design** — suaveplan has no equivalent network-layer session plugin.
|
||||
|
||||
| Requirement | Source lines | Notes |
|
||||
|---|---|---|
|
||||
| Session management | `game-server/README.md` §Sessions | reworded; suaveplan had session stubs in game-server, not network layer |
|
||||
| Session host | `game-server/README.md` §Sessions | reworded; host per session, not per mesh |
|
||||
| Capacity limits | `lobby/README.md` §Capacity | reworded; per-session, not per-lobby |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| Session-scoped message routing | Defensive — prevents free-roam state flooding mission peers |
|
||||
| Nested session hierarchies | Required for GTA V-style missions-within-free-roam |
|
||||
| Parent state bridge | Required so mission players still see world time/weather |
|
||||
| Per-session host election | Required so mission host can be closest to mission peers, not free-roam host |
|
||||
| Session transition with snapshot | Required for seamless join/leave without mesh disconnect |
|
||||
|
||||
## Verification approach
|
||||
|
||||
- `src/session/session.spec-conformance.test.ts` — pins create, destroy, join, leave, move.
|
||||
- `src/router/router.spec-conformance.test.ts` — pins message delivery scope, upward routing, parent bridge.
|
||||
- `src/election/election.spec-conformance.test.ts` — pins per-session host election, independent of mesh host.
|
||||
- `src/transition/transition.spec-conformance.test.ts` — pins join/leave/move with state snapshot.
|
||||
- `src/capacity/capacity.spec-conformance.test.ts` — pins capacity enforcement, rejection, split trigger.
|
||||
- `src/registry/session-registry.spec-conformance.test.ts` — pins session tree tracking, peer counts.
|
||||
- `tests/dep-graph.test.ts` — pins no direct import of webrtc or gaming packages.
|
||||
|
||||
## Migration impact
|
||||
|
||||
None — additive only.
|
||||
115
openspec/archive/2026-05-05-network-plugin-session/tasks.md
Normal file
115
openspec/archive/2026-05-05-network-plugin-session/tasks.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Tasks — network-plugin-session
|
||||
|
||||
> Blocked on: `network-core`, `network-plugin-topology`.
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] 1.1 `packages/network/network-plugin-session/` with `package.json` (`@suaveplan/network-plugin-session@0.0.0`), `tsconfig.json`, `vitest.config.ts`, `README.md`.
|
||||
- [x] 1.2 Workspace deps: `@suaveplan/network`, `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/fsm`, `@suaveplan/governance` (or inline election), `@suaveplan/timing`, `@opentelemetry/api`, `zod`.
|
||||
- [x] 1.3 `sideEffects: false`.
|
||||
|
||||
## 2. Types
|
||||
|
||||
- [x] 2.1 `src/types/session.types.ts` — `Session`, `SessionId`, `SessionConfig`, `SessionRules`, `SessionState`.
|
||||
- [x] 2.2 `src/types/tree.types.ts` — `SessionTree`, `SessionNode`, `SessionPath`.
|
||||
- [x] 2.3 `src/types/message.types.ts` — `SessionMessage`, `SessionScope`, `SessionEnvelope`.
|
||||
- [x] 2.4 `src/types/transition.types.ts` — `SessionTransition`, `TransitionType`, `SessionSnapshot`.
|
||||
- [x] 2.5 `src/error-codes.ts` — `defineCodes("NET_SESSION", [...])`.
|
||||
|
||||
## 3. Session tree
|
||||
|
||||
- [x] 3.1 `src/tree/session-tree.ts` — `SessionTree` maintains parent/child relationships.
|
||||
- [x] 3.2 Root session is created automatically when plugin starts.
|
||||
- [x] 3.3 `createSession(parentId, config)`, `destroySession(sessionId)`.
|
||||
- [x] 3.4 `getSessionPath(sessionId)` returns ancestor chain.
|
||||
- [x] 3.5 Prevents cycles.
|
||||
|
||||
## 4. Session manager
|
||||
|
||||
- [x] 4.1 `src/session/session-manager.ts` — `SessionManager`.
|
||||
- [x] 4.2 `join(sessionId, peerId)` — adds peer to session, removes from previous leaf.
|
||||
- [x] 4.3 `leave(sessionId, peerId)` — removes peer, moves to parent session.
|
||||
- [x] 4.4 `move(peerId, fromSessionId, toSessionId)` — atomic leave + join with snapshot.
|
||||
- [x] 4.5 Emits `session:joined`, `session:left`, `session:moved` on `NetworkBus`.
|
||||
|
||||
## 5. Session router
|
||||
|
||||
- [x] 5.1 `src/router/session-router.ts` — `SessionRouter`.
|
||||
- [x] 5.2 Intercepts `send(targetId, envelope)` from `NetworkNode`.
|
||||
- [x] 5.3 If envelope has `sessionId` header, delivers only to peers in that session or its descendants.
|
||||
- [x] 5.4 If envelope has `scope: 'parent'`, delivers only to parent session host.
|
||||
- [x] 5.5 If envelope has `scope: 'broadcast'`, delivers to all peers in session branch.
|
||||
- [x] 5.6 If no session scope, defaults to peer's current leaf session.
|
||||
|
||||
## 6. Session host election
|
||||
|
||||
- [x] 6.1 `src/election/session-host-election.ts` — `SessionHostElection`.
|
||||
- [x] 6.2 Uses same scoring as governance (latency, uptime, reputation) but scoped to session peers.
|
||||
- [x] 6.3 Election triggered on host leave or periodic health check.
|
||||
- [x] 6.4 Emits `session:host-changed` on bus.
|
||||
|
||||
## 7. Session transition manager
|
||||
|
||||
- [x] 7.1 `src/transition/session-transition-manager.ts` — `SessionTransitionManager`.
|
||||
- [x] 7.2 On `join`: requests state snapshot from session host, applies it locally.
|
||||
- [x] 7.3 On `leave`: emits `session:peer-left` so host can clean up peer state.
|
||||
- [x] 7.4 On `move`: creates forwarding tunnel from old host to new host for in-flight messages.
|
||||
- [x] 7.5 Uses `@suaveplan/fsm` for transition states: `idle → requesting → syncing → active → leaving → complete`.
|
||||
|
||||
## 8. Parent state bridge
|
||||
|
||||
- [x] 8.1 `src/bridge/parent-state-bridge.ts` — `ParentStateBridge`.
|
||||
- [x] 8.2 Configurable read-only keys replicated from parent to child sessions.
|
||||
- [x] 8.3 Examples: `worldTime`, `weather`, `globalEvent`.
|
||||
- [x] 8.4 Child session peers receive parent state updates but cannot send to parent.
|
||||
|
||||
## 9. Session capacity enforcer
|
||||
|
||||
- [x] 9.1 `src/capacity/session-capacity-enforcer.ts` — `SessionCapacityEnforcer`.
|
||||
- [x] 9.2 Rejects `join` when `peers.length >= capacity`.
|
||||
- [x] 9.3 Emits `session:capacity-exceeded` on bus.
|
||||
- [x] 9.4 Optional: triggers `session:split-requested` when capacity exceeded and parent allows splitting.
|
||||
|
||||
## 10. Session registry
|
||||
|
||||
- [x] 10.1 `src/registry/session-registry.ts` — `SessionRegistry`.
|
||||
- [x] 10.2 Tracks all sessions: `id`, `hostId`, `peerCount`, `capacity`, `depth`.
|
||||
- [x] 10.3 Syncs to `NetworkBus` for mesh-wide discovery.
|
||||
- [x] 10.4 Consumed by assignment engine for placing new peers.
|
||||
|
||||
## 11. Plugin wrapper
|
||||
|
||||
- [x] 11.1 `src/plugin/session-plugin.ts` — `SessionPlugin` implements `NetworkPlugin`.
|
||||
- [x] 11.2 Installs session manager, router, election, transition, bridge, capacity, registry on `install()`.
|
||||
- [x] 11.3 Intercepts `NetworkNode.send()` to inject session routing.
|
||||
|
||||
## 12. Spec
|
||||
|
||||
- [x] 12.1 Update `openspec/specs/network-session/spec.md` with `NET-SESS-01` through `NET-SESS-12`.
|
||||
|
||||
## 13. Conformance tests
|
||||
|
||||
- [x] 13.1 `src/session/session.spec-conformance.test.ts` — create, destroy, join, leave, move.
|
||||
- [x] 13.2 `src/router/router.spec-conformance.test.ts` — scope filtering, upward routing, broadcast.
|
||||
- [x] 13.3 `src/election/election.spec-conformance.test.ts` — per-session host, independence from mesh host.
|
||||
- [x] 13.4 `src/transition/transition.spec-conformance.test.ts` — snapshot transfer, forwarding tunnel.
|
||||
- [x] 13.5 `src/capacity/capacity.spec-conformance.test.ts` — enforcement, rejection, split.
|
||||
- [x] 13.6 `src/bridge/bridge.spec-conformance.test.ts` — parent state replication, read-only.
|
||||
- [x] 13.7 `src/registry/session-registry.spec-conformance.test.ts` — tree tracking, peer counts.
|
||||
- [x] 13.8 `src/error-codes.spec-conformance.test.ts` — frozen registry.
|
||||
|
||||
## 14. Verify
|
||||
|
||||
- [x] 14.1 `bunx turbo lint typecheck test --filter=@suaveplan/network-plugin-session` green.
|
||||
- [x] 14.2 `bunx turbo test:coverage --filter=@suaveplan/network-plugin-session` 100/100/100/100.
|
||||
- [x] 14.3 `bun scripts/openspec-conformance.ts @suaveplan/network-plugin-session` PASS.
|
||||
|
||||
---
|
||||
|
||||
## Audit Note (2026-05-05)
|
||||
|
||||
**Status**: SHIPPED — every section delivered at full quality (100/100/100/100 coverage, 100% docs, openspec-conformance PASS, 90 tests across 11 files). One scope adjustment from the original spec:
|
||||
|
||||
- The `CYCLE_DETECTED` error code is omitted because the public `createSession` API rejects duplicate ids (`SESSION_ALREADY_EXISTS`) and missing parents (`PARENT_NOT_FOUND`) before any cycle-forming assignment can land. The remaining six `NET_SESSION` codes cover every reachable failure path.
|
||||
|
||||
The shipped surface (`SessionTree`, `SessionManager`, `SessionRouter`, `SessionHostElection`, `SessionTransitionManager`, `ParentStateBridge`, `SessionCapacityEnforcer`, `SessionRegistry`, `SessionPlugin`) covers `NET-SESS-01` through `NET-SESS-12` from the network-session spec.
|
||||
Reference in New Issue
Block a user