Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions collab-presence-heartbeat-liveness-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Collaborative Presence Heartbeat Liveness Guard

This module adds a focused Real-Time Collaborative Editor slice for issue #12: heartbeat-based cleanup before live cursor and user-presence fanout.

It evaluates synthetic collaboration rooms for:

- stale heartbeats
- ghost sessions that still show cursors or editing state
- reconnect-grace sessions that should keep restore state but not broadcast cursors
- duplicate collaborator tabs
- orphaned cursors without a session record
- expired typing indicators
- expired presence fanout leases
- future heartbeat clock skew

The output is a sanitized presence snapshot plus cleanup, recovery, and fanout queues. The module is dependency-free and uses synthetic data only. It does not call live editor transports, identity services, notebook kernels, storage providers, notification systems, or external APIs.

## Run

```bash
npm run check
npm test
npm run demo
```

`npm run demo` writes reviewer artifacts under `reports/`:

- `presence-liveness-packet.json`
- `presence-liveness-report.md`
- `summary.svg`
- `demo.mp4`

## Scope

This is intentionally separate from presence privacy, notification visibility, notebook kernel leases, lock/checkpoint recovery, autosave recovery, broad editor foundations, and release/export guards. It handles the liveness decision immediately before a presence room publishes cursor and activity snapshots.
37 changes: 37 additions & 0 deletions collab-presence-heartbeat-liveness-guard/acceptance-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Acceptance Notes

The reviewer can verify this slice without accounts, services, or private data.

## Expected Synthetic Result

The demo fixture produces:

- 2 rooms reviewed
- 8 sessions reviewed
- 3 sessions published
- 5 sessions suppressed
- 7 cleanup actions
- 1 reconnect recovery action
- 3 presence fanout events
- 1 room held for broadcast cleanup
- 1 room sanitized and ready

The critical case is `sess-bio-ghost`: its heartbeat is stale while an editing cursor and typing state are still visible. The guard suppresses it and emits cleanup actions before the room fanout can show a ghost collaborator.

## Validation

```bash
npm run check
npm test
npm run demo
```

Optional video metadata check:

```bash
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,duration -show_entries format=size,duration -of default=noprint_wrappers=1 reports/demo.mp4
```

## Boundaries

This module does not implement the editor UI, network transport, CRDT/OT operations, section locking, notebook execution, privacy redaction, notification fanout, or release export. It is a deterministic pre-broadcast liveness guard for cursor and presence snapshots.
91 changes: 91 additions & 0 deletions collab-presence-heartbeat-liveness-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const fs = require("node:fs")
const path = require("node:path")
const { spawnSync } = require("node:child_process")
const { evaluatePresenceLiveness } = require("./index")
const { rooms, policy } = require("./sample-data")

const reportsDir = path.join(__dirname, "reports")
fs.mkdirSync(reportsDir, { recursive: true })

const packet = evaluatePresenceLiveness({
asOf: "2026-05-23T01:20:00.000Z",
rooms,
policy,
})

fs.writeFileSync(
path.join(reportsDir, "presence-liveness-packet.json"),
`${JSON.stringify(packet, null, 2)}\n`,
)

const markdown = [
"# Collaborative Presence Heartbeat Liveness Report",
"",
`Rooms reviewed: ${packet.summary.totalRooms}`,
`Sessions reviewed: ${packet.summary.totalSessions}`,
`Published sessions: ${packet.summary.publishedSessions}`,
`Suppressed sessions: ${packet.summary.suppressedSessions}`,
`Cleanup actions: ${packet.summary.cleanupActions}`,
`Recovery actions: ${packet.summary.recoveryActions}`,
`Fanout events: ${packet.summary.fanoutEvents}`,
`Audit digest: \`${packet.audit.digest}\``,
"",
"## Room Decisions",
...packet.rooms.flatMap((room) => [
"",
`### ${room.title}`,
`- Status: ${room.status}`,
`- Reviewed sessions: ${room.reviewedSessions}`,
`- Published sessions: ${room.publishedSessions}`,
`- Suppressed sessions: ${room.suppressedSessions}`,
`- Cleanup codes: ${room.cleanupQueue.map((item) => item.code).join(", ") || "none"}`,
`- Recovery codes: ${room.recoveryQueue.map((item) => item.code).join(", ") || "none"}`,
]),
"",
"## Cleanup Queue",
...packet.cleanupQueue.map((item) => (
`- ${item.roomId}/${item.sessionId}: ${item.code} (${item.severity})`
)),
"",
]

fs.writeFileSync(path.join(reportsDir, "presence-liveness-report.md"), markdown.join("\n"))

const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540">
<rect width="960" height="540" fill="#101827"/>
<text x="48" y="78" fill="#f8fafc" font-family="Arial" font-size="34" font-weight="700">Collaborative Presence Heartbeat Liveness Guard</text>
<text x="48" y="122" fill="#cbd5e1" font-family="Arial" font-size="18">Ghost session cleanup before live cursor and presence fanout</text>
<rect x="48" y="170" width="250" height="150" rx="12" fill="#047857"/>
<text x="78" y="230" fill="#d1fae5" font-family="Arial" font-size="56" font-weight="700">${packet.summary.publishedSessions}</text>
<text x="78" y="270" fill="#a7f3d0" font-family="Arial" font-size="22">published</text>
<rect x="355" y="170" width="250" height="150" rx="12" fill="#b45309"/>
<text x="385" y="230" fill="#fffbeb" font-family="Arial" font-size="56" font-weight="700">${packet.summary.recoveryActions}</text>
<text x="385" y="270" fill="#fde68a" font-family="Arial" font-size="22">recoverable</text>
<rect x="662" y="170" width="250" height="150" rx="12" fill="#be123c"/>
<text x="692" y="230" fill="#ffe4e6" font-family="Arial" font-size="56" font-weight="700">${packet.summary.suppressedSessions}</text>
<text x="692" y="270" fill="#fecdd3" font-family="Arial" font-size="22">suppressed</text>
<text x="48" y="386" fill="#e2e8f0" font-family="Arial" font-size="20">Controls: stale heartbeat, ghost session, duplicate tab, orphan cursor, expired typing, expired fanout lease.</text>
<text x="48" y="426" fill="#94a3b8" font-family="Arial" font-size="16">Digest ${packet.audit.digest.slice(0, 28)}...</text>
</svg>
`

fs.writeFileSync(path.join(reportsDir, "summary.svg"), svg)

const ffmpeg = spawnSync("ffmpeg", [
"-y",
"-f",
"lavfi",
"-i",
"color=c=0x101827:s=960x540:d=5:r=15",
"-vf",
"drawbox=x=48:y=170:w=250:h=150:color=0x047857@1:t=fill,drawbox=x=355:y=170:w=250:h=150:color=0xb45309@1:t=fill,drawbox=x=662:y=170:w=250:h=150:color=0xbe123c@1:t=fill,drawbox=x=48:y=368:w=864:h=18:color=0x22d3ee@1:t=fill",
"-pix_fmt",
"yuv420p",
path.join(reportsDir, "demo.mp4"),
], { stdio: "ignore" })

if (ffmpeg.status !== 0) {
console.warn("ffmpeg video generation failed; JSON, Markdown, and SVG reports were still generated.")
}

console.log(`Wrote collaborative presence liveness artifacts to ${reportsDir}`)
Loading