.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
audit-completeness.integration.test.tstests/integration/audit-completeness.integration.test.tsraw← integration26,349 B← Back to the app
1/**
2 * TASK-0133 — mandatory audit-event COMPLETENESS (the phase exit criterion
3 * "audit log covers all mandatory events").
4 *
5 * The table is generated from `packages/shared/src/events/audit-catalog.ts`
6 * (the single source of truth — the same list the audit viewer's action
7 * autocomplete reads). For EVERY catalog entry this spec performs the
8 * documented `fire` operation against a seeded workspace/org and asserts an
9 * `AuditLog` row with the catalog `name` action + EVERY catalog-required
10 * `metadata` field present. A missing (or field-dropping) emitter → the row
11 * is absent → the test FAILS THE BUILD.
12 *
13 * Driving surface per event (the `fire` id keys the dispatch table):
14 *   - the plain API surface (pages/records/shares/keys/webhooks/oauth/scim)
15 *     for 16 events;
16 *   - the REAL emitter via a service/worker seam for the 4 with no public
17 *     HTTP surface for the event:
18 *       * sso.saml / sso.oidc → `SsoProvisioningService.provision()` (the
19 *         SAML-ACS / OIDC-callback path is what calls it; driving the real
20 *         method keeps the test honest — a stubbed `sso.login.*` write would
21 *         not prove the IdP path emits it).
22 *       * automation.run → the worker `handleRunAutomation` pipeline (a
23 *         terminal run is what the catalog names; the depth-0 `recordTerminal`
24 *         writes `automation.executed`).
25 *       * ai.budget → `AiBudgetGuardService.onBudgetExceeded()` (the
26 *         402-AI_BUDGET_EXCEEDED path that calls it).
27 *   - file.uploaded is S3-dependent (the `complete` path HEADs the object in
28 *     MinIO); it is asserted when MinIO is available and skipped (documented,
29 *     non-failing) when it is not — exactly like the rest of the file suite.
30 *
31 * A single `it` loops the whole catalog so the table stays the contract:
32 * adding an event to `audit-catalog.ts` without a working emitter here fails
33 * the build (the spec's own "missing emitter fails CI" guarantee).
34 */
35import { afterAll, beforeAll, describe, expect, it } from "vitest";
36import { createHash, randomBytes } from "node:crypto";
37import { PrismaClient } from "@prisma/client";
38import { Redis } from "ioredis";
39import { getRedisUrl } from "./helpers/containers";
40import { createTestApp, type TestAppHandle } from "./helpers/api";
41// The catalog is the CONTRACT — import it from @factory/shared (the same
42// source the API/worker consume) and never re-list the events here: adding an
43// event to the catalog without a working emitter below fails the build.
44import { AUDIT_CATALOG } from "@factory/shared";
45 
46// Worker pipeline + executor seam from SOURCE (the monorepo test path; the
47// worker ships no build) — same convention as automation-pipeline /
48// files-pipeline.
49const { handleRunAutomation } = await import(
50  /* webpackIgnore: true */ "../../apps/worker/src/processors/automations/pipeline.ts"
51);
52const { registerTestExecutor, clearTestExecutors } = await import(
53  /* webpackIgnore: true */ "../../apps/worker/src/processors/automations/executors/registry.ts"
54);
55 
56// The real API emitters (imported by class so the test can call them through
57// the booted production AppModule — `app.get(Service)`).
58const { SsoProvisioningService } = await import(
59  /* webpackIgnore: true */ "../../apps/api/src/modules/sso/provisioning.service.ts"
60);
61const { AiBudgetGuardService } = await import(
62  /* webpackIgnore: true */ "../../apps/api/src/modules/ai/budget-guard.service.ts"
63);
64 
65// ── MinIO gate (file.uploaded) ───────────────────────────────────────────
66const S3_ENDPOINT = (process.env.S3_ENDPOINT ?? "http://localhost:9000").replace(/\/+$/, "");
67async function minioUp(url: string): Promise<boolean> {
68  try {
69    const res = await fetch(`${url}/minio/health/live`, { signal: AbortSignal.timeout(4_000) });
70    return res.ok;
71  } catch {
72    return false;
73  }
74}
75const minioReady = await minioUp(S3_ENDPOINT);
76 
77// ── state ─────────────────────────────────────────────────────────────────
78let app: TestAppHandle;
79let prisma: PrismaClient;
80let workerRedis: Redis | undefined;
81const auth = (token: string) => ({ Authorization: `Bearer ${token}`, "Content-Type": "application/json" });
82 
83interface Seed {
84  email: string;
85  name: string;
86  token: string;
87  userId: string;
88  orgId: string;
89  orgSlug: string;
90  wsId: string;
91  dbId: string;
92}
93const S = { seed: {} as Seed };
94const fired = new Set<string>();
95// Catalog NAMES of infra-gated events (e.g. `file.uploaded` when MinIO is
96// unavailable) — the loop skips their `expectRow` so a documented infra gap
97// never fails the build, but every NON-skipped event is fully asserted.
98const skipped = new Set<string>();
99const skipReasons: Record<string, string> = {};
100 
101const slug = () => `ac-${randomBytes(5).toString("hex")}`;
102const uid = () => randomBytes(6).toString("hex");
103 
104/**
105 * Read a response body ONCE (it is a stream — calling `.text()`/`.json()`
106 * twice throws "Body has already been read"). Returns `{ status, raw, data }`
107 * so the caller can assert on `status` + use the parsed `data`.
108 */
109async function read(res: Response | Promise<Response>): Promise<{ status: number; raw: string; data: unknown }> {
110  const r = await res; // app.request returns a Promise<Response>
111  const raw = await r.text();
112  let data: unknown = {};
113  try {
114    data = JSON.parse(raw);
115  } catch {
116    data = raw; // non-JSON (e.g. HTML consent page) — keep the raw text
117  }
118  return { status: r.status, raw, data };
119}
120 
121async function createAppUser(name: string): Promise<{ email: string; name: string; token: string; userId: string }> {
122  const email = `${slug()}@ac.com`;
123  const pw = "pw-123456789";
124  const reg = await read(app.request("POST", "/api/v1/auth/register", { body: { email, name, password: pw } }));
125  expect(reg.status, reg.raw).toBe(201);
126  const login = await read(app.request("POST", "/api/v1/auth/login", { body: { email, password: pw } }));
127  const lb = login.data as { accessToken: string; user: { id: string } };
128  expect(login.status, login.raw).toBe(200);
129  return { email, name, token: lb.accessToken, userId: lb.user.id };
130}
131 
132/**
133 * The newest AuditLog row for an action (any scope); null when absent. Some
134 * emitters (SCIM) write the row fire-and-forget (`void prisma…create`), so
135 * the row may land a tick after the HTTP response — retry briefly before
136 * concluding the event did not fire.
137 */
138async function latest(action: string): Promise<{ metadata: unknown } | null> {
139  for (let i = 0; i < 20; i++) {
140    const row = await prisma.auditLog.findFirst({ where: { action }, orderBy: { createdAt: "desc" } });
141    if (row) return row;
142    if (i === 19) return null;
143    await new Promise((r) => setTimeout(r, 50));
144  }
145  return null;
146}
147 
148/**
149 * Assert the action fired with EVERY catalog-required metadata field present
150 * (a field may hold null/"" — presence, not non-emptiness, is the contract;
151 * the emitters always populate them).
152 */
153function assertFired(action: string, required: string[]): void {
154  const row = firedRows.get(action);
155  if (!row) throw new Error(`audit completeness: no ${action} row`);
156  const meta = (row.metadata ?? {}) as Record<string, unknown>;
157  for (const f of required) {
158    if (!(f in meta)) throw new Error(`audit completeness: ${action} missing required metadata.${f} (got ${JSON.stringify(Object.keys(meta))})`);
159  }
160  fired.add(action);
161}
162const firedRows = new Map<string, { metadata: unknown }>();
163async function expectRow(action: string, required: string[]): Promise<void> {
164  const row = await latest(action);
165  if (row) firedRows.set(action, row);
166  assertFired(action, required);
167}
168 
169// ── fire dispatch (the `fire` id → the real operation) ───────────────────
170async function fire(id: string): Promise<void> {
171  const { seed } = S;
172  const h = auth(seed.token);
173 
174  switch (id) {
175    case "login": {
176      // A fresh login (register + login) — the catalog `user.login` row.
177      // The email is asserted by the generic expectRow; we capture it here
178      // only to keep the "a real login happened" intent explicit.
179      await createAppUser("Completeness Login");
180      return;
181    }
182 
183    case "page.create": {
184      const p = await read(app.request("POST", `/api/v1/workspaces/${seed.wsId}/pages`, { headers: h, body: { title: "Completeness page" } }));
185      expect(p.status, p.raw).toBe(201);
186      return;
187    }
188    case "page.update": {
189      const p = await read(app.request("POST", `/api/v1/workspaces/${seed.wsId}/pages`, { headers: h, body: { title: "to update" } }));
190      const pageId = (p.data as { id?: string }).id;
191      const up = await read(app.request("PATCH", `/api/v1/pages/${pageId}`, { headers: h, body: { title: "renamed" } }));
192      expect([200, 201].includes(up.status), up.raw).toBe(true);
193      return;
194    }
195    case "page.trash": {
196      const p = await read(app.request("POST", `/api/v1/workspaces/${seed.wsId}/pages`, { headers: h, body: { title: "to trash" } }));
197      const pageId = (p.data as { id?: string }).id;
198      const del = await read(app.request("DELETE", `/api/v1/pages/${pageId}`, { headers: h }));
199      expect(del.status, del.raw).toBe(200);
200      return;
201    }
202    case "share.create": {
203      const p = await read(app.request("POST", `/api/v1/workspaces/${seed.wsId}/pages`, { headers: h, body: { title: "to share" } }));
204      const pageId = (p.data as { id?: string }).id;
205      const s = await read(app.request("POST", "/api/v1/shares", { headers: h, body: { workspaceId: seed.wsId, targetType: "page", targetId: pageId, kind: "link", level: "VIEW" } }));
206      expect([200, 201].includes(s.status), s.raw).toBe(true);
207      return;
208    }
209    case "permission.change": {
210      // A role change on the second (seam-added) member.
211      const member = await createAppUser("Perm Member");
212      await app.request("POST", `/internal/workspaces/${seed.wsId}/members`, {
213        headers: { Authorization: `Bearer ${app.internalServiceToken}`, "Content-Type": "application/json" },
214        body: { email: member.email, role: "MEMBER" }
215      });
216      const patch = await read(app.request("PATCH", `/api/v1/workspaces/${seed.wsId}/members/${member.userId}`, { headers: h, body: { role: "ADMIN" } }));
217      expect(patch.status, patch.raw).toBe(200);
218      return;
219    }
220    case "database.create": {
221      // A database is a PAGE of type "database" (databases.service seeds it
222      // through the pages route) — that fires `database.created`.
223      const db = await read(app.request("POST", `/api/v1/workspaces/${seed.wsId}/pages`, { headers: h, body: { title: "Completeness DB 2", type: "database" } }));
224      expect([200, 201].includes(db.status), db.raw).toBe(true);
225      return;
226    }
227    case "record.create": {
228      // The title is set via the `title` field (not a property — TITLE is
229      // read-only through properties); the catalog only needs `databaseId`.
230      const r = await read(app.request("POST", `/api/v1/databases/${seed.dbId}/records`, { headers: h, body: { title: "r-create" } }));
231      expect([200, 201].includes(r.status), r.raw).toBe(true);
232      return;
233    }
234    case "record.update": {
235      const r = await read(app.request("POST", `/api/v1/databases/${seed.dbId}/records`, { headers: h, body: { title: "r-update" } }));
236      const rec = (r.data as { id: string }).id; // flat record DTO
237      const up = await read(app.request("PATCH", `/api/v1/records/${rec}`, { headers: h, body: { title: "r-updated" } }));
238      expect(up.status, up.raw).toBe(200);
239      return;
240    }
241    case "file.upload": {
242      if (!minioReady) {
243        skipped.add("file.uploaded");
244        skipReasons["file.uploaded"] = "MinIO unavailable";
245        return;
246      }
247      const bytes = "completeness-bytes".repeat(32);
248      const presign = await read(app.request("POST", "/api/v1/files/uploads", {
249        headers: { ...h, "x-factory-workspace": seed.wsId },
250        body: { name: "completeness.txt", mimeType: "text/plain", size: Buffer.byteLength(bytes) }
251      }));
252      expect(presign.status, presign.raw).toBe(201);
253      const upload = presign.data as { uploadUrl: string; headers: Record<string, string> };
254      const put = await fetch(upload.uploadUrl, { method: "PUT", headers: upload.headers, body: bytes });
255      expect(put.status, await put.text()).toBe(200);
256      return;
257    }
258    case "api_key.create": {
259      const k = await read(app.request("POST", `/api/v1/workspaces/${seed.wsId}/api-keys`, { headers: h, body: { name: "completeness-key", scopes: ["READ"] } }));
260      expect([200, 201].includes(k.status), k.raw).toBe(true);
261      return;
262    }
263    case "webhook.create": {
264      const w = await read(app.request("POST", `/api/v1/workspaces/${seed.wsId}/webhooks`, { headers: h, body: { url: "https://hooks.example.com/completeness", events: ["page.created"] } }));
265      expect([200, 201].includes(w.status), w.raw).toBe(true);
266      return;
267    }
268    case "webhook.revoke": {
269      const w = await read(app.request("POST", `/api/v1/workspaces/${seed.wsId}/webhooks`, { headers: h, body: { url: "https://hooks.example.com/completeness-revoke" } }));
270      const whId = (w.data as { id?: string }).id;
271      const del = await read(app.request("DELETE", `/api/v1/workspaces/${seed.wsId}/webhooks/${whId}`, { headers: h }));
272      expect(del.status, del.raw).toBe(200);
273      return;
274    }
275    case "scim.create": {
276      await scimCreate("comp-scim-create");
277      return;
278    }
279    case "scim.deactivate": {
280      await scimDeactivate();
281      return;
282    }
283    case "ai.budget": {
284      const guard = app.get?.(AiBudgetGuardService) as unknown as { onBudgetExceeded: (ws: string, used: number, limit: number, req?: unknown) => Promise<void> };
285      if (!guard) throw new Error("AiBudgetGuardService not resolvable from the app module");
286      await guard.onBudgetExceeded(seed.wsId, 100, 100, undefined);
287      return;
288    }
289    case "automation.run": {
290      await fireAutomationRun();
291      return;
292    }
293    case "sso.saml":
294    case "sso.oidc": {
295      const protocol = id === "sso.saml" ? "SAML" : "OIDC";
296      await fireSsoLogin(protocol);
297      return;
298    }
299    case "oauth.token":
300    case "oauth.revoke": {
301      await fireOAuth(id);
302      return;
303    }
304    default:
305      throw new Error(`audit completeness: unknown fire id "${id}" (catalog drift — update the dispatch table)`);
306  }
307}
308 
309// ── SCIM (shared org, enable-once) ────────────────────────────────────────
310let scimToken: string;
311async function enableScim(): Promise<string> {
312  // `userEmailDomain` maps `@`-less SCIM `userName`s → `<userName>@<domain>`
313  // (the enable schema has no `name` field; only these three options).
314  const r = await read(app.request("POST", `/api/v1/orgs/${S.seed.orgId}/scim`, {
315    headers: auth(S.seed.token),
316    body: { userEmailDomain: "scim-completeness.test" }
317  }));
318  if (r.status !== 201 && r.status !== 200) throw new Error(`enable scim failed (${r.status}): ${r.raw}`);
319  scimToken = (r.data as { token?: string }).token as string;
320  return scimToken;
321}
322const scimHeaders = (token: string) => ({ Authorization: `Bearer ${token}`, "Content-Type": "application/scim+json" });
323async function scimCreate(userBase: string): Promise<unknown> {
324  const res = await app.request("POST", "/scim/v2/Users", {
325    headers: scimHeaders(scimToken),
326    body: { schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"], userName: `${userBase}.${uid()}`, active: true }
327  });
328  const body = (await res.json()) as unknown; // read ONCE
329  expect(res.status, JSON.stringify(body)).toBe(201);
330  return body;
331}
332async function scimDeactivate(): Promise<void> {
333  // A fresh user (the deactivate path suspends a SCIM-created user).
334  const created = (await scimCreate("comp-scim-deact")) as { id: string };
335  const del = await read(app.request("DELETE", `/scim/v2/Users/${created.id}`, { headers: scimHeaders(scimToken) }));
336  // SCIM DELETE = deactivate: 200 + the (retained) User resource, `active:false`.
337  expect(del.status, del.raw).toBe(200);
338}
339 
340// ── SSO login (real provision(); the ACS/IDP-callback path is the caller) ─
341async function fireSsoLogin(protocol: "SAML" | "OIDC"): Promise<void> {
342  const svc = app.get?.(SsoProvisioningService) as unknown as { provision: (i: unknown) => Promise<unknown> };
343  if (!svc) throw new Error("SsoProvisioningService not resolvable from the app module");
344  const origin = `https://idp-${uid()}.example.test`;
345  const config =
346    protocol === "SAML"
347      ? { entityId: `${origin}/entity`, ssoUrl: `${origin}/sso/saml`, acsUrl: `${app.baseUrl}/saml/acs` }
348      : { issuer: origin, clientId: "completeness-client", authorizationEndpoint: `${origin}/authorize`, tokenEndpoint: `${origin}/token`, jwksUri: `${origin}/jwks`, redirectUri: `${app.baseUrl}/sso/oidc/${S.seed.orgId}/callback`, scope: "openid email profile" };
349  // One provider per org (`organizationId` UNIQUE) — the SAML leg already
350  // created one, so clear it before the OIDC leg (the provision path only
351  // reads THIS provider row; the audit row is what the catalog asserts).
352  await prisma.ssoProvider.deleteMany({ where: { organizationId: S.seed.orgId } });
353  const provider = await prisma.ssoProvider.create({
354    data: { organizationId: S.seed.orgId, type: protocol as never, name: `${protocol} Completeness`, config: config as never, enforce: false }
355  });
356  await svc.provision({
357    req: { ip: "127.0.0.1", headers: { "user-agent": "completeness-test" } },
358    // issueTokens → setRefreshCookie only calls `res.setHeader` (no status /
359    // send) — a no-op stub is enough; no network is touched.
360    res: { setHeader: () => undefined },
361    organizationId: S.seed.orgId,
362    provider: { id: provider.id, type: protocol, name: provider.name, config: config as Record<string, unknown> },
363    externalId: `ext-${uid()}`,
364    email: `sso-${protocol.toLowerCase()}-${uid()}@ac.com`,
365    displayName: "SSO Completeness User"
366  });
367}
368 
369// ── OAuth2 (the full authorize→token→revoke flow, PKCE + confidential) ────
370const s256 = (v: string) => createHash("sha256").update(v).digest("base64url");
371 
372async function fireOAuth(which: "oauth.token" | "oauth.revoke"): Promise<void> {
373  const seed = S.seed;
374  const redirectUri = `http://localhost:9999/${uid()}.cb`;
375  const appRes = await app.request("POST", `/api/v1/workspaces/${seed.wsId}/oauth/apps`, {
376    headers: auth(seed.token),
377    body: { name: `Completeness App ${uid()}`, scopes: ["pages:read"], redirectUris: [redirectUri] }
378  });
379  const reg = (await appRes.json()) as { clientId: string; clientSecret?: string };
380  expect(appRes.status, JSON.stringify(reg)).toBe(201);
381 
382  const verifier = randomBytes(32).toString("base64url");
383  const challenge = s256(verifier);
384  const params = new URLSearchParams({
385    response_type: "code",
386    client_id: reg.clientId,
387    redirect_uri: redirectUri,
388    scope: "pages:read",
389    state: "cs",
390    code_challenge: challenge,
391    code_challenge_method: "S256"
392  });
393  const consent = await app.request("GET", `/api/v1/oauth/authorize?${params.toString()}`, { headers: auth(seed.token), redirect: "manual" });
394  const html = await consent.text(); // read ONCE — the body is a stream
395  expect(consent.status, html.slice(0, 200)).toBe(200);
396  const consentToken = html.match(/name="consent_token" value="([^"]+)"/)?.[1];
397  expect(consentToken, "consent page must carry consent_token").toBeTruthy();
398  const form = new URLSearchParams({
399    client_id: reg.clientId,
400    redirect_uri: redirectUri,
401    scope: "pages:read",
402    state: "cs",
403    decision: "allow",
404    consent_token: consentToken!,
405    code_challenge: challenge,
406    code_challenge_method: "S256"
407  });
408  const approve = await fetch(`${app.baseUrl}/api/v1/oauth/authorize`, {
409    method: "POST",
410    headers: { ...auth(seed.token), "Content-Type": "application/x-www-form-urlencoded" },
411    body: form.toString(),
412    redirect: "manual"
413  });
414  expect(approve.status, "authorize must 302 to the redirect with a code").toBe(302);
415  const code = new URL(approve.headers.get("location")!).searchParams.get("code");
416  expect(code, "authorize must return a code").toMatch(/^oc_/);
417 
418  // Token exchange (PKCE + the confidential client's secret, as the proven
419  // oauth.integration spec does).
420  const tok = await app.request("POST", "/api/v1/oauth/token", {
421    headers: { "Content-Type": "application/json" },
422    body: {
423      grant_type: "authorization_code",
424      code: code!,
425      code_verifier: verifier,
426      redirect_uri: redirectUri,
427      client_id: reg.clientId,
428      client_secret: reg.clientSecret
429    }
430  });
431  const t = (await tok.json()) as { access_token?: string; refresh_token?: string };
432  expect(tok.status, JSON.stringify(t)).toBe(200);
433  expect(t.access_token, "token endpoint must return an access token").toBeTruthy();
434 
435  if (which === "oauth.revoke") {
436    // Revoke the family → the catalog `oauth.token_revoked` (the 202
437    // `oauth.token_issued` was already written above for THIS leg).
438    await app.request("POST", "/api/v1/oauth/revoke", {
439      headers: { "Content-Type": "application/json" },
440      body: { token: t.refresh_token ?? t.access_token, token_type_hint: "refresh_token" }
441    });
442  }
443}
444 
445// ── automation.executed (the real worker terminal path) ──────────────────
446async function fireAutomationRun(): Promise<void> {
447  const seed = S.seed;
448  const db = await prisma.database.findUnique({ where: { id: seed.dbId } });
449  if (!db) throw new Error("seed database missing");
450  const id = `auto_ac_${uid()}`;
451  const runId = `run_ac_${uid()}`;
452  await prisma.automation.create({
453    data: {
454      id,
455      workspaceId: seed.wsId,
456      name: "Completeness Automation",
457      status: "ACTIVE",
458      databaseId: seed.dbId,
459      createdById: seed.userId,
460      triggers: { create: [{ type: "RECORD_CREATED", config: { databaseId: seed.dbId }, position: 0 }] },
461      // Nested create via the relation — the FK `automationId` is implicit
462      // (Prisma rejects it as an explicit argument here). `retries` is the
463      // default 3 (the shared schema rejects 0) — it never matters here, the
464      // test executor succeeds on the first attempt.
465      actions: { create: [{ type: "CREATE_PAGE", config: { title: "Completeness auto page" }, position: 0 }] }
466    }
467  });
468  await prisma.automationRun.create({
469    data: { id: runId, workspaceId: seed.wsId, automationId: id, triggerEvent: { recordId: "seed" } as never, status: "PENDING" }
470  });
471  const deps = { prisma, redis: workerRedis!, env: { AUTO_RUN_MAX_RETRIES: 2 } as never };
472  const job = { runId, automationId: id, workspaceId: seed.wsId, triggerEvent: { recordId: "seed" }, eventId: `evt_${uid()}` };
473  // A no-op executor: the pipeline validates the known action type and
474  // executes our override (returns a terminal SUCCEEDED result), so
475  // `recordTerminal` fires the depth-0 `automation.executed` event.
476  registerTestExecutor("CREATE_PAGE", {
477    type: "CREATE_PAGE",
478    execute: async () => ({ output: { ok: true }, emittedEvents: [], audit: null })
479  });
480  const status = await handleRunAutomation(deps as never, { data: job });
481  clearTestExecutors();
482  expect(status).toBe("SUCCEEDED");
483}
484 
485// ── bootstrap ─────────────────────────────────────────────────────────────
486beforeAll(async () => {
487  app = await createTestApp();
488  prisma = app.prisma;
489  try {
490    const redisUrl = await getRedisUrl();
491    workerRedis = new Redis(redisUrl);
492  } catch {
493    workerRedis = undefined;
494  }
495 
496  // Shared seed: owner + org + workspace + database.
497  const owner = await createAppUser("Completeness Owner");
498  const org = await read(app.request("POST", "/api/v1/orgs", { headers: auth(owner.token), body: { name: `Completeness Org ${uid()}` } }));
499  expect(org.status, org.raw).toBe(201);
500  const ws = await read(app.request("POST", "/api/v1/workspaces", { headers: auth(owner.token), body: { name: `Completeness WS ${uid()}` } }));
501  expect(ws.status, ws.raw).toBe(201);
502  const db = await read(app.request("POST", `/api/v1/workspaces/${(ws.data as { id: string }).id}/pages`, { headers: auth(owner.token), body: { title: "Completeness DB", type: "database" } }));
503  expect([200, 201].includes(db.status), db.raw).toBe(true);
504 
505  S.seed = {
506    email: owner.email,
507    name: owner.name,
508    token: owner.token,
509    userId: owner.userId,
510    orgId: (org.data as { id: string }).id,
511    orgSlug: (org.data as { id: string; slug: string }).slug,
512    wsId: (ws.data as { id: string }).id,
513    dbId: (db.data as { databaseId: string }).databaseId
514  };
515 
516  // SCIM needs a token (enable-once, org-scoped).
517  scimToken = await enableScim();
518}, 180_000);
519 
520afterAll(async () => {
521  await workerRedis?.quit().catch(() => {});
522  await app?.close();
523});
524 
525describe("TASK-0133 mandatory audit-event completeness", () => {
526  it("fires EVERY catalog event with its required metadata (catalog = the contract)", async () => {
527    for (const entry of AUDIT_CATALOG) {
528      // eslint-disable-next-line no-await-in-loop
529      await fire(entry.fire);
530      if (skipped.has(entry.name)) continue; // infra-gated (e.g. no MinIO)
531      // eslint-disable-next-line no-await-in-loop
532      await expectRow(entry.name, entry.metadata);
533    }
534 
535    if (skipped.size) {
536      // Document the infra-gated skips (MinIO unavailable) without failing the
537      // build — the rest of the catalog is fully asserted.
538      const list = [...skipped].map((n) => `${n} (${skipReasons[n] ?? "infra"})`).join(", ");
539      console.warn(`[audit-completeness] infra-gated skips (asserted when the infra is available): ${list}`);
540    }
541 
542    // The build guarantee: no catalog event may be missing an emitter
543    // (infra-gated events are excluded from the count).
544    expect(fired.size, `missing emitters: ${AUDIT_CATALOG.filter((e) => !fired.has(e.name) && !skipped.has(e.name)).map((e) => e.name).join(", ")}`).toBe(
545      AUDIT_CATALOG.length - skipped.size
546    );
547  });
548});
549