.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
phase-14-enterprise.mddocs/tasks/phase-14-enterprise.md← tasks72,187 B← Back to the app

Phase 14 — Enterprise

Purpose: Task specs for Phase 14 — enterprise features: SSO (SAML 2.0 + OIDC), SCIM user/group provisioning, and the audit + org policies + data retention + org admin console cluster.

Phase exit criteria (ROADMAP.md, Phase 14):

  • SSO round-trip E2E.
  • SCIM sync tested.
  • Retention job purges per policy (integration test).
  • Audit log covers all mandatory events.

Task range: TASK-0131 … TASK-0133 (3 tasks — this agent owns exactly TASK-0131..TASK-0133; TASK-0096..0120 are owned by other agents).

Note on section references: the brief cites PRODUCT-SPEC "§36–46"; the actual PRODUCT-SPEC.md sections are §21 (AI), §22 (API/Webhooks), §23 (Audit Logging), §24 (Security Requirements), §26 (Performance Targets). This file cites the real section numbers.

Task Table

ID Title Estimate
TASK-0131 SSO: SAML 2.0 SP + OIDC (JIT provisioning, enforce mode, admin UI) L
TASK-0132 SCIM 2.0 user/group provisioning M
TASK-0133 Audit viewer, org policies, data retention, org admin console L

Phase Completion Checklist

Test Fixtures & Mocks (shared across Phase 14 tasks)

The three tasks share one fixture directory, tests/e2e/mocks/, so the E2E suites never depend on external IdPs:

Fixture Stack What it provides
tests/e2e/mocks/saml-idp.ts samlify (IdP mode) Full SAML 2.0 IdP on a local HTTP port (test-only): login page with auto-approve profile (and a manual-approve mode for the E2E), AuthnResponse builder with signature (RSA-SHA256), NameID, attribute statements (email, name); certificate generated at fixture startup (per-run keypair — values match the *_test_* gitleaks allowlist from TASK-0134); also serves IdP metadata XML so the admin "test-connection" path has a real endpoint to fetch.
tests/e2e/mocks/oidc-issuer.ts custom (node:http + node:crypto) Minimal OIDC provider: /.well-known/openid-configuration, /.well-known/jwks.json (per-run RS256 keypair), /authorize (auto-approve + a manual consent page for the E2E), /token (code + refresh grants), ID-token issuer with configurable claims; in-memory code store (single-use, 10 min) mirroring the contract under test.
tests/e2e/mocks/scim-client.ts typed fetch wrapper SCIM 2.0 client for the E2E: createUser, listUsers(filter, page), patchUser, deleteUser, createGroup, …; carries the bearer token captured from the SCIM enable response; asserts the application/scim+json content type and error envelope on every call.
tests/e2e/mocks/saml-goldens/ static XML Golden SP-metadata document + a valid AuthnResponse + the tampered variants (bad signature, expired, wrong audience, wrong destination, replayed nonce, non-success status) — one file per variant, referenced by name from the assertion-matrix test so each rejection is reproducible and reviewable.

Fixture lifecycle: each Playwright worker run boots the fixtures it needs (beforeAll), tears them down (afterAll); ports are fixed in tests/e2e/mocks/ports.ts (no collisions with the compose stack). The fixtures are contract sources: if the admin UI or the API changes a SAML/OIDC/SCIM behavior, the fixture golden is updated in the same PR and the diff is reviewed as part of the security review.


TASK-0131

Title

SSO: SAML 2.0 service provider + OIDC, JIT provisioning, enforce mode, admin UI

Objective

Let organizations log in through their identity provider: SAML 2.0 SP (metadata, ACS with full assertion validation, JIT provisioning, auto workspace membership, session) and OIDC (authorization code + PKCE, JWKS ID-token validation, claims mapping) through the same provisioning path — with an ssoEnforced org policy that blocks password login, audited logins, and an admin configuration UI with test-connection.

Background

Models exist: SsoProvider (per org: type SAML|OIDC, name, config Json, enforce Boolean), SsoIdentity (providerId, externalId, userId, unique per provider), Organization.policy Json (holds ssoEnforced), Organization.ssoConfig Json?. Phase 1 provides sessions (JWT access + rotating refresh), login/logout, and audit. Phase 13's OAuth2 module provides PKCE/JWT machinery to reuse for OIDC (claims mapping differs: SSO signs the user in — type:"sso" JWTs, not third-party grants). Admin config surface lands in the org admin console built by TASK-0133 (the SSO tabs component is built here, mounted there; if TASK-0133 lands first, the tabs are dropped into its layout — either way the route /org/[id]/admin/integrations?tab=sso renders them).

Requirements

Wire examples (canonical shapes the fixtures and tests assert against):

SP metadata (abridged, GET /saml/metadata/acme):

<md:EntityDescriptor entityID="https://factory.example.com/saml/metadata/acme">
  <md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true"
                      protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
                                 Location="https://factory.example.com/saml/acs" index="0"/>
  </md:SPSSODescriptor>
</md:EntityDescriptor>

Accepted AuthnResponse (what the samlify IdP mock POSTs to /saml/acs; the golden tests/e2e/mocks/saml-goldens/valid-authn-response.xml is the full signed document):

<samlp:Response Id="_test_resp_001" InResponseTo="_test_authnreq_001"
    Destination="https://factory.example.com/saml/acs"
    Issuer="urn:mock-idp:acme" Status="Success">
  <samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
  <saml:Assertion ID="_test_assert_001" Issuer="urn:mock-idp:acme">
    <saml:Subject>
      <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">jane@acme.test</saml:NameID>
      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData NotOnOrAfter="…+10min" InResponseTo="_test_authnreq_001"
                                      Recipient="https://factory.example.com/saml/acs"/>
      </saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Conditions NotBefore="…−1min" NotOnOrAfter="…+10min">
      <saml:AudienceRestriction><saml:Audience>https://factory.example.com/saml/metadata/acme</saml:Audience></saml:AudienceRestriction>
    </saml:Conditions>
    <saml:AttributeStatement>
      <saml:Attribute Name="mail"><saml:AttributeValue>jane@acme.test</saml:AttributeValue></saml:Attribute>
      <saml:Attribute Name="displayName"><saml:AttributeValue>Jane Doe</saml:AttributeValue></saml:Attribute>
    </saml:AttributeStatement>
  </saml:Assertion>
</samlp:Response>

OIDC authorize redirect (step 1 of the flow):

GET /sso/oidc/org_01J9…/authorize → 302
Location: https://mock-oidc.test/authorize?response_type=code
        &client_id=cli_sso_01J9…&redirect_uri=https://factory.example.com/sso/oidc/org_01J9…/callback
        &scope=openid%20email%20profile&state=<signed, Redis-bound>&nonce=<128-bit>
        &code_challenge=<S256>&code_challenge_method=S256

Token exchange (step 3):

POST /token  (mock-oidc.test; client secret from the encrypted org config)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=oc_…&redirect_uri=…&code_verifier=<original verifier>

→ 200 { "access_token": "eyJ…", "id_token": "eyJ…", "token_type": "Bearer",
        "expires_in": 900, "scope": "openid email profile" }

(The id_token is validated per the Requirements — JWKS, iss/aud/exp/nbf, nonce — then the claims-mapping + provisioning path runs; the access_token from the IdP is discarded after validation, never stored.)

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Requirement conflicts & recorded decisions (rule 29)

  1. samlify SP-mode vs per-check rejection reasons. The Backend Changes say "use samlify in SP mode for parsing/validation to avoid hand-rolled XML crypto", but the Acceptance Criteria require distinct, per-check sso.login_failed reasons (bad signature / expired / audience / destination / replay / status) — samlify's validateSamlResponse collapses all of those into one rejection and exposes no per-check hook. Decision: parse with @xmldom/xmldom, verify with xml-crypto (SignedXml, enveloped + exc-C14N, RSA-SHA256) in the exact documented check order, and use samlify only for what it does uniquely (the mock IdP's signing in tests/e2e/mocks/saml-idp.ts — SamlLib.constructSAMLSignature). The "no hand-rolled XML crypto" intent is preserved: all signature crypto is xml-crypto's.
  2. require2FA policy field has no 2FA implementation. Organization.policy includes require2FA (Phase 2 schema) but no 2FA exists in the codebase yet (later task). SSO does not gate on it; recorded so TASK-0133's policy editor doesn't advertise it as enforceable.
  3. PRODUCT-SPEC "§36–46" citations vs the real spec. The task text references product-spec sections that don't exist under those numbers (the SSO/SCIM/audit requirements actually live in the real §23/§24/§26 enterprise sections). Implemented against the real sections; the task file's citations left as-is (historical).
  4. @xmldom/xmldom ships no types and pollutes the global DOM namespace. Added apps/api/src/types/xmldom.d.ts (shim for the two classes we use) + a tsconfig paths pin. @xmldom/xmldom and xml-crypto were samlify's transitive deps and are now direct deps of apps/api (justified per rule 17: they ARE the validation engine, per conflict #1).
  5. SSO protocol routes outside api/v1 vs the reference nginx's /api/-only proxying. The protocol surface is browser binding (IdP form-POST + browser GETs), so it must be reachable without the api/v1 prefix. Decision: main.ts excludes /saml/* + /sso/* from the global prefix, AuthGuard whitelists them, and the reference nginx carries location /saml/ + location /sso/ → api. Two-origin deployments additionally require SSO_ORIGIN (the API's public origin) — documented in docs/development/deployment.md §10 + .env.example. Single-origin deployments need nothing (WEB_ORIGIN serves both).
  6. No API endpoint sets Workspace.organizationId. The workspace create schema is {name, slug?, description} (.strict()) and no org↔workspace route exists, yet the DoD requires "auto-join all non-personal workspaces of the org". Decision (interim): the E2E seeds the link via direct Prisma (tests/e2e/specs/sso.spec.ts, setupOrg). Owner: TASK-0133 — the org admin console should add the "attach workspace to org" route and own the invariant; SSO provisioning is correct-by-construction against Workspace.organizationId.
  7. JIT emails are invisible to anonymous login-page discovery. GET /auth/sso/options resolves by existing membership (minimal-info anti-enumeration, per the Security section), so a brand-new JIT email gets {orgs: []} and no "Sign in with SSO" button on the login page. Decision: the entry path for first-time SSO users is the org-provided link <SSO_ORIGIN>/sso/start/<slug> (public dispatcher — it only redirects to the org's own IdP). Documented in docs/api/sso.md + docs/ui/sso.md §1; NOT a defect.
  8. Enforce lockout recovery has no "elevated service credential". The Security section presumes "admin recovers via API with an elevated service credential — documented runbook". No service-account concept exists in the schema or auth module (searched; none). Decision: the SSO path is never blocked by enforcement, and the enforcement state is plain data — recovery = (a) sign in via SSO once the IdP is reachable, or (b) flip enforce off with a pre-enforcement session. Runbook: docs/development/deployment.md §10.4; tested: sso.spec.ts "enforce mode…" (owner 403 → SSO path open → un-enforce → password login again).

TASK-0132

Title

SCIM 2.0 user/group provisioning

Objective

Provision users and groups from an identity system via SCIM 2.0: POST/GET/PATCH/DELETE /scim/v2/Users and /scim/v2/Groups with org bearer-token auth, create → User + workspace memberships, groups → teams, deactivation = disable (not delete), standard filters and pagination, audited, with a full lifecycle E2E (mock SCIM client creates a user who can log in, deactivation blocks login).

Background

Organization.scimConfig Json? holds the SCIM setup: {bearerTokenHash (SHA-256 of the raw scim_… token, raw shown once at enable), provisioningBase? (unused, we are the target), autoJoinOrgWorkspaces: true (default), defaultRole: "MEMBER"}. User.status enum (ACTIVE, SUSPENDED, …) exists (Phase 1/2); teams exist (Team, TeamMember). The modular monolith gets apps/api/src/modules/scim/. SCIM 2.0 (RFC 7643/7644) subset: Users schema (userName, name, emails, active, metadata), Groups schema (displayName, members), filters userName eq "…" / active eq true|false (and combined with and), pagination via startIndex/count (SCIM ListResponse totalResults/startIndex/itemsPerPage), ETag support (If-Match on PATCH/DELETE → 412 on mismatch), standard SCIM errors (400 invalidFilter, 409 duplicate, 403 forbidden, 401 invalidCredentials).

Requirements

Wire examples (canonical SCIM shapes):

Create user (IdP → us):

POST /scim/v2/Users
Authorization: Bearer scim_live_01J9…
Content-Type: application/scim+json

{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "jane.doe", "name": { "givenName": "Jane", "familyName": "Doe" },
  "emails": [{ "value": "jane@acme.test", "primary": true }],
  "password": "initial-rotated-by-idp", "active": true }

→ 201  Location: /scim/v2/Users/usr_01J9…
  { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "usr_01J9…",
    "userName": "jane@acme.test", "active": true,
    "meta": { "resourceType": "User", "created": "…", "location": "…",
              "version": "W/\"etag-a1b2\"" } }

(No @ in userName + no emails[0] → mapped to jane.doe@<userEmailDomain>.)

Deactivate (replace active):

PATCH /scim/v2/Users/usr_01J9…
If-Match: W/"etag-a1b2"
Content-Type: application/scim+json

{ "schemas": ["urn:ietf:params:scim:patch:2.0"],
  "Operations": [{ "op": "replace", "path": "active", "value": "false" }] }

→ 200 (User.status → SUSPENDED; login + in-flight sessions blocked; membership kept)

List with filter + pagination:

GET /scim/v2/Users?filter=active eq false&startIndex=1&count=50

→ 200 { "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
        "totalResults": 7, "startIndex": 1, "itemsPerPage": 50,
        "Resources": [ … ] }

SCIM error envelope (every non-2xx):

{ "schemas": ["urn:ietf:params:scim:api:errors:2.0"],
  "status": "409", "detail": "A User with userName 'jane@acme.test' already exists." }

(status is the HTTP status as a string per RFC 7644; codes used: 400 invalidFilter/invalidSyntax, 401 invalidCredentials, 403 forbidden, 404, 409 duplicate, 412 on ETag mismatch.)

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Requirement conflicts & recorded decisions (rule 29)

  1. The in-flight SUSPENDED check was missing (prerequisite fix). The task says "confirm SUSPENDED blocks login and in-flight sessions — if the in-flight check is missing, add it to the JWT middleware status validation." It WAS missing: login/refresh blocked suspended users (Phase 1) but a previously-issued access token kept working until it expired. Decision: AuthGuard.resolveJwt now re-checks User.status per request (point PK query, fail-open on DB error) for user-JWTs; a suspended user gets 403 { reason: "account_suspended" } and the block is audited auth.suspended_blocked (user-scoped, best-effort). Placed after the OAuth 401, before both the OAuth and user-JWT returns. Tested in the SCIM integration (2g) and E2E lifecycle (in-flight page.request call).
  2. application/scim+json needs a dedicated body-parser. IdPs send Content-Type: application/scim+json (RFC 7644), but body-parser's default json type matches ONLY application/json — so SCIM bodies arrived unparsed (req.body undefined) and every create 400'd. Decision: mount a second json parser in main.ts + the integration helper with type: "application/scim+json" (a second json parser only parses its own content type; the default one is untouched). The SCIM controller also accepts application/json (tolerant) and always responds application/scim+json.
  3. The SCIM exceptions filter must handle ApiError, not just HttpException. The shared ZodValidate pipe throws ApiError (which extends plain Error, not HttpException), so an invalid SCIM body hit the filter's 500 fallthrough instead of a 400. Decision: ScimExceptionsFilter matches ApiError BEFORE HttpException and maps VALIDATION_ERROR → 400 invalidValue, NOT_FOUND → 404, others by their status.
  4. The shared SCIM Operations[].value union is ambiguous → unknown. emails (an array of Email objects) and members (an array of member objects) are structurally ambiguous in one shared zod union (both carry value: string), so a single {value,$ref} member object was swallowed by the non-strict Email/Name schemas. Decision: ScimOpSchema.value is z.unknown(); the per-path service logic disambiguates (users / groups cast to the shape they expect). Bounded by the op-count cap (≤50)
    • each service's field caps.
  5. The test-app + OpenAPI generator needed the scim/:path* exclusion too. tests/integration/helpers/api.ts mirrors main.ts's setGlobalPrefix exclude list and scripts/generate-openapi.mjs has a NON_V1_BASES skip list — both now include scim/v2 so the protocol surface is (a) reachable in integration tests and (b) excluded from the TASK-0127 contract (documented in docs/api/versioning.md §4).
  6. Deactivation = SUSPENDED, never hard-delete (recorded decision). SCIM's DELETE /Users deprovisions; the task requires we disable, never delete. Both DELETE and PATCH replace active:false set User.status → SUSPENDED (login + in-flight blocked, memberships kept); the response meta note + the audit row (via: "delete" | "patch") surface the semantics. Re-activation restores access.
  7. Groups map to Teams in the org's PRIMARY workspace. A Group becomes a Team in the first non-personal, non-deleted workspace (oldest). DELETE removes the team only — members keep their workspace membership (only TeamMember is dropped). Member value = our User id or email; unresolved values are skipped + audited (membersUnresolved).
  8. SCIM id = our User id (no externalId). User has no externalId field (only SsoIdentity does), so the SCIM id IS the User id — GET /scim/v2/Users/:id is a point lookup + org-membership check (cross-org → 404, existence hiding).

TASK-0133

Title

Audit log viewer, org policies, data retention, org admin console

Objective

Ship the org admin console (tabbed: overview, members, integrations, policies, retention, API keys, webhooks, audit) gated by the org-admin role, with inside it: a filterable audit viewer with async CSV export, the org policy editor with live validation and five enforcement points, and the retention engine (per-org policies, daily worker job with dry-run/execute, audited purges) — closing the phase with a full E2E.

Background

AuditLog (actorType/actorId, workspaceId, pageId, resourceType, resourceId, action, ipAddress, userAgent, metadata, createdAt; indexes on [workspaceId, action, createdAt], [resourceType, resourceId, createdAt], [actorType, actorId, createdAt]) exists and is written across phases 1–13. Mandatory events per PRODUCT-SPEC §23: user.login, page.created|updated|deleted|shared, permission.changed, database.created, record.created|updated, file.uploaded, automation.executed, api_key.created — plus phase-12/13 additions that are security-mandatory: ai.budget_exceeded, oauth.token_issued|revoked, sso.login.saml|oidc, scim.user.created|deactivated, webhook.created|revoked. Organization.policy Json (default {}), RetentionPolicy (org, type PURGE|HOLD, scope, retainDays, config). Worker has the maintenance queue pattern (Phase 11). The org admin role: OrganizationMember.role with ADMIN/OWNER (the WorkspaceRole enum is reused for org membership per the existing schema — the gate is role ∈ {OWNER, ADMIN} on OrganizationMember).

Requirements

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Requirement conflicts & recorded decisions (rule 29)

  1. The in-flight SUSPENDED check landed in TASK-0132; TASK-0133 reuses it. The task says "confirm SUSPENDED blocks login and in-flight sessions." The missing in-flight check (a previously-issued access token kept working until expiry) was fixed in TASK-0132 (AuthGuard.resolveJwt re-checks User.status per request for user-JWTs; 403 { reason: "account_suspended" }, audited auth.suspended_blocked). Decision: the console's suspend action PATCHes the SAME User.status path (one code path, no new enforcement) — TASK-0133 only verifies it (integration 2g + E2E Part 2: login 403 after suspend, in-flight blocked).
  2. AuditLog has no organizationId column — the org scope is derived. The viewer/export/retention need one org scope. Decision: @factory/shared/events/audit-scope (auditScopedWhere) is the single source of truth: workspaceId IN (the org's workspaces) OR the org-level branch (workspaceId IS NULL AND (metadata.orgId = orgId OR resourceType IN (organization, org) AND resourceId = orgId)). A cross-cutting index (workspaceId, createdAt) + (resourceType, resourceId, createdAt) covers the viewer queries (no new migration beyond the conditional audit index in the DB changes above).
  3. The audit export is content-type-driven (no kind field). The viewer and the picker can't both know the row count without a round- trip. Decision: POST …/audit/export returns 200 text/csv (RFC 4180, 13-column header) for ≤ 10k rows and 202 {exportId, cap, total} above; the client reads the Content-Type to decide. The metadata column is compact JSON (no embedded newlines), so CSV line counts are safe. The web client uses apiFetchRaw (the sync/async union — the sync branch is raw CSV, not JSON).
  4. The presigned export URL is strictly single-use. The task says the export is "rate-limited" but not the URL lifetime. Decision: the API mints a 15-minute SigV4 GET on the FIRST ready-GET of the export status and flips downloadUsed; every later call (including ?redownload=1) returns the status WITHOUT a URL (no re-mint). The mint is pure signing (no S3 round-trip at the API). One active export per org (409 export_in_progress). The object lives under exports/{orgId}/{exportId}.csv with a 7-day lifecycle; the row cap is 500k (truncated: true when hit — unit-tested with an injectable cap, since seeding 500k rows is not a realistic unit fixture).
  5. Retention execute honors the admin's mode directly. The task's daily job is "dry-run by default" but the console needs a deliberate "Execute". Decision: the worker's retention.run job honors data.mode — an admin execute purges even when WORKER_RETENTION_EXECUTE is off (that flag scopes only the DAILY cron's unattended mode, not the deliberate admin action). Dry-run writes lastDryRun, execute writes lastRun on the policy row. audit_logs HOLD + files retainDays: 0 report skipped (no purge). The run report is Redis worker:retention:report:{runId} (7 d TTL) — the console polls it (the worker has no DB row for the run; the policy row carries the last-run stamps).
  6. The share-page notice is UX only; the server is authoritative. The task's enforcement point is the 403 on share CREATE. Decision: the share ROUTE page (app/(app)/w/[workspace]/page/[id]/share/page.tsx) reads the member-facing GET /workspaces/:id/policy/effective and shows a notice + disables the "public" radio when disableGuestPublicShare is true. The enforcement is ShareService.create (POST /shares, kind: "public" → 403 { reason: "guest_public_share_disabled" }); member/link shares are unaffected. (The ShareDialog component is unused — the route page is the live share surface. The web pageApi.share → PUT /pages/:id/share is a pre-existing dead route (404); out of scope for this task.)
  7. The org admin role gate is the single 403 surface. Every /orgs/:orgId/admin/* route is requireOrgAdmin (OWNER/ADMIN membership; everyone else, including members of OTHER orgs, gets the SAME 403 { reason: "requires_org_admin" } — cross-org is never distinguishable). The console's 403 gate is driven by the picker's GET …/admin/workspaces 403 (the reason is stable so the shell renders the gate panel). Suspend/role-change cannot remove the LAST owner (409 last_org_owner); granting OWNER is rejected (ownership_transfer_required) — ownership transfer is out of scope in v1.
  8. The file picker uses the org-aware cap (plan is the fallback). The picker previously computed the cap from the plan alone (ignoring the org policy). Decision: use-file-uploads.ts now also fetches GET /workspaces/:id/policy/effective and takes min(plan, effective.maxFileSizeBytes); on fetch failure it falls back to the plan cap. The presign 413 stays authoritative — the picker is a UI pre-check only. (The member-facing endpoint returns the SAME effectiveMaxFileBytes the upload path enforces, so there is no divergence between the warning and the 413.)
  9. Policy writes are audited with old/new values (no version table). The task's policy editor has "live validation" but no versioning. Decision: PATCH …/policy is audited org.policy.changed with the changed fields' old/new values — the change trail IS the policy history (no separate policy-version table in v1). The bounds are validated by the OrgPolicy Zod schema (unit-tested: bounds, defaults, plan-cap interaction via effectiveMaxFileBytes).
  10. SCIM If-Match is optional; the email is used verbatim. (Recorded here because it affects the E2E + the SCIM card.) The SCIM PATCH/DELETE controllers call ifMatchMatches(header, etag) but an ABSENT header passes (no If-Match is required). toEmail uses an @-containing userName VERBATIM (lowercased), so the E2E's test email domain must EXACTLY equal the configured userEmailDomain. The SCIM id IS the User.id (no externalId). SsoProvider.organizationId is @unique → one SSO provider per org → the E2E role gate uses the SSO JIT user (provisioned OrganizationMember role MEMBER, capped by policy.ssoDefaultRole); a second provider would collide. SSO login creates a Session (issueTokens, authMethod: "sso") and sets the factory_refresh cookie — the E2E captures it and posts to /auth/refresh for the JIT's access token.
  11. The audit-viewer routes were missing the org-admin gate (found via lint's unused-import signal; fixed + regression-tested). The four audit routes (GET admin/audit, GET admin/audit/actions, POST admin/audit/export, GET admin/audit/exports/:id) accepted @Req() req but never called assertOrgAdmin, and OrgAuditService's methods didn't gate either — the controller imported assertOrgAdmin and never used it (the lint error was the tell). Any org MEMBER could have read/exported the whole org audit log (and the original guard-matrix integration test only 403-tested /admin/overview). Decision: the gate now lives in OrgAuditService (a requireAdmin(orgId, req) helper — requireUser + assertOrgAdmin, matching every other org-admin service) and the controller passes req through; a new integration test (audit viewer routes are admin-only) locks member/guest/ non-member → 403 requires_org_admin and owner → 200 on all four routes. Recorded per rule 7 (never bypass authorization) / rule 29.