# `@ifc-lite/collab` — Implementation Plan & Task Tracker

**Spec:** `Spec: @ifc-lite/collab — Real-time collaborative BIM via CRDT on IFCX` (Draft v0.1, target ifc-lite 3.0).
**Branch:** `claude/plan-task-tracking-XyeJi`.
**Owner:** Louis Trümpler (LT+).
**Status legend:** ☐ pending · ◐ in progress · ☑ done · ⚠ blocked.

This document is the source of truth for tracking the rollout of CRDT-backed
collaboration on top of IFCX. Each phase maps 1:1 to the roadmap in §19 of the
spec. Each task has a concrete artifact (file path, package, or interface) so
progress is unambiguous. Cross-cutting concerns (testing, performance budget,
open problems) are listed once at the end and referenced from each phase.

---

## 0. Working agreements

- **Branch hygiene:** every phase ships behind a feature flag (`collab.enabled`)
  and lands on `main` only after its exit criteria are green.
- **Two new packages** are created up front so PRs from every phase land in
  stable locations:
  - `packages/collab/` — client runtime (Y.Doc, providers, snapshot, awareness)
  - `packages/collab-server/` — reference sync server (`y-websocket` compatible)
- **Conventions to honor (from `AGENTS.md`):**
  - Strict IFC nomenclature in user-facing APIs (`IfcRelAggregates`, not
    `Aggregates`); Pset / property names in PascalCase.
  - Federation-aware IDs: never bypass `FederationRegistry` for cross-model
    lookups.
  - License headers on every new `.ts` / `.rs` source file.
  - Changesets for any change that touches a published package.
  - File size cap ~400 LOC; no `as any`, no bare `catch {}`.
  - Tests required for every new package and feature.
- **Dependencies (added once, in the right package — never the root):**
  - `packages/collab`: `yjs`, `y-protocols`, `y-indexeddb`, `y-websocket`,
    `y-webrtc` (optional), `lib0`.
  - `packages/collab` (history sidecar, v0.7 only): `@automerge/automerge`.
  - `packages/collab-server`: `ws`, `y-websocket` (server bits), `jsonwebtoken`,
    plus a persistence driver chosen at v0.5 time (`@aws-sdk/client-s3` or
    `ioredis`).

---

## 1. Phase v0.1 — Foundation (4 weeks)

> **Status: ☑ Landed.** Both `packages/collab` and `packages/collab-server`
> ship in this PR. `pnpm --filter @ifc-lite/collab test` and
> `pnpm --filter @ifc-lite/collab-server test` pass 19 tests including a
> round-trip against the buildingSMART hello-wall fixture, two-peer
> convergence with the conflict detector firing on both peers, end-to-end
> sync through the websocket server, undo isolation, and per-user layer
> extraction. The `Web Worker snapshot` and `Bim.fromFile binding` items
> are deferred to follow-on PRs (the worker is an apps-layer concern; the
> SDK binding is a v0.2 task once the existing mutation layer migrates).

**Goal:** A single peer can open an `.ifcx` file, mutate it through a Y.Doc
that mirrors the spec's data model, persist edits to IndexedDB, and snapshot
the Y.Doc back out to a valid `.ifcx`. No network, no presence, no conflict
resolution UX yet.

**Spec sections:** §5 data model, §6 operations mapping, §6.1 tombstones &
undo, §12.1 local persistence, §12.3 IFCX snapshots (single-peer slice),
§16.1 package structure, §17 migration path.

### 1.1 Package scaffolding

- ☑ Create `packages/collab/` with `package.json`, `tsconfig.json` extending
  `tsconfig.packages.json`, MPL license header, README, CHANGELOG.
- ☐ Add `packages/collab` and `packages/collab-server` to
  `pnpm-workspace.yaml` (already covered by `packages/*` glob — verify).
- ☐ Add a changeset describing the new `@ifc-lite/collab` package at minor
  version `0.1.0`.
- ☐ Wire `packages/collab` into `turbo.json` build pipeline.

### 1.2 Y.Doc schema (§5)

Implement in `packages/collab/src/doc/schema.ts`:

- ☐ `createCollabDoc()` returns a `Y.Doc` with three top-level shared types:
  `entities` (`Y.Map<EntityDoc>`), `relationships` (`Y.Map<RelationshipDoc>`),
  `geometry` (`Y.Map<GeometryRef>`).
- ☐ Type definitions for `EntityDoc`, `RelationshipDoc`, `GeometryRef`,
  `PropertyValue` (mirror §5.1–§5.3 verbatim, including
  `attributes: Y.Map<Y.Map<unknown>>` keyed by namespace).
- ☐ `assertSchemaInvariants(doc)` — runtime check that the three top-level
  maps exist and have the expected key shapes. Used in tests and in
  `from-ifcx` seeding.
- ☐ Property-shaped helpers (`packages/collab/src/doc/entity.ts`,
  `relationship.ts`, `geometry.ts`) implementing the operations table in §6:
  create, delete, set attribute, set Pset property, classification add,
  material assign, hierarchy move, geometry replace, type promotion
  (delete + create with `meta.previousPath`).
- ☐ Every user-facing edit wraps in `ydoc.transact(() => …, origin)` for
  atomicity and undo grouping.

### 1.3 IFCX seeding & snapshotting (§12.3, §17)

In `packages/collab/src/snapshot/`:

- ☐ `from-ifcx.ts`: parse `.ifcx` (use `@ifc-lite/ifcx` reader), build a Y.Doc
  whose state matches the composed IFCX. Idempotent: seeding twice with the
  same file produces structurally equal Y states.
- ☐ `to-ifcx.ts`: traverse Y.Doc → produce IFCX `data[]` records. Round-trip
  test: `seed → snapshot → seed` must converge.
- ☐ `layers.ts`: extract a per-user layer file using Yjs's update history
  filtered by `clientID`. Output a valid `.ifcx` whose composition with the
  base produces the same state as the live Y.Doc.
- ☐ Snapshotting runs in a Web Worker (`apps/*` consumer wires the worker;
  package exports a worker-compatible entry point).

### 1.4 Local persistence (§12.1)

- ☐ `packages/collab/src/providers/indexeddb.ts`: thin wrapper over
  `y-indexeddb` keyed by `roomId`. Always-on for any session.
- ☐ Cold load <3s for 100k entities (perf budget §15) — measured by a
  benchmark in `packages/collab/test/perf.bench.ts`.

### 1.5 Undo / tombstones (§6.1)

- ☐ `packages/collab/src/undo.ts` exports `createUndoManager(doc, origin)`
  built on `Y.UndoManager`, scoped to the local origin.
- ☐ "Deleted entities" view: read tombstones from Y state and expose a
  recovery API (`session.restoreDeleted(path)`).
- ☐ GC at snapshot boundary: when `to-ifcx` runs, tombstones older than the
  snapshot timestamp are purged.

### 1.6 Public API surface (§16.2)

- ☐ `packages/collab/src/index.ts` exports `createCollabSession(opts)` with
  the v0.1 subset: no network, just `bind(bim)`, `snapshot()`, `dispose()`.
- ☐ Bind into `@ifc-lite/sdk`: `Bim.fromFile('model.ifcx', { collab })` —
  add a `collab?: CollabSession` option to the existing `Bim.fromFile`.
- ☐ Mutations from `@ifc-lite/mutations` route through the Y.Doc when a
  session is bound; otherwise behave as today (no regression).

### 1.7 Tests (v0.1 exit criteria)

- ☐ Unit tests for every helper in `doc/`, `snapshot/`, `undo.ts`.
- ☐ Round-trip property test: random IFCX → seed → snapshot → IFCX, must be
  semantically equal (entity set, relationships, attributes).
- ☐ Convergence smoke test (single-peer): apply a recorded edit trace from a
  fixture, snapshot, re-seed, verify identical Y state.
- ☐ Perf benchmark: cold load + first edit on 100k-entity fixture under
  budget.

**Exit criteria:** open `model.ifcx`, edit through `bim.mutate.*`, reload the
tab, see edits restored, export `.ifcx`, diff against expected fixture.

---

## 2. Phase v0.2 — Multi-peer (4 weeks)

> **Status: ◐ Mostly landed; renderer math factored out.** Websocket
> provider, awareness/presence, sync server, in-memory + file
> persistence, JWT auth hook, healthcheck, two-client convergence
> test, and the latency-simulation harness are all in.
> **`peerVisuals(peers, opts)`** + **`cursorScreenPosition`** now ship as
> renderer-agnostic helpers: pure math that turns raw presence into
> `{ color, label, opacity, isStale, cursor3d, cursor2d, selection }`.
> Any rendering target (Three.js, WebGPU, 2D canvas) wires the result
> into its scene. Actually drawing the avatars in `packages/viewer`
> remains a viewer-package task.

**Goal:** Two browsers in the same room see each other's edits and cursors in
real time over a websocket.

**Spec sections:** §7 awareness protocol, §8 sync protocol, §8.1 providers
(websocket only at this phase), §15 perf budget for sync.

### 2.1 Sync server

- ☐ Scaffold `packages/collab-server/` with the layout in §16.1:
  `server.ts`, `room-manager.ts`, `persistence.ts`, `auth.ts`.
- ☐ Implement a `y-websocket`-compatible server in `server.ts` using `ws`.
  Per-room Y.Doc held in memory; updates fanned out to all peers in the room.
- ☐ Persistence (v0.2 minimal): append-only file log per room on disk.
  Compaction policy comes in v0.5.
- ☐ Healthcheck endpoint `/healthz` and a single-instance dev script
  (`pnpm --filter @ifc-lite/collab-server dev`).

### 2.2 Client provider

- ☐ `packages/collab/src/providers/websocket.ts` — wraps `y-websocket` with
  reconnect/backoff and a `status` observable (`connected | connecting |
  offline | error`).
- ☐ `packages/collab/src/sync/room.ts` — joins/leaves rooms, exposes
  `session.status`.
- ☐ Extend `createCollabSession({ provider: 'websocket', serverUrl, … })`.

### 2.3 Awareness (§7)

- ☐ `packages/collab/src/awareness/protocol.ts` — wraps
  `y-protocols/awareness` on the same transport as the Y.Doc updates, on a
  separate channel.
- ☐ `packages/collab/src/awareness/presence.ts` — typed `PresenceState` per
  §5.4 (`cursor3d`, `cursor2d`, `selection`, `camera`, `activeView`,
  `activeSection`, `isolated`, `tool`, `status`).
- ☐ Stable per-user color hash from user ID.
- ☐ Stale presence eviction within 10s of disconnect.
- ☐ Update rate cap at 30 Hz with delta-only payloads.

### 2.4 Viewer integration (cursors, selections)

- ☐ `packages/viewer` consumes `session.presence` and renders:
  - 3D cursors as floating arrow + name tag at world-space hit-point under
    each peer's screen cursor.
  - Selection outlines per peer in their assigned color (re-uses existing
    selection shader; new uniform for peer color).
- ☐ Settings toggle to hide other users' cursors / selections.

### 2.5 Tests

- ☐ Two-peer integration test in `packages/collab/test/multi-peer.test.ts`
  using two Y.Docs over an in-process server: edits on A appear on B within
  budget; awareness updates flow.
- ☐ Disconnect/reconnect test: A goes offline, edits, comes back, B converges.
- ☐ Network-latency simulation for §15 sync targets (LAN <50ms,
  intercontinental <300ms with simulated 200ms RTT).

**Exit criteria:** demo two browsers in different windows editing the same
wall's `Name` and seeing each other's cursors. This is the LinkedIn-demo
milestone called out in §19.

---

## 3. Phase v0.3 — Geometry (6 weeks)

> **Status: ◐ Foundations + GC landed.** Content-addressed blob store
> (`MemoryBlobStore`, `IndexedDbBlobStore`, `HttpBlobStore`,
> `LayeredBlobStore`) and the matching server route
> (`PUT/GET/HEAD/DELETE /blobs/<hash>` + `GET /blobs`) are in. CSG-tree
> CRDT (`Y.Array<CSGOp>` with append/insert/remove/move) is in.
> Conflict UI bridge (`createConflictUIBridge`) emits open/update/close
> bucket lifecycle events. **Blob GC** (open problem #6) now ships:
> `collectReferencedBlobHashes(doc)`, `planBlobSweep(store, ref, opts)`,
> `sweepBlobs(store, decision)` — epoch + reference-count sweep over
> any `BlobStore` implementing the optional `stat()` method. Still
> pending: parametric → mesh kernel hookup, viewer-side conflict badge,
> and the determinism CI matrix.

**Goal:** Geometric edits (parametric and mesh) sync correctly without
inflating Y.Doc memory or requiring central authority.

**Spec sections:** §5.3 geometry refs, §9.4 geometry conflicts, §11 deep
dive, §15 perf (mesh data must NOT be in Y.Doc).

### 3.1 Blob store

- ☐ `packages/collab/src/geometry/blob-store.ts` — content-addressed blob
  store with two backends:
  - Local: IndexedDB object store keyed by hash.
  - Remote: HTTP `PUT/GET /blobs/:hash` against `collab-server`.
- ☐ Server route `packages/collab-server/src/blobs.ts` with size limits and
  per-room quota.
- ☐ Reference-counting GC sketch (full GC scheme deferred per open
  problem #6).

### 3.2 Parametric geometry as CRDT (§11.2)

- ☐ Extend `GeometryRef` to carry a `params: Y.Map<unknown>` for parametric
  primitives (`extruded-area-solid`, `swept-disk-solid`, `revolved-area-solid`,
  primitives).
- ☐ Bridge from Y.Map params → `ifc-lite-geometry` Rust kernel: on param
  change, recompute the mesh locally; emit content-hash; update
  `GeometryRef.geomId`.
- ☐ Validate determinism of the Rust kernel (open problem #5): bit-identical
  output across Linux/macOS/Windows for the same params. Add a CI matrix
  test.
- ☐ Fallback path: if the kernel reports non-deterministic output (e.g.
  `csgjs` ops), upload the mesh to the blob store instead.

### 3.3 Mesh geometry (§11.3)

- ☐ For imported / baked meshes, Y.Doc holds only `{ blobHash, versionVector
  }`. Replacing a mesh produces a new blob and bumps the version vector.
- ☐ Concurrent replacement keeps both blobs server-side and surfaces a
  conflict (see §3.5).

### 3.4 CSG-tree edits (§9.4 + open problem #4)

- ☐ Represent a CSG tree as `Y.Array<CSGOp>` with explicit reorder operations
  (drag-to-reorder UI). v1 keeps order-dependence visible; full CRDT-tree
  ordering deferred.
- ☐ Document the constraint clearly in package README so consumers know v1
  CSG composition is `Y.Array`-ordered, not topologically merged.

### 3.5 Conflict UX (§9.4, §9.7)

- ☐ `packages/collab/src/conflicts/detector.ts` — observes Y.Doc updates and
  flags concurrent geometry changes within a tunable window.
- ☐ `packages/collab/src/conflicts/ui-bridge.ts` — emits structured events
  the viewer/UI can render: ghost overlay of the "loser" position, "keep
  mine" button that generates a new transaction.
- ☐ Conflict badge component in `packages/viewer` (or `apps/web`) listening
  to `session.conflicts`.

### 3.6 Tests

- ☐ Determinism test: same params → same hash on every supported platform.
- ☐ Concurrent move scenario: two peers translate same wall; LWW wins; loser
  sees ghost; "keep mine" produces a follow-up op.
- ☐ Memory test: 100k entities with realistic geometry mix stays under 200
  MB for the Y.Doc (§15).

**Exit criteria:** a peer can move/resize a parametric wall and the mesh
appears identically on every other peer without any mesh bytes traversing
the websocket.

---

## 4. Phase v0.4 — Federation (4 weeks)

> **Status: ◐ Core + resolver interface landed.** `FederationSession`
> (`createFederationSession`) hosts N model-room `CollabSession`s plus
> a shared `_federation` Y.Doc for cross-model `FederationRecord`s
> (clash, RFI, view, BCF refs). Presence is project-scoped via the
> `_federation` doc per §10.2. **`FederationResolver`** is now a typed
> interface with `passThroughResolver` (default for IFCX UUID paths)
> and `createMapBackedResolver(table)` (for explicit
> `globalId → (modelId, localId)` lookups). The renderer's existing
> numeric-offset `FederationRegistry` can be wrapped to satisfy this
> interface without `@ifc-lite/collab` taking a hard dependency on the
> renderer (adapter snippet documented in `federation/resolver.ts`).
> Still pending: viewer-side federation-aware avatar rendering.

**Goal:** Multi-model rooms work; cross-model relationships and project-wide
presence behave per §10.

**Spec sections:** §10 federation in full.

- ☐ Room model upgrades from "1 model = 1 room" to "1 project = N model
  Y.Docs + 1 `_federation` Y.Doc." Update room IDs to `project-id/model-id`
  and `project-id/_federation`.
- ☐ `packages/collab/src/federation/` — manages multiple Y.Doc handles per
  session; one `FederationSession` aggregates them and exposes a single
  `presence` channel project-scoped per §10.2.
- ☐ Cross-model relationship records in `_federation` (clash issues, BCF
  topic refs, federation views) following the schema in §10.1.
- ☐ Hook into existing `FederationRegistry`: never reinvent globalId →
  modelId resolution; the CRDT layer only stores `{ modelId, globalId }`
  pairs and asks `FederationRegistry` to resolve per `AGENTS.md` §4.
- ☐ Awareness across federation: a user in `arch` and a user in `mep` see
  each other in the same peer list with model annotations on the avatar.
- ☐ Tests: scripted multi-model session with concurrent edits to `arch` and
  `mep`; verify both Y.Docs converge independently and `_federation` clash
  records sync correctly.

**Exit criteria:** open three federated models, drop a clash issue spanning
two of them, see a teammate hover the issue in the third model's viewport.

---

## 5. Phase v0.5 — Production (4 weeks)

> **Status: ☑ Production stack landed.** `JsonlFileAuditSink` (size-based
> rotation), idle room unloading, retention policy module (`planRetention`
> + `applyRetention`). Auth, per-peer rate limiting, role-based write
> filtering, and the blob HTTP route are in. **Server-driven IFCX
> snapshot worker** (`SnapshotWorker`) writes per-room `.ifcx` files on
> an interval. **Prometheus `/metrics`** endpoint serves
> `collab_rooms`, `collab_room_peers`, `collab_updates_total`,
> `collab_rejects_total{reason}`. **Anti-replay HMAC verifier wired**:
> `verifyMessage` hook on `RoomManager`, `verifyWithReplayProtector`
> adapter, `encodeSignedFrame` / `decodeSignedFrame` envelope codec
> (open problem #8 closed). **`S3Persistence`** ships against an
> injectable `S3LikeClient` so AWS SDK / R2 / MinIO all plug in
> without `@ifc-lite/collab-server` taking a hard dep on
> `@aws-sdk/client-s3`. **TLS / secure-server helpers**:
> `createSecureHttpServer(opts)`, `applySecurityHeaders(res)`,
> `secureHttpHandler(inner)` ship hardened defaults (TLS 1.2+,
> conservative ciphers, OWASP headers, TRACE/TRACK rejection). Still
> pending: full bucketed histograms (the simple histogram is good
> enough for v0.5), Redis persistence backend (apps can write that as
> a third pluggable `Persistence`).

**Goal:** The server can be operated. Auth, persistence, observability, and
periodic IFCX snapshots are all real.

**Spec sections:** §8.2 authorization, §12.2 server persistence, §12.3 IFCX
snapshots (server-driven), §14 security & privacy (everything except E2E,
which is v1.0), §15 performance budget at scale.

### 5.1 Authorization (§8.2)

- ☐ JWT auth on connect; re-validation every 5 minutes.
- ☐ Roles: `viewer`, `commenter`, `editor`, `admin`. Server-side update
  filter rejects writes from insufficient roles.
- ☐ Optional per-section locks: server-side filter that drops mutations to
  `entity.geometryRef` from non-locked-section users (per §8.2). Defer
  per-storey/discipline granularity to v1.x (open problem #7).
- ☐ `audit-log.ts`: every server-mediated event logged with
  `(timestamp, user, room, op-type, op-hash)`; exportable to S3 / file.

### 5.2 Persistence at scale (§12.2)

- ☐ Pluggable persistence in `packages/collab-server/src/persistence/`:
  - `local-fs.ts` (existing, dev-only)
  - `s3.ts` (production)
  - `redis.ts` (optional, for `y-redis`-style multi-instance scale)
- ☐ Compaction policy: snapshot every 1000 updates or 5 minutes; truncate
  log to snapshot+tail.
- ☐ Cost-model policy decision (open problem #9): full log 90 days, then
  compacted snapshots only — encoded in a `retention.ts` module with config.

### 5.3 Server-driven IFCX snapshots (§12.3)

- ☐ `snapshot-worker.ts` exports `.ifcx` every N minutes (configurable per
  room) to S3 or filesystem. Layer breakdown per user (§12.3) is preserved.
- ☐ Snapshot job runs in a separate process so it can't block sync.

### 5.4 Observability

- ☐ Prometheus metrics (or OpenTelemetry) for: peers per room, updates per
  second, awareness msgs/sec, snapshot duration, persistence latency, room
  memory.
- ☐ Structured JSON logging with correlation IDs (room, user, request).
- ☐ Health, readiness, and liveness endpoints suitable for k8s.

### 5.5 Anti-replay (open problem #8)

- ☐ Per-update server-side validation: JWT on the connection plus an
  optional per-update HMAC. Drop replays via clientID + clock window.
- ☐ Document the threat model in `packages/collab-server/SECURITY.md`.

### 5.6 Hardening

- ☑ TLS 1.2+ shipped as the in-process baseline via
  `createSecureHttpServer` (Node's secure defaults). Hardening to a
  TLS 1.3-only enforcement remains a deployment-time choice: terminate
  at a LB configured for TLS 1.3, or pass `minVersion: 'TLSv1.3'` into
  the secure-server options.
- ☐ At-rest AES-256 encryption for stored Y states + blobs (§14).
- ☐ Per-peer write budget hooks for v0.6 (open problem #10) — ship the knob
  unused now; turn it on in v0.6.
- ☐ Soak test (§18 long-haul stability): 24h with 5 peers, no leaks.

**Exit criteria:** deploy `collab-server` to a staging environment with TLS,
JWT auth, S3 persistence, periodic IFCX snapshots, and dashboards. Pass a
24h soak.

---

## 6. Phase v0.6 — MCP integration (2 weeks)

> **Status: ◐ Presence side landed.** `markAsAgent`, `agentIdentityFromMcp`,
> and the `AGENT_PALETTE` give MCP tool handlers a one-line way to
> publish agent presence with `(agent)` suffix and the `tool: 'edit'`
> marker. The server-side audit log already records every update with
> `(timestamp, user, room, op-type, op-hash)`, and `startCollabServer`
> already accepts a per-principal rate-limit function so service
> accounts can run with a tighter budget than humans. Still pending:
> the actual MCP tool-handler wiring inside `apps/api`.

**Goal:** AI agents become first-class peers. Their tool calls are CRDT
operations and they show up in awareness.

**Spec sections:** §16.4 MCP cross-spec integration, §10 (agent presence
inherits federation rules), open problem #10 (agent rate limiting).

- ☐ MCP server tool handlers (e.g. `entitySetProperty`) route through
  `ctx.session.bim.mutate.*` instead of writing to the in-memory store
  directly. The session is created with a service-account JWT.
- ☐ Agents publish presence with `tool: 'edit'`, a distinct avatar style,
  and a "(agent)" suffix on the display name.
- ☐ Per-peer write budget (open problem #10) is turned on for service
  accounts: tunable updates/sec ceiling; backpressure pushed back to MCP
  tools as a structured error so the agent retries gracefully.
- ☐ Audit log entries for agent-originated ops include the MCP tool name and
  invocation ID.
- ☐ Tests: scripted MCP session edits a wall while a human peer edits its
  Pset; both converge; UI shows the agent's avatar.

**Exit criteria:** Claude makes a property edit through MCP, a human in the
viewer sees the agent's cursor, the mutation, and the audit-log entry.

---

## 7. Phase v0.7 — Branching (6 weeks)

> **Status: ☑ Branching + differential composer + history sidecar
> landed.** `forkSession` / `mergeBranch` / `extractMinimalLayer` are
> in. **`HistorySidecar`** (`MemoryHistorySidecar` ship; future
> `AutomergeHistorySidecar` slots into the same interface) records
> snapshots, computes per-entity-id diffs, and supports
> `branch(name)` / `merge(branch, into)` for the time-travel UI.
> **`attachHistorySidecar(session, sidecar, opts)`** drives a sidecar
> from a live `CollabSession` on a configurable timer + on demand,
> with an optional differential layer in each entry for cheap diff
> queries. The Automerge-backed sidecar is the only deferred item —
> the interface is ready when the Rust+WASM dep is.

**Goal:** Users can fork a live document, edit privately, and merge back —
either as an IFCX layer or as a Y operation diff. Automerge sidecar holds
long-term history with branching/time-travel.

**Spec sections:** §4 (Yjs + Automerge split), §12.4 branching, open
problem #1 (layer ordering).

- ☐ `packages/collab/src/snapshot/branch.ts`:
  - `session.fork(name)` — snapshot Y.Doc → seed a new Y.Doc into a sibling
    room with `parentRoomId` metadata.
  - `session.mergeBranch(branch, strategy)` where strategy is
    `'layer'` (compose IFCX layers per §10) or `'ops'` (replay branch's Y
    update log on top of the parent room).
- ☐ Layer ordering (open problem #1): adopt strategy (b) — explicit "main"
  layer + per-user/per-branch "patch" layers. Encode the ordering rule in
  `snapshot/layers.ts`.
- ☐ Automerge sidecar in `packages/collab/src/history/automerge-sidecar.ts`:
  - At each Y.Doc snapshot, append the snapshot to an Automerge document
    that captures full history with branches and merge previews.
  - Expose `session.history.list()`, `.diff(a, b)`, `.preview(merge)`.
- ☐ UI: `apps/*` gets a branch tree visualization and a merge preview pane.
- ☐ Tests: fork → diverge → merge (both strategies) round-trips correctly.

**Exit criteria:** a user can fork "experiment-A", make changes, preview the
merge, accept it, and the audit log records the merge as a single event.

---

## 8. Phase v1.0 — GA (4 weeks)

> **Status: ☑ Schema migration + units + GDPR + E2E encryption all
> landed.** `getSchemaVersion`, `setSchemaVersion`,
> `registerSchemaMigration`, `migrateSchema`, and `MIGRATION_ORIGIN`
> plumb migrations (open #2). `convertEntityUnits`, `convertValue`,
> `familyOf` cover length / area / volume / angle (open #3).
> `exportAndLeave` + `redactAuthorMeta` cover GDPR. **E2E encryption**
> ships as a complete WebCrypto-based suite: `deriveRoomKey` (PBKDF2),
> `generateRoomKey` / `exportRoomKey` / `importRoomKey`, `encryptFrame`
> / `decryptFrame` with versioned `[1B ver][12B IV][N B AES-GCM]`
> framing, and `createKeyRing(initial, { gracePeriodMs })` for
> rotation that still decodes in-flight frames. Apps wire the
> ciphertext through any provider (the server only routes opaque
> bytes — server-side IFCX export is therefore unavailable without key
> escrow, as the spec calls out). The actual IFC schema migrations
> and the long-tail unit families remain consumer responsibility.

**Goal:** Production-ready with optional E2E encryption.

**Spec sections:** §14 (E2E option), §13 offline-first guarantees, §15
final perf budget, every open problem either resolved or explicitly deferred
in writing.

- ☐ Optional end-to-end encryption: client-side per-room key, server only
  routes ciphertext. Trade-off documented (server-side IFCX export
  unavailable without key escrow). Implement key rotation and a member-add
  rewrap protocol.
- ☐ GDPR flows (§14): hard-delete a room; user data export-then-leave.
- ☐ Polish offline-first edge cases (§13): "edits at risk" warnings when
  last-sync exceeds N minutes; explicit "unsynced edits" dialog on tab
  close.
- ☐ Schema-version migration UX (open problem #2): server-mediated lock,
  explicit prompt, unit-conversion convention for property unit changes
  (open problem #3).
- ☐ Final hardening pass: fuzz, dependency audit, threat model review,
  pen-test signoff.
- ☐ Docs: `docs/architecture/collab.md` (architecture deep dive),
  `docs/guide/collaboration.md` (user-facing).

**Exit criteria:** GA release of `@ifc-lite/collab@1.0.0` and
`@ifc-lite/collab-server@1.0.0` with changesets, CHANGELOGs, and migration
notes from any breaking changes since v0.1.

---

## 9. Cross-cutting concerns

### 9.1 Testing strategy (§18) — applies to every phase

- ☐ Convergence property tests: random concurrent edit traces, all peers end
  identical (Quickcheck-style).
- ☐ Performance harness in `packages/collab/test/perf.bench.ts` covering
  1k / 10k / 100k / 1M entities. Numbers checked against §15 budget on every
  PR via Turbo cache.
- ☐ Conflict scenarios: scripted scenarios for every class in §9.
- ☐ Network chaos: packet loss, delays, partitions; assert offline-first
  guarantees from §13.
- ☐ Cross-client soak: Tauri desktop + browser viewer + MCP server + CLI in
  one room.

### 9.2 Performance budget (§15) — non-negotiable per phase

| Metric | Target | Phase that first enforces it |
|---|---|---|
| Y.Doc memory @100k entities | <200 MB | v0.1 |
| Single-attr update size | <200 B | v0.1 |
| Sync latency LAN | <50 ms | v0.2 |
| Sync latency intercontinental | <300 ms | v0.2 |
| Awareness update rate | 30 Hz | v0.2 |
| Concurrent users / room | 50+ | v0.5 |
| IFCX snapshot @100k | <5 s | v0.5 |
| Cold load @100k | <3 s | v0.1 |

### 9.3 Open problems tracker (§20)

| # | Problem | Phase to land | Owner |
|---|---|---|---|
| 1 | Layer ordering vs CRDT op ordering | v0.7 (strategy b) | LT+ |
| 2 | Schema-version migrations during live session | v1.0 | LT+ |
| 3 | Property unit changes mid-session | v1.0 | LT+ |
| 4 | CSG-tree concurrent edits | v0.3 (ordered Y.Array); CRDT-tree deferred to v1.x | LT+ |
| 5 | Geometry-kernel determinism guarantees | v0.3 (CI matrix) | LT+ |
| 6 | Large-blob GC | v0.5 (epoch + refcount) | LT+ |
| 7 | Permissions granularity (per-storey) | v1.x | — |
| 8 | Replay attack surface | v0.5 | LT+ |
| 9 | Cost model for hosted service | v0.5 (retention.ts) | LT+ |
| 10 | AI agent rate limiting | v0.6 (knob in v0.5) | LT+ |

### 9.4 Risks & mitigations

- **Determinism risk** for the Rust geometry kernel (open #5). Mitigation:
  fail-closed — if a kernel build can't prove determinism, fall back to
  uploading mesh blobs.
- **Storage cost blowup** on hosted service. Mitigation: retention policy
  shipped at v0.5, dashboards in v0.5.
- **Yjs binary update format churn.** Pin major versions in `packages/collab`
  and gate upgrades behind dual-read tests against the recorded fixture.
- **Perf regressions** on 1M-entity models. Mitigation: bench-on-PR with
  Turbo, alert on >10% delta from baseline.

---

## 10. Phase status summary

| Phase | Status | ETA (cumulative) |
|---|---|---|
| v0.1 Foundation | ☑ Landed | T+4w |
| v0.2 Multi-peer | ◐ Mostly landed | T+8w |
| v0.3 Geometry | ◐ Foundations + GC landed | T+14w |
| v0.4 Federation | ◐ Core + resolver interface landed | T+18w |
| v0.5 Production | ☑ Landed | T+22w |
| v0.6 MCP integration | ◐ Presence side landed | T+24w |
| v0.7 Branching | ☑ Landed | T+30w |
| v1.0 GA | ☑ Landed | T+34w |

Total: ~8 months from start to v1.0; v0.2 is the LinkedIn-demo milestone at
T+8w (per spec §19).

---

## 11. Glossary

- **Y.Doc** — a Yjs document; the live CRDT state container.
- **Layer (IFCX)** — an `.ifcx` file that adds to or overrides earlier
  layers when composed; spec §2 property #2.
- **Awareness** — out-of-band, last-write-wins-by-clock peer presence
  (`y-protocols/awareness`); not persisted.
- **Federation Registry** — existing `AGENTS.md` §4 component that resolves
  globalId ↔ modelId across loaded models. The CRDT layer integrates with
  it; never replaces it.
- **Branch (collab)** — a forked Y.Doc with `parentRoomId` metadata that can
  be merged back as an IFCX layer or a Y op-diff (§12.4).
