The page
Shows the briefing and its controls, and makes every request that changes something.
A React app. It only draws what the server sent: a redaction bar on screen is a bar in the data, and there is nothing behind it for the developer tools to find.
Code web/src/Room.tsx web/src/api.ts
More about this in the story →
Section editors
One live editor per section the member is cleared for, each on its own connection.
Each editor holds a Yjs copy of one section and merges other people's edits as they arrive. A section the member can't read has no editor and no connection at all.
Code web/src/SectionEditor.tsx web/src/SectionBoundary.tsx
More about this in the story →
Audit log
Records who did what, in the same transaction as the change itself.
Each row carries a hash of the row before it, so a deleted or edited row breaks the chain. The head of the chain is signed, so a copy of the log can be checked away from the server.
Code src/audit/audit.service.ts src/audit/chain.ts src/audit/checkpoint.ts
REST API
Handles requests. Every route names the action it needs and fails if nobody asked the policy.
Each request runs inside one database transaction pinned to the caller's organization. A route that finishes without the policy having decided its declared action returns an error instead of an answer, so an endpoint that forgets to check can't leak.
Code src/members/members.service.ts src/briefings/briefings.service.ts src/database/tenant.ts
More about this in the story →
Policy
One file of plain functions that answers "may this member do this to that?".
Roles, departments, clearance levels and shares all meet here. The REST API and the live connections call the same functions, so a browser tab and an open connection can never disagree about what someone may see.
Code src/policy/policy.ts src/briefings/access.ts
Collaboration server
Holds the open connections, merges edits, and re-checks every connection when access changes.
A Hocuspocus server inside the same process. A connection is checked when it opens and again whenever a permission change is announced. A read-only connection is read-only on the server: edits it sends are dropped, whatever the page shows.
Code src/realtime/realtime.service.ts
More about this in the story →
Redacted copies
Writes a separate copy of a section for each clearance level, with classified words replaced by bars.
Readers below a word's classification connect to a copy that never contained it. The bar's length is rounded so it doesn't give away the length of the word behind it.
Code src/realtime/projection.ts
More about this in the story →
Tenant tables
Every organization's rows, behind row-level security the server can't switch off.
The server connects as a role that owns nothing. A query that forgets its organization filter returns no rows instead of another organization's rows.
Code src/database/migrations.ts src/database/tenant.ts
More about this in the story →
Notifications
PostgreSQL's own broadcast (LISTEN/NOTIFY), used to tell every server instance that access changed.
A notice is sent inside the transaction that makes the change, so it is delivered only if the change commits. No message broker to run, and no notice for a change that didn't happen.
Code src/realtime/access-changes.ts
More about this in the story →