.github/9
ISSUE_TEMPLATE/3bug_report.md config.yml feature_request.md
workflows/3ci.yml clean-clone.yml release.yml
CODEOWNERS dependabot.yml pull_request_template.md
apps/630
api/250
src/243
common/30
__tests__/3authorize.test.ts bigint-serialization.interceptor.test.ts files-limits.test.ts
filters/1all-exceptions.filter.ts
guards/3auth.guard.ts internal-service.guard.ts rate-limit.guard.ts
interceptors/2actor-context.interceptor.ts bigint-serialization.interceptor.ts
middleware/2idempotency.test.ts idempotency.ts
pipes/1zod-validation.pipe.ts
actor-context.ts audit.service.ts cache.test.ts cache.ts cookies.ts core.module.ts deprecation.ts email.service.ts errors.ts event-bus.ts log-scrubber.test.ts log-scrubber.ts logger.ts permission-cache-bridge.ts prisma.service.ts redis.service.ts trace-metadata.ts types.ts
config/1env.ts
lib/11
__tests__/5crypto.test.ts filter-builder.test.ts jwt.test.ts pagination.test.ts positions.test.ts
authorize.ts crypto.ts filter-builder.ts jwt.ts pagination.ts positions.ts
modules/197
ai/28
__tests__/4ai-writing.test.ts database-ai.test.ts generate-executor.test.ts prompts.test.ts
generator/4executor.ts generate.controller.ts generate.dto.ts generate.service.ts
ai-chat.service.ts ai-rate-limit.ts ai-writing.controller.ts ai-writing.dto.ts ai-writing.service.ts ai.controller.ts ai.module.ts ai.service.ts budget-guard.service.ts database-ai.controller.ts database-ai.dto.ts database-ai.service.ts db-prompts.ts dto.ts gateway-provider.ts ledger.service.ts prompts.ts rag.controller.ts rag.dto.ts rag.service.ts
api-keys/3api-keys.controller.ts api-keys.module.ts api-keys.service.ts
audit/2audit-log.controller.ts audit.module.ts
auth/4auth.controller.ts auth.module.ts auth.service.ts dto.ts
automations/5automations.controller.ts automations.module.ts automations.service.ts dto.ts webhook-incoming.controller.ts
blocks/5
__tests__/1batch-version-cas.test.ts
block.service.ts blocks.controller.ts blocks.module.ts dto.ts
comments/4comments.controller.ts comments.module.ts comments.service.ts dto.ts
databases/9
__tests__/3normalize-select-options.test.ts option-id-canonicalization.test.ts record-open-query-shape.test.ts
database.service.ts databases.controller.ts databases.module.ts dto.ts property-cache-schema.ts query.service.ts
docs/2docs.controller.ts docs.module.ts
files/10
__tests__/1quota.test.ts
dto.ts files.controller.ts files.module.ts files.service.ts quota.ts record.ts s3.ts uploads.controller.ts uploads.service.ts
forms/18debug.controller.ts form.dto.ts forms.controller.ts forms.module.ts forms.service.ts markdown.test.ts markdown.ts optional-auth.guard.ts public-forms.controller.ts public-forms.service.ts question.dto.ts questions.service.ts rate-limit.ts recaptcha.ts slug.ts submissions.controller.ts submissions.service.ts submit-schema.ts
health/3health.controller.ts health.module.ts health.service.ts
integrations/3integrations.controller.ts integrations.module.ts integrations.service.ts
internal/12
__tests__/2flush-query-shape.test.ts internal.endpoints.test.ts
ai-test-seam.controller.ts ai-test-seam.dto.ts ai-test-seam.service.ts internal.controller.ts internal.dto.ts internal.module.ts internal.service.ts test-seam.controller.ts test-seam.dto.ts test-seam.service.ts
notifications/3notifications.controller.ts notifications.module.ts notifications.service.ts
oauth/6
__tests__/1oauth.scopes.test.ts
oauth.controller.ts oauth.module.ts oauth.service.ts scopes.ts token-keys.ts
org/15audit.service.ts embed-preview.controller.ts guards.ts members.service.ts org-admin.controller.ts org-policy.service.ts org.module.ts overview.service.ts policy-effective.controller.ts policy.schema.ts policy.test.ts retention.service.ts totp.test.ts totp.ts two-factor.service.ts
orgs/5dto.ts orgs.controller.ts orgs.module.ts orgs.service.ts util.ts
pages/4dto.ts page.service.ts pages.controller.ts pages.module.ts
scim/15
__tests__/3email-mapping.test.ts etag.test.ts filters.test.ts
etag.ts filters.ts groups.service.ts schemas.ts scim-admin.controller.ts scim-admin.service.ts scim-auth.guard.ts scim-auth.service.ts scim-errors.ts scim.controller.ts scim.module.ts users.service.ts
search/4
__tests__/1search.service.test.ts
search.controller.ts search.module.ts search.service.ts
sessions/3sessions.controller.ts sessions.module.ts sessions.service.ts
share/3share.controller.ts share.module.ts share.service.ts
sso/16
__tests__/4enforcement.test.ts oidc.verify.test.ts provisioning.test.ts saml.validate.test.ts
enforcement.ts oidc.controller.ts oidc.service.ts oidc.ts provisioning.service.ts saml.controller.ts saml.ts schemas.ts sso-admin.controller.ts sso-admin.service.ts sso-secrets.ts sso.module.ts
teams/4dto.ts teams.controller.ts teams.module.ts teams.service.ts
users/4dto.ts users.controller.ts users.module.ts users.service.ts
webhooks/3webhooks.controller.ts webhooks.module.ts webhooks.service.ts
workspaces/4dto.ts workspaces.controller.ts workspaces.module.ts workspaces.service.ts
types/1xmldom.d.ts
app.module.ts constants.ts main.ts
_probe_rawbody.cjs .env.example Dockerfile package.json README.md tsconfig.json vitest.config.ts
realtime/33
src/17auth.ts comments.ts config.ts event-source.ts http-store.ts index.ts metrics.ts notifications.ts page-updated-source.ts presence.ts prisma-store.ts protocol.ts rate-limit.ts room-key.ts rooms.ts server.ts update-log.ts
test/11auth.test.ts config.test.ts event-source.test.ts http-store.test.ts metrics-merge.test.ts page-updated-source.test.ts presence.test.ts protocol.test.ts rate-limit.test.ts rooms.test.ts task-0094-builders.test.ts
.env.example Dockerfile package.json README.md tsconfig.json
web/255
src/244
app/45
__tests__/1theme-tokens.test.ts
(app)/26
w/25
[workspace]/25
ask/1page.tsx
databases/1
[id]/1page.tsx
favorites/1page.tsx
files/1page.tsx
notifications/1page.tsx
page/4
[id]/4
[block]/1page.tsx
share/1page.tsx
layout.tsx page.tsx
search/1page.tsx
settings/10
appearance/1page.tsx
automations/3
[id]/1page.tsx
new/1page.tsx
page.tsx
billing/1page.tsx
developers/1page.tsx
members/1page.tsx
permissions/1page.tsx
teams/1page.tsx
page.tsx
shared/1page.tsx
templates/1page.tsx
trash/1page.tsx
layout.tsx page.tsx
layout.tsx
(auth)/4
login/1page.tsx
register/1page.tsx
reset-password/1page.tsx
layout.tsx
api/2
metrics/1route.ts
v1/1
health/1route.ts
f/1
[slug]/1page.tsx
health/1
ready/1route.ts
org/2
[orgId]/2
admin/2
integrations/1page.tsx
page.tsx
share/1
[id]/1page.tsx
error.tsx global-error.tsx globals.css layout.tsx not-found.tsx page.tsx providers.tsx
components/118
admin/10
org-admin/8ApiKeysTab.tsx AuditTab.tsx MembersTab.tsx OverviewTab.tsx PoliciesTab.tsx RetentionTab.tsx WebhooksTab.tsx WorkspacePicker.tsx
ScimCard.tsx SsoTabs.tsx
ai/6ai-panel.tsx ask-ai-submenu.tsx db-ai-dialog.tsx generate-page-dialog.tsx writing-actions-menu.tsx writing-diff.tsx
automations/5action-editor.tsx condition-editor.tsx lib.ts run-history.tsx trigger-editor.tsx
collaboration/6CursorLayer.tsx OfflineBanner.tsx PresenceBar.tsx README.md ReviewFailedDialog.tsx StatusChip.tsx
comments/3comment-panel.tsx comment-thread.tsx mention-input.tsx
database/27
__tests__/2cell-editor.test.tsx cell-renderer.test.tsx
AiExtractDialog.tsx AiFormulaDialog.tsx AiGenerateDialog.tsx board-view.tsx buttons-panel.tsx calendar-view.tsx cell-editor.tsx cell-renderer.tsx chart-view.tsx database-page-view.tsx database-toolbar.tsx database-view-renderer.tsx db-embedded.tsx filter-builder.tsx form-builder-view.tsx formula-editor.tsx gallery-view.tsx group-builder.tsx list-view.tsx property-editor.tsx record-header.tsx record-panel.tsx sort-builder.tsx table-view.tsx timeline-view.tsx
dialogs/7
__tests__/1move-to-dialog.test.tsx
create-dialog.tsx dialog.tsx file-permissions-dialog.tsx invite-dialog.tsx move-to-dialog.tsx share-dialog.tsx
editor/19
__tests__/3code-block-text.test.ts code-block.test.tsx expandable.test.tsx
block-card.tsx block-renderer.tsx block-save-queue.ts block-toolbar.tsx context-menu.tsx expandable.tsx history-popover.tsx inline-editor.tsx insert-dialog.tsx page-canvas.tsx page-spinner.tsx page-view.tsx selection-toolbar.tsx slash-menu.tsx template-dialog.tsx type-menu.tsx
files/4FileBlock.tsx FileBrowser.tsx FilePicker.tsx PreviewModal.tsx
form/4form-field.tsx form-renderer.tsx form-responses-view.tsx form-view.tsx
layout/8
__tests__/1page-tree-icon.test.tsx
command-palette.tsx factory-logo.tsx page-tree-node.tsx sidebar.tsx theme-provider.tsx topbar.tsx workspace-shell.tsx
mentions/3
__tests__/1mention-model.test.ts
mention-model.ts MentionPopup.tsx
search/3ask-ai-search.tsx results-list.tsx search-dialog.tsx
settings/10
api-keys/4ApiKeyCreateDialog.tsx ApiKeyRevealDialog.tsx ApiKeyTable.tsx ScopesBadge.tsx
webhooks/5CurlExamples.tsx DeliveryInspector.tsx EventExplorer.tsx OverviewTab.tsx WebhooksTab.tsx
settings-shell.tsx
ui/3confirm-dialog.tsx form.tsx toaster.tsx
hooks/21
__tests__/4realtime-frame-handler.test.ts use-blocks.test.ts use-database-options.test.ts use-workspace.test.tsx
realtime-frame-handler.ts use-ai-stream.ts use-auth.ts use-blocks.ts use-database.ts use-db-ai-stream.ts use-forms.ts use-members.ts use-mention-sources.ts use-notifications.ts use-offline-status.ts use-pages.ts use-presence-announcer.ts use-presence-toasts.ts use-presence.ts use-realtime.ts use-workspace.ts
lib/50
__tests__/21ai-database.test.ts ai-generate.test.ts ai-sse.test.ts ai-writing.test.ts api-keys.test.ts api-members.test.ts auth-refresh.test.ts auth-store.test.ts batch-queue-drop.test.ts block-content.test.ts clipboard-import.test.ts code-highlight.test.ts offline-status.test.ts presence-announcer.test.ts presence-color.test.ts presence-cursor.test.ts presence-emitter.test.ts realtime-url.test.ts search.test.ts utils.test.ts writing-diff.test.ts
files/7
__tests__/2picker.test.ts upload.test.ts
api.ts picker.ts upload-store.ts upload.ts use-file-uploads.ts
ai-database.ts ai.ts api-keys.ts api.ts auth-store.ts batch-queue.ts block-content.ts clipboard-import.ts code-highlight.ts constants.ts offline-status.ts org-admin.ts presence-announcer.ts presence-color.ts presence-cursor.ts presence-editor-views.ts presence-emitter.ts query-client.ts realtime.ts search.ts types.ts utils.ts
stores/8
__tests__/1block-patch-store.test.ts
block-patch-store.ts comment-pill-store.ts database-store.ts editor-store.ts offlineQueue.ts presence-store.ts ui-store.ts
instrumentation.ts middleware.ts
.env.example Dockerfile next-env.d.ts next.config.mjs package.json postcss.config.mjs README.md tailwind.config.ts tsconfig.json tsconfig.tsbuildinfo vitest.config.ts
worker/92
src/87
__tests__/13ai-embedding.test.ts ai-request.test.ts ai-usage-budget.test.ts audit-export.test.ts email-decision.test.ts encrypt.test.ts health-search-index-lag.test.ts mime.test.ts retention.test.ts s3-sign.test.ts search-index.test.ts smtp.test.ts webhook-sign.test.ts
health/1server.ts
lib/12
__tests__/1clamav.test.ts
clamav.ts encrypt.ts job-otel.ts mime.ts notify.ts prisma.ts redis-stream.ts redis.ts s3.ts sharp.ts smtp.ts
processors/56
automations/28
__tests__/3condition-evaluator.spec.ts executors.spec.ts run-pipeline.spec.ts
executors/17assign-user.ts call-integration.ts change-status.ts classify.ts create-notification.ts create-page.ts email-cap.ts helpers.ts integration-registry.ts permission.ts record-write.ts registry.ts run-ai-action.ts send-email.ts send-webhook.ts types.ts update-record.ts
condition-evaluator.ts dead-letter.ts log.ts matcher.ts pipeline.ts run-state.ts schedule.ts watchdog.ts
files/15
__tests__/4errors.test.ts scan-gate.test.ts status.test.ts temp.test.ts
steps/4finalize.ts metadata.ts scan.ts thumbnails.ts
cleanup.ts errors.ts generateThumbs.ts processor.ts status.ts temp.ts types.ts
search-index/4builders.ts grants.ts index.ts processor.ts
ai-embedding.ts ai.processor.ts audit-export.ts automations.processor.ts email-decision.processor.ts email.processor.ts maintenance.processor.ts retention.ts webhooks.processor.ts
config.ts events.ts index.ts logger.ts queues.ts
.env.example Dockerfile package.json README.md tsconfig.json
docs/73
api/17ai.md authentication.md automations.md blocks.md comments.md databases.md files.md forms.md openapi.yaml org-admin.md pages.md scim.md search.md sso.md versioning.md webhooks.md workspaces.md
architecture/10ai-architecture.md automation.md backend-architecture.md database-architecture.md frontend-architecture.md realtime-architecture.md search-architecture.md security-architecture.md storage.md system-architecture.md
database/2database-architecture.md redis-keys.md
development/8admin-guide.md coding-standards.md deployment.md development-guide.md files-pipeline.md perf-report.md performance.md testing.md
product/4functional-requirements.md non-functional-requirements.md product-requirements.md ux-requirements.md
requirements/1requirements-matrix.md
security/5audit-report.md files.md forms.md license-allowlist.md security-architecture.md
tasks/16phase-00-foundation.md phase-01-authentication.md phase-02-workspace.md phase-03-pages.md phase-04-editor.md phase-05-databases.md phase-06-relations.md phase-07-collaboration.md phase-08-search.md phase-09-files.md phase-10-forms.md phase-11-automation.md phase-12-ai.md phase-13-api.md phase-14-enterprise.md phase-15-production.md
ui/9ai.md automations.md design-system.md developers.md files.md forms.md org-admin.md scim.md sso.md
support-matrix.md
infrastructure/71
caddy/1Caddyfile
docker/6
init/101-extensions.sql
opensearch/1init-indices.json
.env.docker docker-compose.yml opensearch-init.sh README.md
kubernetes/40
observability/20
alertmanager/1alertmanager.yaml
grafana/8
dashboards/6ai.json api-overview.json infra.json realtime.json search.json workers-queues.json
provisioning/2
dashboards/1dashboards.yaml
datasources/1datasource.yaml
prometheus/3alerts.yaml recording-rules.yaml scrape.yaml
scripts/1sync-sources.mjs
alertmanager.yaml grafana.yaml kustomization.yaml namespace.yaml node-exporter.yaml otel-collector.yaml prometheus.yaml
postgres/7configmap.yaml cronjob-backup.yaml cronjob-opensearch-snapshot.yaml pgbouncer.yaml replica.yaml statefulset.yaml walg-iam-policy.yaml
api.yaml configmap.yaml ingress.yaml kustomization.yaml namespace.yaml networkpolicy.yaml pdb.yaml README.md realtime.yaml redis.yaml secret.example.yaml web.yaml worker.yaml
nginx/2nginx.conf README.md
observability/14
alertmanager/1alertmanager.yaml
grafana/8
dashboards/6ai.json api-overview.json infra.json realtime.json search.json workers-queues.json
provisioning/2
dashboards/1dashboards.yaml
datasources/1datasource.yaml
prometheus/3alerts.yaml recording-rules.yaml scrape.yaml
scripts/1dashboard-completeness.mjs
README.md
terraform/8compute.tf data.tf main.tf network.tf outputs.tf README.md security.tf variables.tf
packages/289
ai/24
src/20
providers/7anthropic.ts google.ts http.ts index.ts openai-compatible.ts rag.test.ts writing.test.ts
schemas/2database.ts generate.ts
adapters.test.ts config.test.ts config.ts gateway.test.ts gateway.ts index.ts tools.test.ts tools.ts types.ts usage.test.ts usage.ts
package.json README.md tsconfig.json vitest.config.ts
auth/11
src/7index.ts passwords.test.ts passwords.ts sessions.test.ts sessions.ts tokens.test.ts tokens.ts
package.json README.md tsconfig.json vitest.config.ts
collaboration/17
src/13awareness.ts codec.test.ts codec.ts index.ts lww.test.ts lww.ts mapping.ts offline.ts outbox.test.ts outbox.ts provider.test.ts provider.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
config/7
src/4eslint.base.js index.ts prettier.base.json tsconfig.base.json
package.json README.md tsconfig.json
database-client/5
src/2index.test.ts index.ts
package.json README.md tsconfig.json
database-engine/33
src/29
filter/2filter.test.ts index.ts
formula/8evaluator.ts formula.test.ts functions.ts index.ts lexer.ts parser.ts types.ts validate.ts
query/6group.ts index.ts paginate.ts pivot.ts query.test.ts sort.ts
relations/2index.ts relations.test.ts
rollups/2index.ts rollups.test.ts
values/5dates.ts index.ts registry.ts types.ts values.test.ts
views/3buttons.ts index.ts views.test.ts
index.ts
package.json README.md tsconfig.json vitest.config.ts
editor/25
src/21
marks/1highlight.ts
nodes/2equation.ts mention.ts
autosave.test.ts autosave.ts clipboard.test.ts clipboard.ts codec.test.ts codec.ts context-menu.test.ts context-menu.ts diff.test.ts diff.ts factory.ts history.test.ts history.ts index.ts positions.ts slash.test.ts slash.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
observability/20
src/16
__tests__/8db-redis-metrics.test.ts metrics.test.ts otel.test.ts pino-otel.test.ts prometheus.test.ts resource.test.ts scrubber.test.ts traceparent.test.ts
index.ts metrics.ts otel.ts pino-otel.ts prometheus.ts resource.ts scrubber.ts traceparent.ts
package.json README.md tsconfig.json vitest.config.ts
permissions/11
src/7index.ts levels.test.ts levels.ts resolve.test.ts resolve.ts rules.test.ts rules.ts
package.json README.md tsconfig.json vitest.config.ts
search/22
src/18chunking.test.ts chunking.ts client.test.ts client.ts embeddings.test.ts embeddings.ts hybrid.test.ts hybrid.ts index.ts indexer.test.ts indexer.ts mapping.test.ts mapping.ts queries.test.ts queries.ts rrf.test.ts rrf.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
shared/61
src/57
dto/17ai.dto.ts auth.dto.test.ts auth.dto.ts block.dto.ts browser-safety.test.ts comment.dto.ts database.dto.test.ts database.dto.ts file.dto.ts formula-ast.ts index.ts notification.dto.ts page.dto.ts search.dto.ts share.dto.ts webhook.dto.ts workspace.dto.ts
events/8audit-catalog.test.ts audit-catalog.ts audit-csv.test.ts audit-csv.ts audit-scope.test.ts audit-scope.ts catalog.test.ts catalog.ts
realtime/6comment-events.ts frames.test.ts frames.ts notification-policy.ts notification-prefs.ts task-0094.test.ts
automation.test.ts automation.ts constants.ts crypto.test.ts crypto.ts env.ts errors.ts events.test.ts events.ts forms.test.ts forms.ts index.ts markdown.test.ts markdown.ts mentions.test.ts mentions.ts net.test.ts net.ts positions.test.ts positions.ts presence.test.ts presence.ts security-headers.ts webhook-delivery.test.ts webhook-delivery.ts webhook-events.ts
package.json README.md tsconfig.json vitest.config.ts
storage/16
src/12client.test.ts client.ts index.ts keys.test.ts keys.ts mime.test.ts mime.ts presign.test.ts presign.ts thumbnails.test.ts thumbnails.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
ui/37
src/33
components/19avatar.tsx badge.tsx button.tsx checkbox.tsx command.tsx context-menu.tsx dialog.tsx dropdown-menu.tsx empty-state.tsx index.ts popover.tsx separator.tsx skeleton.tsx slider.tsx spinner.tsx switch.tsx tabs.tsx toast.tsx tooltip.tsx
color.ts command-filter.test.ts command-filter.ts focus.test.ts focus.ts formatters.test.ts formatters.ts index.ts theme.test.ts theme.ts toast-store.test.ts toast-store.ts tokens.test.ts tokens.ts
package.json README.md tsconfig.json vitest.config.ts
prisma/16
migrations/13
20260927000000_init/1migration.sql
20260928000000_search_documents_content_hash/1migration.sql
20260929000000_workspace_usage_storage_bytes/1migration.sql
20260930000000_form_submitted_notification_type/1migration.sql
20261001000000_ai_phase12/1migration.sql
20261002000000_webhooks_secret_encrypted_dlq/1migration.sql
20261002111436_webhook_delivery_latency/1migration.sql
20261002120000_org_admin_2fa_retention_unique/1migration.sql
20261002215124_audit_export/1migration.sql
20261003210000_perf_indexes_hot_paths/1migration.sql
20261004004500_perf_indexes_hot_paths_2/1migration.sql
migration_lock.toml README.md
schema.prisma seed.ts verify.ts
scripts/7backup-health.ts docs-completeness.mjs generate-openapi.mjs minio-init.ts opensearch-reindex.ts storage-orphans.ts verify-clean-clone.sh
tests/169
e2e/63
.auth/1user.json
fixtures/7
files/6clip.mp4 clip.wav clip.webm hero-1400.png note.docx note.txt
realtime.ts
mocks/2oidc-issuer.ts saml-idp.ts
playwright-report/3
data/2a6557bae9c2714a658968d4eb8534195c08ebda8.png e3092d07d00a16063e46385c02a559a9054fa847.webm
index.html
reports/1files-security.json
specs/34ai-database.spec.ts ai-generate.spec.ts ai-rag.spec.ts ai-security.spec.ts ai-writing.spec.ts ai.spec.ts api-keys.spec.ts appearance-theme.spec.ts auth.spec.ts automation.spec.ts collab-two-tab.spec.ts comments.spec.ts databases.spec.ts default-workspace-alias.spec.ts developers.spec.ts editor.spec.ts files.spec.ts final-flow.spec.ts forms.spec.ts oauth.spec.ts org-admin.spec.ts pages.spec.ts performance.spec.ts presence-cursors.spec.ts qa-paste-toggle.spec.ts realtime.spec.ts scim.spec.ts search-index-lag.spec.ts search.spec.ts sharing.spec.ts sso.spec.ts task-0094-comments-notifications.spec.ts webhooks.spec.ts workspace.spec.ts
test-results-final-flow/3
final-flow-Release-v1-0-fi-6c06b-w-state-carries-sequential--chromium-desktop/2test-failed-1.png trace.zip
.last-run.json
utils/4api.ts dnd.ts search-lag.ts wait.ts
.gitignore fixtures.ts global-setup.ts package.json playwright.config.ts playwright.final-flow.config.ts README.md tsconfig.json
fixtures/1
files/1make-sparse.ts
integration/60
ai-capability/2ai-app.capability.test.ts gateway.capability.test.ts
drill/2seed-drill.mjs verify-restore.mjs
fixtures/5
files/5clip.mp4 clip.wav doc.pdf hero-1400.png note.txt
helpers/3api.ts containers.ts seed.ts
realtime/11control-channel.test.ts convergence.test.ts flush-rebase-http.test.ts handshake.test.ts heartbeat-limits.test.ts lifecycle.test.ts presence.test.ts rebase.test.ts resume.test.ts rt-helpers.ts task-0094-fanout.test.ts
ai-chat.integration.test.ts ai-database.integration.test.ts ai-generate.integration.test.ts ai-rag.integration.test.ts ai-writing.integration.test.ts api-keys.integration.test.ts audit-completeness.integration.test.ts audit-export-s3.integration.test.ts auth.integration.test.ts automation-pipeline.integration.test.ts automations.integration.test.ts blocks.integration.test.ts cache-invalidation.integration.test.ts comments.integration.test.ts databases.integration.test.ts files-lifecycle.integration.test.ts files-pipeline.integration.test.ts files-security.integration.test.ts files.integration.test.ts forms-responses.integration.test.ts forms.integration.test.ts oauth.integration.test.ts openapi.contract.test.ts org-admin.integration.test.ts package.json pages.integration.test.ts permissions.integration.test.ts README.md replica-routing.integration.test.ts retention-engine.integration.test.ts scim.integration.test.ts search.integration.test.ts sso.integration.test.ts tsconfig.json vitest.config.ts webhooks.integration.test.ts workspace.integration.test.ts
performance/28
fixtures/1search-100k.ts
helpers/2seed-100k.mjs seed-1k-load.mjs
reports/7api-load-smoke.json database-load-smoke.json editor-local.json realtime-load.json search-full.json search-last.json search-reduced.json
api-load.js api.js archive.tar database-load.js database.js editor-local.mjs extract-100k-env.mjs package.json README.md realtime-1k.js realtime-load.js realtime.js run-realtime-load.sh search-search.k6.js search-summary.json seed-100k.mjs threshold-breach-demo.js verify-zero-op-loss.mjs
security/16
helpers/1app.ts
audit-report.test.ts auth-paths.test.ts deps.test.ts formula-fuzz.test.ts gitleaks-config.test.ts headers.test.ts images.test.ts oracle.ts package.json permission-fuzz.test.ts rate-limits.test.ts README.md tsconfig.json upload-abuse.test.ts vitest.config.ts
README.md
.dockerignore .env .env.bak-task0125 .env.example .eslintrc.json .gitignore .npmrc .prettierignore .prettierrc.json AGENTS.md ARCHITECTURE.md CHANGELOG.md CONTRIBUTING.md gitleaks.toml LICENSE package.json pnpm-lock.yaml pnpm-workspace.yaml PRODUCT-SPEC.md README.md ROADMAP.md SECURITY.md tsconfig.json turbo.json
realtime-architecture.mddocs/architecture/realtime-architecture.md← architecture33,529 B← Back to the app

Realtime Architecture (apps/realtime)

Node 20 + ws + Yjs. Sibling docs: system · frontend · database · security.

Position of Yjs in the truth hierarchy (ADR-003): Yjs is the source of truth while a page/database is open for editing; the persisted blocks table is the source of truth at rest. This document defines the wire protocol, the document model, persistence, presence, offline behavior, the latency budget, scaling, and failure modes.


1. Process & Room Model

apps/realtime (one node type, N replicas)
├── ws server            port 4001, wss://host/realtime (nginx upgrades /realtime → :4001)
├── RoomManager
│   ├── rooms: Map<roomKey, Room>            # in-memory, per node
│   ├── roomKey = sha256(workspaceId + ':' + resourceType + ':' + resourceId) % 32bit
│   └── ownership: node owns the rooms whose hash % instanceCount == nodeIndex
│        (consistent-hash ring; edge routes by room key, §6)
├── Room (one per open page/database)
│   ├── ydoc: Y.Doc                          # the editable state (blocks + meta)
│   ├── awarenessProvider: Yjs awareness     # presence (ephemeral)
│   ├── peers: Map<connId, Peer>             # sockets in this room
│   ├── persistTimer                         # debounced flush state
│   └── updateLog: RingBuffer<YjsUpdate>     # resume log, until acked-persisted
├── Hydrator            # Postgres → Y.Doc (blocks table → codec)
├── Persister           # Y.Doc → blocks table (debounced, batched upserts)
├── EventBus link       # 'page.updated' / 'block.updated' on persist; 'comment.created' push
└── AuthBridge          # verifies JWTs (packages/auth, shared secret) + permission checks

Room resources:

Room key suffix Resource What syncs
page:{pageId} a page (or record page) block tree (Yjs doc), presence, cursors
database:{databaseId} a database (home view) property schema + view config + record position/order ops (the record values are still REST — see §4.4)
comment:{pageId} a page's comment panel comment thread state (lightweight, non-CRDT message fan-out)

A page room and its database home room coexist when a DATABASE block is open: the database block inside a page renders the database's view, and edits to rows are REST mutations broadcast to both rooms' subscribers (the row grid is not CRDT-edited; the block tree that embeds it is).

1.1 Why one authoritative doc per resource

Yjs docs are per-connection merged, but ordering and persistence need a single writer per resource: the room's doc receives every update, so (1) persistence is serialized (one debounced flusher), (2) the updateLog for resume is consistent, and (3) two nodes never both persist the same page (which would double-emit page.updated). Room sharding (§6) guarantees exactly one owning node per room.


2. WebSocket Protocol

2.1 Connection & auth handshake

Client                                            Server
  │  GET /realtime?token=<JWT>&room=page:abc
  │      (Upgrade: websocket, Origin, Sec-WebSocket-Protocol: factory.v1,
  │       X-Realtime-Room: page:abc)                        │
  ├────────────────────────────────────────────────────────▶│
  │                          1. rate limit: 30 conns/min/IP, 100 conns/user
  │                          2. verify JWT (access token, 15 min TTL, HS256,
  │                             secret shared with api via env; issuer=factory)
  │                             → { userId, workspaceId claim }
  │                          3. permission check (packages/permissions, same engine
  │                             as the API guard):
  │                             page room  → level ≥ VIEW to join; EDIT to send doc updates
  │                             database   → same
  │                             (VIEW-only peers receive doc updates, cannot send)
  │                          4. join room (hydrate if needed — see §4.2)
  │◀────────────────────────────────────────────────────────┤
  │  101 Switching Protocols (Sec-WebSocket-Protocol: factory.v1, room:page:abc)
  │
  │◀── {"t":"hello","roomId":"page:abc","self":{"userId":"u_1","color":"#e91e63"},
  │     "docStateVector":base64,…,"awareness":[…existing peers…]}
  │
  │── {"t":"sync.request","stateVector":base64}        # client's Yjs state vector
  │◀── {"t":"sync.state","updates":[base64…]}          # doc delta to reach server state
  │     (+ full doc if the client is new: updates = encoded doc, size < 2 MB,
  │      else paged "sync.state" messages with `seq`)
  │
  │── {"t":"sync.ack"}                                 # client applied; room now active

Rules:

2.2 Message types (wire format: single-line JSON, Yjs updates as base64)

t Dir Schema Purpose
hello S→C {roomId, self:{userId,color}, awareness[]} post-upgrade greeting; server-side presence seed
sync.request C→S {stateVector: base64} "give me what I'm missing" (Yjs state-vector protocol)
sync.state S→C {updates: base64[], seq?: int} delta (or paged full doc)
sync.ack C→S {} client applied; client is now in the doc
update C→S {doc: 'blocks'|'meta', update: base64} Yjs update from the client (only EDIT-capable peers)
update.ack S→C {clock: number} server applied + broadcast; clock = server doc's Yjs clock (used by the offline queue)
presence.set both {state: AwarenessPayload} full awareness state for self (join, field change)
presence.patch both {state: AwarenessPayload, keys: string[]} partial awareness update (cursor moved — the hot path)
presence.drop S→C {userId} peer left / pruned
cursor.move C→S {blockId, offset, selection?: {fromBlockId,fromOffset,toBlockId,toOffset}} sugar over awareness for the cursor (debounced 50 ms on client)
comments.event S→C {kind: 'created'|'replied'|'resolved'|'reaction', payload} realtime comment fan-out (REST writes, socket pushes)
notification.push S→C {notification: NotificationDto} in-app notification without a page open (user-level socket room=user:{userId})
record.updated S→C {databaseId, recordIds[], changedProperties[]} row grid refresh hint (REST mutations broadcast to database room)
doc.persisted S→C {pageId, version} persist flush completed (client can clear "saving" UI early)
ping / pong C↔S {} keepalive: server pings every 25 s; client must pong within 25 s (then close 4000)
error S→C {code: 'RATE_LIMITED'|'UNAUTHORIZED'|'FORBIDDEN'|'TOO_LARGE'|'INTERNAL', message} non-fatal; connection continues unless code is auth-related
bye S→C {code, message} pre-close notice (server drain, room moved) — code 4001 "reconnect, room may have moved"

Yjs-specific messages use the standard Yjs sync protocol (state vectors + update messages) wrapped in our envelope; the base64 payloads are raw Y.encodeStateAsUpdate buffers. Max single-message size: 256 KB (a 256 KB update ≈ a few thousand block ops); clients chunk larger local edits (the offline queue chunks at 128 KB), and the server closes 4008 on oversize after an error TOO_LARGE.

2.3 The per-user control socket

Independent of page rooms, each authenticated user may hold one control socket (room=user:{userId}) that receives notification.push, comments.event (for pages the user has open but isn't editing), and record.updated for databases they've subscribed to ({"t":"subscribe","room":"database:abc"} over the control socket). The browser opens the control socket at app start (not per page), so notifications arrive with zero additional connections.


3. Connection Lifecycle

connect ─▶ auth ─▶ join room ─▶ hydrate (if cold) ─▶ sync handshake ─▶ ACTIVE
  │
  │  heartbeat: server ping 25 s / client pong 25 s
  │  awareness: peer joined → presence.set broadcast (≤ 1 frame per 100 ms per peer)
  │  updates:   update → apply → broadcast (fan-out, coalesced per event-loop tick)
  │  persist:   debounced flush (1 s quiet / 10 s max dirty) → 'doc.persisted'
  │
  ├── disconnect (clean): presence.drop, room may persist-final, release slot
  ├── idle: room with 0 peers → persist-final → doc evicted after 5 min LRU
  │         (updateLog cleared after successful persist)
  ├── socket dropped (network): client auto-reconnects (exponential backoff
  │   1 s → 2 s → 4 s … 30 s cap, full jitter); on reconnect it re-runs the sync
  │   handshake with its state vector — the server answers from the in-memory doc
  │   (hot room) or the updateLog (warm room) or a fresh hydrate (cold room)
  └── server drain (SIGTERM / room move): send `bye {code 4001}` → stop accepting
        updates → persist-final → close 1001. Client reconnects; room hash may now
        map to a different node → edge routes there (§6.2).

Presence pruning: the server heartbeats awareness (Yjs awareness does this via updateTimeout = 30 s). A peer with no presence.patch/ping for 60 s is pruned: its cursor disappears, presence.drop is broadcast. Pruning is per-room; the user's other rooms are unaffected.


4. Yjs Integration

4.1 Document model

One Y.Doc per room, two top-level types (see frontend, §3.4 for the codec that bridges to Block JSON):

Y.Doc (page room)
├── yXml   Y.XmlFragment   # ProseMirror document via y-prosemirror
│     # every block = one item; text = y.Text; attrs: {id, type, position}
└── yMeta  Y.Map           # keyed by block id:
      # {content: Json, properties: Json, position: string}
      # position lives in yMeta (NOT yXml order) so that:
      #   • reordering is a meta update (one small CRDT op, no tree rewrite)
      #   • persist ordering is deterministic (sort by position, not array order)

Y.Doc (database room)
├── ySchema  Y.Map         # property list + order (position), view list + config
│     # property add/rename/reorder = CRDT-merged (two users adding columns don't lose one)
└── yRows    Y.Map         # recordId → {position}
      # row creation/deletion/reorder = CRDT; property VALUES remain REST (below)

Awareness (yAwareness) is per-room and ephemeral — never persisted, never sent to Postgres; it survives only for the room's lifetime in memory.

4.2 Server hydration (Postgres → Y.Doc)

Cold room (first peer after eviction, or node restart):

1. SELECT id, parent_id, type, position, content, properties
   FROM blocks WHERE page_id = ? AND deleted_at IS NULL
   ORDER BY position                       -- one range-indexed scan
2. codec.decode(blocks) → yXml + yMeta     -- deterministic: sort by position,
                                           --   parentId from the tree
3. if the doc is a record page: nothing extra (properties are record_properties,
   fetched over REST by the client, not part of the ydoc)
4. room.ydoc = hydrated doc; log hydrate_ms metric (target < 200 ms for 10k blocks)

Hydration cost is bounded by block count: 10k blocks ≈ 8 MB scan ≈ 30–80 ms on a warm Postgres; 100k blocks ≈ 600 ms (acceptable one-time cost, hidden behind the "loading page" shell — the frontend shows metadata immediately and the doc streams in via the sync handshake's paged sync.state).

Hydrate-on-write race: a peer could send update during hydration. The room buffers incoming updates until hydration completes (max 5 s), then applies them to the hydrated doc — Yjs merge is commutative, so order doesn't matter, but applying to a half-built doc would corrupt the Y.XmlFragment structure.

4.3 Update persistence (debounced)

update arrives (per room, serialized on the room's event loop)
  1. doc.transact(apply update, 'remote')
  2. if !peer.canEdit → drop + error FORBIDDEN (defense in depth: guard already checked)
  3. broadcast to other peers (coalesced: all updates within one event-loop tick
     are encoded once per peer — Yjs merges before encode)
  4. mark room dirty; updateLog.push(update)   # until persisted
  5. persistTimer: clearTimeout; setTimeout(flush, 1000)
     hard cap: if dirty > 10 s → flush immediately

flush(pageId, actorSummary):
  1. blocks = codec.encode(doc)                  -- walk yXml + yMeta, position-ordered
  2. roomTouched = diffBlockSets(lastPersistedBlocks, blocks).ids()
     # captured PRE-rebase: the blocks THIS room edited (the flush wins on these)
  3. CAS persist against pages.version (baseVersion = room.lastVersion):
       UPDATE pages SET version = baseVersion + 1
         WHERE id = pageId AND version = baseVersion        -- count must be 1
       (plus the block batch upsert + soft-delete in the same tx)
     on CONFLICT (an external REST write bumped pages.version):
       -- the 3-way rebase (normative, TASK-0092): a concurrent REST writer raced us
       BASE   = room.lastPersistedBlocks          # what we last persisted
       OURS   = blocks                            # the CRDT doc (this flush's state)
       THEIRS = the store's current blocks        # what the REST writer just wrote
       externalOps = diffBlockSets(externalBase, THEIRS)   # the REST writer's changes
       absorb into the doc ONLY externalOps whose block.id ∉ roomTouched
         (a block both sides edited → the ROOM's (OURS) version wins; the REST op is
          dropped for that block, but its other blocks are kept)
       apply the absorbed ops to the doc (Y.transact origin 'realtime-flush-rebase')
       externalBase = THEIRS; retry the CAS persist (budgeted: 2 attempts)
       a SECOND conflict (a writer landed during the retry) → the doc is already in
         sync with the latest external state; advance room.lastVersion to it, keep the
         room DIRTY, and re-arm persistTimer (converges on the next flush — no clobber)
  4. room.lastVersion = the new version
     room.lastPersistedBlocks = the persisted blocks; room.updateLog.clear()
  5. EventBus.publish('page.updated', {pageId, version, blockDelta, ops})
       -- blockDelta = {inserted, updated, moved, deleted} over the diff (R3
       --    minimality: the ops are the minimal op set — unchanged blocks emit no op)
       -- actor: 'realtime-flush' (auditable, not impersonable — the public API has
       --    no path to set that actor)
       → search indexer, rollup recompute (if a database block changed), audit-flush
  6. broadcast doc.persisted {pageId, version}

4.4 What does NOT go through Yjs (and why)

Data Path Why
Record property values REST (PATCH /databases/:id/records/:rid) + record.updated socket broadcast values are server-validated (property type, relation cardinality, computed-property protection); CRDT-merging hostile values is a security hole — the server must veto
Comments REST + comments.event push append-mostly, small, needs mention fan-out logic (worker), not CRDT
Notifications REST + notification.push on control socket per-user, not per-resource
Database property schema values (select options etc.) Yjs ySchema (merged) + debounced persist (same flusher) schema edits are collaborative in practice (two admins adding columns)
File uploads REST + file.uploaded event → search out of scope for doc state

The rule: Yjs merges structure and text; REST validates semantics.

4.5 Resume protocol (after reconnect)

client reconnects (same page, has local ydoc from memory/IndexedDB)
  1. new socket → auth → join room
  2. client sends sync.request {stateVector: localSV}
  3. server:
       hot room:    delta = ydoc since localSV → 1–few sync.state messages
       warm room:   same, but the doc was re-hydrated from Postgres after a flush —
                    still correct (Postgres had everything ≤ last flush; the client's
                    own unflushed local ops are in its local doc and get merged)
       cold room:   hydrate first, then delta
  4. client applies delta → merge is idempotent (CRDT) → sync.ack
  5. client ships any OUTBOUND ops it queued while offline (offline queue, §5) —
     these may already be in the server doc (if another tab of the same user synced
     them) — the CRDT merge discards duplicates automatically

No "document version" handshake is needed beyond state vectors: Yjs state vectors are the resume protocol. The updateLog ring buffer (last ≤ 2000 updates or 10 MB, kept per room until the next successful flush) covers the window where the client's last ack is older than the last persisted state (rare; usually the delta from the live doc suffices).


5. Presence & Cursors

5.1 Awareness payload schema

Each peer's awareness state (Yjs Awareness.setLocalStateField):

interface AwarenessPayload {
  user: { id: string; name: string; color: string };   // color assigned from a
                                                       //   20-color palette by userId hash
  cursor?: {
    blockId: string;
    offset: number;               // text offset within the block
    selection?: {                 // non-trivial selections only
      fromBlockId: string; fromOffset: number;
      toBlockId: string;   toOffset: number;
    };
  };
  editingBlockId?: string;        // block currently focused (drives the "pencil" badge)
  page?: { pageId?: string };     // when present on the database room: which record page
  lastSeen: number;               // epoch ms (server heartbeat uses this)
}

5.2 Presence guarantees

5.3 Implemented (TASK-0093, CLOSED 2026-09-27 — recorded deviations)

Presence is implemented over the (b) additive-bridge control frames, not the §2.2 binary awareness relay. Recorded conflicts (the task file docs/tasks/phase-07-collaboration.md TASK-0093 "Implementation" section is the full record):


6. Scaling

6.1 Room sharding (consistent hash)

6.2 Per-connection backpressure & limits

Limit Value Enforcement
New connections 30/min/IP, 100/user, 2000/node counter at upgrade; 4429 close
Inbound message rate 100 msg/s sustained, burst 200 token bucket per socket; exceed → error RATE_LIMITED, then close 4008 after 10 s of excess
Inbound update size 256 KB/message, 2 MB/s sustained size check + token bucket; TOO_LARGE error
Outbound queue 16 KB/socket in-flight ws onDrain-gated send: if the socket's send buffer > 16 KB, the room pauses fan-out to that peer and queues (bounded 1 MB); on overflow the peer is dropped with 4009 "slow consumer" and told to reconnect (its local doc has everything — sync on reconnect catches up)
Room size 500 peers/room (alert at 200) beyond that, joins get error ROOM_FULL (503-class); product guidance: split huge docs
Node capacity ~2–5k steady rooms (measured) HPA on ws_connections + doc_flush_duration_p99

Fan-out cost model: a keystroke from 1 of 10 peers = 9 encodes+sends of one small update. Yjs encodes per-recipient only when their state differs (state-vector check); in practice all peers receive the same bytes (batched with Buffer.concat). 100 peers × 20 updates/s = 2k small frames/s ≈ 20 MB/s — comfortable on a single node; this is why rooms, not nodes, are the unit of scaling.

6.3 Node sizing & health


7. Latency Budget (< 200 ms p95, peer-to-peer on the same node)

Segment Budget (p95) Notes
Client keystroke → Yjs op < 5 ms local, synchronous
Op → socket frame (client) < 1 ms local loopback to wss
TLS + network (same AZ) < 10 ms wss over 1 hop
Server auth/room lookup < 1 ms in-memory maps (per-socket auth cached after handshake)
Doc apply < 5 ms Yjs apply is O(update size); 10 KB update ≈ 1 ms
Broadcast encode (N peers) < 10 ms for N ≤ 20 per-recipient encode only when SV differs
Network fan-out (same AZ) < 10 ms
Peer apply + render < 50 ms ProseMirror DOM patch; heavy blocks defer
Total (1 → N, same node) < 90 ms leaves 110 ms margin to the 200 ms SLO

Cross-AZ adds ~30–80 ms (still < 200 ms with margin); cross-region is not supported (rooms are single-region by deployment). The measured SLO is enforced by a synthetic probe (two headless clients in tests/performance, histogram on update.ack RTT) in CI for every release.

Persistence latency is explicitly OUT of the 200 ms budget — it's debounced (≤ 10 s). The budget covers collaboration responsiveness, not durability.


8. Failure Modes & Mitigations

Failure Detection Behavior User impact
Node crash (OOM/kill) K8s restart in-memory docs lost; ≤ 10 s unpersisted updates or the RT_PENDING handoff if drain completed peers reconnect (backoff), re-sync from Postgres (correct state ≤ last flush); edits made in the last 10 s by crashed-node peers are lost only if their clients didn't re-send — mitigated: clients keep ops in the offline queue until update.ack arrives, so a reconnect re-sends them (CRDT merge discards dupes)
Postgres down readyz / persist error rooms keep editing in memory (doc stays authoritative); persist retries with backoff (1 s → 30 s cap); rt_persist_errors_total alerts; after 5 min of persist failure, new writes get error PERSIST_DEGRADED and clients switch to "offline mode" UI (keeps working locally) editing continues; durability window grows (documented); no data loss while Postgres is up again (flush catches up)
Redis down readyz event emission buffers in-memory (bounded 10k events, oldest dropped + metric) — search/webhooks/notifications lag; auth unaffected (JWT stateless); rate limits fail-open to per-process counters search index lag grows (SLA breach, alerted); no edit impact
Client network drop pong timeout / onclose client backoff reconnect; offline queue grows (IndexedDB); awareness peer pruned at 60 s "Offline — N changes queued" badge; on reconnect, full merge
Slow consumer (bad network) send buffer > 16 KB fan-out paused → bounded queue → drop at 1 MB with 4009 that peer reconnects; everyone else unaffected
Malformed/malicious Yjs update apply throws / size limits update dropped, error, peer flagged (3 strikes → socket closed 4008 + audit realtime.abuse + IP rate-limit bump) abuser disconnected; room unaffected (Yjs apply is all-or-nothing)
Two nodes owning one room (bug during rebalance) both persist → CAS conflict on pages.version the second persister gets CONFLICT, re-reads, re-merges (Yjs merge of its doc with the fresh Postgres state) — no user edit is lost (both docs contain both edits after merge) none observable
Full room (500 peers) join check ROOM_FULL to latecomers; page opens in read-only REST mode for them latecomer sees a "too many editors" banner, can still view
Clock skew (client) n/a no client clock is trusted for ordering (Yjs Lamport clocks + fractional positions are monotonic per actor); lastSeen is advisory only none

Crash-consistency statement: at any moment, Postgres holds a state that is a valid prefix-consistent view of the doc (≤ 10 s stale), and every client's doc is a superset merge of (Postgres state) ∪ (ops it has been acked) ∪ (its own pending ops). On any recovery, merging converges to the union of all edits — no edit is ever silently discarded; at worst, an unacked edit is replayed and deduplicated.


9. Security Notes (summary — detail in security)