@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 onmainonly 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-websocketcompatible)- Conventions to honor (from
AGENTS.md): - Strict IFC nomenclature in user-facing APIs (
IfcRelAggregates, notAggregates); Pset / property names in PascalCase. - Federation-aware IDs: never bypass
FederationRegistryfor cross-model lookups. - License headers on every new
.ts/.rssource file. - Changesets for any change that touches a published package.
- File size cap ~400 LOC; no
as any, no barecatch {}. - 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-s3orioredis).
1. Phase v0.1 — Foundation (4 weeks)¶
Status: ☑ Landed. Both
packages/collabandpackages/collab-servership in this PR.pnpm --filter @ifc-lite/collab testandpnpm --filter @ifc-lite/collab-server testpass 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. TheWeb Worker snapshotandBim.fromFile bindingitems 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/withpackage.json,tsconfig.jsonextendingtsconfig.packages.json, MPL license header, README, CHANGELOG. - ☐ Add
packages/collabandpackages/collab-servertopnpm-workspace.yaml(already covered bypackages/*glob — verify). - ☐ Add a changeset describing the new
@ifc-lite/collabpackage at minor version0.1.0. - ☐ Wire
packages/collabintoturbo.jsonbuild pipeline.
1.2 Y.Doc schema (§5)¶
Implement in packages/collab/src/doc/schema.ts:
- ☐
createCollabDoc()returns aY.Docwith 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, includingattributes: 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 infrom-ifcxseeding. - ☐ 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 withmeta.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/ifcxreader), 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 IFCXdata[]records. Round-trip test:seed → snapshot → seedmust converge. - ☐
layers.ts: extract a per-user layer file using Yjs's update history filtered byclientID. Output a valid.ifcxwhose 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 overy-indexeddbkeyed byroomId. 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.tsexportscreateUndoManager(doc, origin)built onY.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-ifcxruns, tombstones older than the snapshot timestamp are purged.
1.6 Public API surface (§16.2)¶
- ☐
packages/collab/src/index.tsexportscreateCollabSession(opts)with the v0.1 subset: no network, justbind(bim),snapshot(),dispose(). - ☐ Bind into
@ifc-lite/sdk:Bim.fromFile('model.ifcx', { collab })— add acollab?: CollabSessionoption to the existingBim.fromFile. - ☐ Mutations from
@ifc-lite/mutationsroute 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)+cursorScreenPositionnow 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 inpackages/viewerremains 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 inserver.tsusingws. 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
/healthzand a single-instance dev script (pnpm --filter @ifc-lite/collab-server dev).
2.2 Client provider¶
- ☐
packages/collab/src/providers/websocket.ts— wrapsy-websocketwith reconnect/backoff and astatusobservable (connected | connecting | offline | error). - ☐
packages/collab/src/sync/room.ts— joins/leaves rooms, exposessession.status. - ☐ Extend
createCollabSession({ provider: 'websocket', serverUrl, … }).
2.3 Awareness (§7)¶
- ☐
packages/collab/src/awareness/protocol.ts— wrapsy-protocols/awarenesson the same transport as the Y.Doc updates, on a separate channel. - ☐
packages/collab/src/awareness/presence.ts— typedPresenceStateper §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/viewerconsumessession.presenceand 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.tsusing 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 anyBlobStoreimplementing the optionalstat()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/:hashagainstcollab-server. - ☐ Server route
packages/collab-server/src/blobs.tswith 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
GeometryRefto carry aparams: Y.Map<unknown>for parametric primitives (extruded-area-solid,swept-disk-solid,revolved-area-solid, primitives). - ☐ Bridge from Y.Map params →
ifc-lite-geometryRust kernel: on param change, recompute the mesh locally; emit content-hash; updateGeometryRef.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.
csgjsops), 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(orapps/web) listening tosession.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-roomCollabSessions plus a shared_federationY.Doc for cross-modelFederationRecords (clash, RFI, view, BCF refs). Presence is project-scoped via the_federationdoc per §10.2.FederationResolveris now a typed interface withpassThroughResolver(default for IFCX UUID paths) andcreateMapBackedResolver(table)(for explicitglobalId → (modelId, localId)lookups). The renderer's existing numeric-offsetFederationRegistrycan be wrapped to satisfy this interface without@ifc-lite/collabtaking a hard dependency on the renderer (adapter snippet documented infederation/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
_federationY.Doc." Update room IDs toproject-id/model-idandproject-id/_federation. - ☐
packages/collab/src/federation/— manages multiple Y.Doc handles per session; oneFederationSessionaggregates them and exposes a singlepresencechannel 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 asksFederationRegistryto resolve perAGENTS.md§4. - ☐ Awareness across federation: a user in
archand a user inmepsee each other in the same peer list with model annotations on the avatar. - ☐ Tests: scripted multi-model session with concurrent edits to
archandmep; verify both Y.Docs converge independently and_federationclash 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.ifcxfiles on an interval. Prometheus/metricsendpoint servescollab_rooms,collab_room_peers,collab_updates_total,collab_rejects_total{reason}. Anti-replay HMAC verifier wired:verifyMessagehook onRoomManager,verifyWithReplayProtectoradapter,encodeSignedFrame/decodeSignedFrameenvelope codec (open problem #8 closed).S3Persistenceships against an injectableS3LikeClientso AWS SDK / R2 / MinIO all plug in without@ifc-lite/collab-servertaking 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 pluggablePersistence).
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.geometryReffrom 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, fory-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.tsmodule with config.
5.3 Server-driven IFCX snapshots (§12.3)¶
- ☐
snapshot-worker.tsexports.ifcxevery 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 passminVersion: '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 theAGENT_PALETTEgive MCP tool handlers a one-line way to publish agent presence with(agent)suffix and thetool: 'edit'marker. The server-side audit log already records every update with(timestamp, user, room, op-type, op-hash), andstartCollabServeralready 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 insideapps/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 throughctx.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/extractMinimalLayerare in.HistorySidecar(MemoryHistorySidecarship; futureAutomergeHistorySidecarslots into the same interface) records snapshots, computes per-entity-id diffs, and supportsbranch(name)/merge(branch, into)for the time-travel UI.attachHistorySidecar(session, sidecar, opts)drives a sidecar from a liveCollabSessionon 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 withparentRoomIdmetadata.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, andMIGRATION_ORIGINplumb migrations (open #2).convertEntityUnits,convertValue,familyOfcover length / area / volume / angle (open #3).exportAndLeave+redactAuthorMetacover GDPR. E2E encryption ships as a complete WebCrypto-based suite:deriveRoomKey(PBKDF2),generateRoomKey/exportRoomKey/importRoomKey,encryptFrame/decryptFramewith versioned[1B ver][12B IV][N B AES-GCM]framing, andcreateKeyRing(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.tscovering 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/collaband 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
.ifcxfile 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
parentRoomIdmetadata that can be merged back as an IFCX layer or a Y op-diff (§12.4).