.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-11-automation.mddocs/tasks/phase-11-automation.md← tasks78,515 B← Back to the app

Phase 11 — Automation (Task Specs)

Purpose

Phase 11 delivers the automation engine: workspace-scoped automations (trigger → conditions → actions) that react to domain events, schedules, incoming webhooks, and manual runs; a BullMQ-backed execution pipeline with runs, step logs, retries with exponential backoff, dead-lettering, and loop prevention; and the UI for building, monitoring, and running them.

Phase Exit Criteria (from ROADMAP.md § Phase 11)

Tasks

ID Title Estimate
TASK-0110 Automation engine core: CRUD API + trigger matcher (events, schedule, webhook, manual) L
TASK-0111 Condition evaluator (AND/OR/NOT tree over payload + record properties) M
TASK-0112 Action executors (9 action types, config schemas, error classification) L
TASK-0113 Queue + workers + dead-letter + loop prevention + run recording L
TASK-0114 Automation editor UI + execution history UI + Phase-11 E2E/safety suite L

Phase Completion Checklist

Phase 11 — Verification Result + Gap Report (2026-09-30)

Verification (all in-scope suites green)

Suite Result
tests/e2e/automation.spec.ts (7 tests × desktop+mobile) 14/14 passed
Integration: automations.integration.test.ts + automation-pipeline.integration.test.ts 70/70 passed
Worker unit (18 files, incl. automations + search-index) 321/322 (1 pre-existing files-scope failure, see G5)
Shared unit 237/237 passed
API unit (14 files) 132/134 (2 pre-existing crypto-test failures, see G6)
Typecheck: shared / api / worker / integration / e2e all exit 0, 0 errors
Lint (project policy: eslint:recommended + @typescript-eslint/recommended) over shared+api+worker+integration+e2e 0 errors (369 no-unused-vars warnings on intentional DI/interface signatures + test-mock callbacks; 46 no-undef warnings = Node/DOM globals, declared by the real .eslintrc.json env but not by the flat translation used here)

Fixes applied during Phase 11 verification

Recorded conflicts / pre-existing gaps (rule 29: recorded, not silently

re-guessed; none are Phase-11 regressions)

Honest status

Phase 11's own scope — CRUD, matcher, condition evaluator, 9 executors, BullMQ pipeline with backoff/retry/DEAD_LETTER/admin notification, loop prevention (self-trigger + depth 5), run recording/logs, editor + history UI, and the E2E safety suite — is complete and verified per the tables above. The ROADMAP exit checkboxes are marked on that basis. G1–G10 are recorded conflicts/gaps, not Phase-11 defects; G1, G2, G3, G4, G8 are the ones a Phase-11 user will feel first and are the recommended follow-ups.

Cross-Phase Contracts (read first)


TASK-0110

Title

Automation engine core: CRUD API + trigger matcher (domain events, schedule, incoming webhook, manual)

Objective

Ship automation CRUD (workspace-scoped, optionally database-scoped, four statuses) and the trigger layer: a worker-side matcher that consumes domain events on Redis streams and matches trigger type + filters, schedule triggers as BullMQ repeatable cron jobs, incoming webhook triggers via a signed public endpoint, and manual triggers via API/button.

Background

All Prisma models and enums exist (see Cross-Phase Contracts). The worker has the automations queue scaffolded (apps/worker/src/processors/ — processor files are created by this task family); domain events flow on Redis streams via the shared event bus used by the search indexer (TASK-0096) and files pipeline (TASK-0102) — reuse the same consumer-group pattern with a NEW consumer group (automations-matcher) so the matcher never starves other consumers. BullMQ repeatable jobs are the established pattern (Phase-9 sweeper, Phase-8 reconciliation). Webhook/WebhookDelivery models are the Phase-13 OUTBOUND webhook feature; the incoming trigger endpoint here is separate and must not be confused with it (document the distinction).

Requirements

  1. CRUD API (all workspace-scoped; EDIT on workspace settings required — automations are an admin/member capability: Owner/Admin always, Member with EDIT on the scoped database when databaseId set; server-side, never trust the UI): POST /api/v1/workspaces/:wsId/automations { name (1..100), description? (≤ 500), databaseId? } → DRAFT; GET /api/v1/workspaces/:wsId/automations?status=&cursor= (list, 50/ page, with trigger/condition/action summaries); GET /api/v1/automations/:id (full detail incl. latest run); PATCH /api/v1/automations/:id (name, description, databaseId, status transitions); DELETE /api/v1/automations/:id (→ ARCHIVED, not hard-deleted — runs are retained for audit; re-activate allowed); sub-resource upserts used by the editor (TASK-0114): PUT /api/v1/automations/:id/triggers (replace set, 1..3 triggers, OR-semantics — any trigger firing starts a run), PUT /api/v1/automations/:id/conditions (0..1 tree), PUT /api/v1/automations/:id/actions (ordered list, 1..50). Every sub-resource write zod-validates each trigger/condition/action config against its schema (schemas live in TASK-0111/0112 shared modules); invalid → 422 with path-located errors.
  2. Status machine: DRAFT → ACTIVE (enables matching + schedules repeatable cron jobs + registers incoming endpoint), ACTIVE ↔ PAUSED (matching disabled; scheduled jobs paused; existing queued runs still complete — document), ACTIVE/PAUSED → ARCHIVED (matching off, cron jobs removed, endpoint 404); ARCHIVED → ACTIVE (re-armed). Transitions enforced server-side (invalid transition → 409); each transition audited (automation.status_changed audit event with from/to).
  3. Trigger matcher (worker, apps/worker/src/processors/automations/ matcher.ts): consumes domain events (consumer group automations-matcher on the shared stream, same at-least-once semantics as TASK-0096) for trigger-relevant events:
    • RECORD_CREATED ← record.created; RECORD_UPDATED ← record.updated; PROPERTY_CHANGED ← record.updated (only when changedProperties[] in the event payload intersects the trigger's config.properties[] — the record events from Phase 5 must carry changedProperties; if a fixture/legacy event lacks it, PROPERTY_CHANGED does not fire for that event — documented, no false positives); PAGE_CREATED ← page.created; FORM_SUBMITTED ← form.submitted (Phase-10 payload).
    • Filters: config.databaseId must equal the event's databaseId (event without databaseId never matches a database-scoped trigger); optional config.propertyId/config.properties narrowing for RECORD_* triggers; workspace isolation is implicit (events carry workspaceId; matcher only loads automations of that workspace — cross-workspace matching is impossible by construction).
    • Match → enqueue run job (TASK-0113's run-automation) with the event payload as triggerEvent. One run per (automation, event) — dedupe on a Redis SET NX key auto:dedupe:{automationId}:{eventId} (event publishers assign a stable eventId; TTL 10 min).
  4. Schedule triggers: config: { cron: string, timeZone?: string (default workspace TZ or UTC), runEvery?: "each_match" } — cron validated with a real cron parser (6-field, seconds optional; reject expressions with intervals < 60 s — 422 CRON_TOO_FREQUENT); on ACTIVE, a BullMQ repeatable job automations:run:{automationId} with the cron pattern is registered; on PAUSE/ARCHIVE/inactive-cron-edit it is removed (idempotent removeRepeatable). The scheduled run's triggerEvent = { type: "schedule", firedAt, cron }.
  5. Incoming webhook triggers: on save/activate, a WEBHOOK trigger generates { webhookId: cuid, secret: "whsec_" + 32 random bytes hex }; the secret is returned ONCE in the save response (also shown once in the UI with a copy button — never stored in plaintext; sha256(secret) in config.secretHash, webhookId in config.webhookId). Endpoint POST /api/v1/webhooks/incoming/:webhookId (public, no auth): body application/json ≤ 256 KB; headers X-FS-Signature: t=<unix-ts>,v1=hex(hmac_sha256(secret, t + "." + rawBody)) + X-FS-Timestamp; server recomputes with the stored hash's preimage — stored hash is sha256 of the secret, so signature verification needs the secret: STORE the secret encrypted with the server KMS key (env AUTOMATION_WEBHOOK_KMS_KEY, AES-256-GCM wrap) — document this; timestamp tolerance ±5 min (reject WEBHOOK_TIMESTAMP_STALE); constant- time comparison (reject WEBHOOK_SIGNATURE_INVALID); per-webhookId rate limit 60/min (Redis); on success: 200 { accepted: true } (processing is async) + enqueue run with triggerEvent = { type: "webhook", payload: <json body> (truncated to 64 KB for run storage), headers: {…non-sensitive} }. Wrong webhookId → 404 (no existence leak beyond the id itself).
  6. Manual triggers: POST /api/v1/automations/:id/run (permission: EDIT on the automation) with optional { input?: Json ≤ 64 KB } → enqueues a run with triggerEvent = { type: "manual", actorId, input? }; returns 202 { runId } (the UI polls the run — TASK-0114). DRAFT automations CAN be run manually (that's the dry-run path, TASK-0114 preview).
  7. Observability: matcher logs every decision at debug (event → matched/ skipped + reason: wrong_workspace is impossible, status_not_active, no_trigger_match, filter_mismatch, deduped, depth_limit) and metrics automations:matched{type}, automations:skipped{reason}.

Acceptance Criteria

Backend Changes

Frontend Changes

None (editor is TASK-0114; this task ships the API the editor calls).

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done


TASK-0111

Title

Condition evaluator: AND/OR/NOT tree over event payload + current record properties

Objective

Implement the condition layer: a stored AND/OR/NOT expression tree evaluated in the worker against the triggering event payload plus the current record properties — with a deterministic, unit-tested decision matrix — and make conditions optional (empty tree = always true).

Background

AutomationCondition.expression is a Json tree. The worker already has context at evaluation time: the triggerEvent (TASK-0110) and, for record-scoped triggers, the DatabaseRecord + RecordProperty rows (Phase-5 read service). The evaluator must be a pure function (ctx) => boolean so the decision matrix is exhaustively unit-testable without infrastructure. Property value comparison must understand all 22 PropertyType value shapes (Phase-5 packages/database-engine value coercion/normalization — reuse its comparators, do not write new ones).

Requirements

  1. Expression shape (zod schema in @factory/shared/automation.ts, CONDITION_EXPRESSION_SCHEMA):
    type Expr =
      | { op: "AND" | "OR"; children: Expr[] (≥ 1; AND/OR need ≥ 2 unless NOT) }
      | { op: "NOT"; child: Expr }
      | Predicate
    type Predicate = {
      field: { ref: "event" | "property"; name: string }
      // event.name ∈ ["type","databaseId","workspaceId","recordId","changedProperties","payload.*"]
      // property.name = DatabaseProperty name (e.g. "Priority")
      operator: "eq"|"neq"|"contains"|"not_contains"|"starts_with"|"ends_with"|
                "gt"|"gte"|"lt"|"lte"|"in"|"not_in"|"is_empty"|"is_not_empty"
      value?: Json | Json[]   // required unless is_empty/is_not_empty
    }
    
    Tree depth ≤ 5, total nodes ≤ 100 (422 CONDITION_TOO_COMPLEX at save time and at evaluation time — a corrupt stored tree fails the run with CONDITION_INVALID, never "true by default").
  2. Evaluation context (worker builds it):
    • event: the triggerEvent object (so field.ref = "event" reads triggerEvent.type, triggerEvent.databaseId, triggerEvent.payload for WEBHOOK/FORM_SUBMITTED bodies — dot-path payload.category).
    • property: for triggers that have a record (RECORD_*, FORM_SUBMITTED via recordId, PROPERTY_CHANGED), the CURRENT property values, read fresh at evaluation time (post-event state — documented: conditions see the state AFTER the change; for RECORD_CREATED, the new record's values). No record available (PAGE_CREATED without a record) → any property.* predicate evaluates false (fail closed, logged).
    • Value normalization: property values normalized via Phase-5 comparators before comparison (SELECT → option string, NUMBER → number, DATE → ISO, CHECKBOX → boolean, MULTI_SELECT → sorted array, PERSON → user id array, etc.).
  3. Operator semantics: eq/neq — normalized equality (arrays: set-equality for MULTI_SELECT/PERSON); contains/not_contains — string substring (case-insensitive) or array membership (value is a single item ); starts_with/ends_with — strings, case-insensitive; gt/gte/lt/lte — numbers or dates only (other types → predicate false + warn log — no crash); in/not_in — value array membership (value may be array: for MULTI_SELECT, in = intersection non-empty); is_empty/is_not_empty — null/""/[]/{} semantics per type. Comparison errors (type mismatch like date vs string) → predicate false + warn (deterministic, documented).
  4. Worker integration: in the run processor (TASK-0113), after trigger match, before action execution: load conditions (0 → skip evaluation, result true), build context (≤ 2 extra queries: record + its properties; event already in hand), evaluate; result false → run recorded as SUCCEEDED with output = { skipped: "conditions_not_met" } (a skipped run is a success — it was correctly not executed; logged at info with the failing predicates for debug) and NO actions run.
  5. Determinism: same ctx → same result; evaluation must not touch the network (property values are pre-read); total eval time budget 50 ms (over → run fails CONDITION_TIMEOUT, log the tree size).

Acceptance Criteria

Backend Changes

Frontend Changes

None (builder UI is TASK-0114).

Database Changes

None.

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done


TASK-0112

Title

Action executors: 9 action types with zod config schemas, isolation, timeouts, error classification

Objective

Implement all 9 action executors as isolated, schema-validated, timeout- bounded units that execute against the automation's context, each with a clear retryable-vs-fatal error classification, so the run pipeline (TASK-0113) can execute, retry, log, and dead-letter them uniformly.

Background

The run context (built by TASK-0113) contains: runId, automationId, workspaceId, databaseId?, recordId? (+ current record + properties), triggerEvent, input? (manual/webhook payload), actor (system user identity for the automation — see Requirements §10), depth. Prisma models for all targets exist. SEND_EMAIL uses the Phase-7/Phase-0 email queue (email BullMQ queue scaffolded) — executors ENQUEUE email jobs, they never open SMTP sockets themselves. SEND_WEBHOOK reuses the S3-free HTTP client pattern; outbound webhook delivery (with its own retries/deliveries table) is Phase 13 — this executor does a single POST with its own timeout and classifies errors (the Phase-13 WebhookDelivery pipeline is a separate concern; documented to avoid duplication).

Requirements

  1. Executor interface (apps/worker/src/processors/automations/executors/ types.ts):
    interface ActionExecutor {
      type: AutomationActionType
      schema: ZodType                      // config schema (zod)
      execute(cfg: z.infer<schema>, ctx: RunContext): Promise<ActionResult>
    }
    interface ActionResult {
      output: Json                          // recorded on the run
      emittedEvents: string[]               // domain events fired (for logs)
      audit: { action: string, resourceType: string, resourceId: string } | null
    }
    
    Registry executors/index.ts maps type → executor; unknown type at runtime (corrupt row) → fatal UNKNOWN_ACTION_TYPE.
  2. Config schemas (all in @factory/shared/automation.ts, zod, single source of truth for API save-validation and runtime):
    • UPDATE_RECORD: { recordId: "from_trigger" | cuid, propertyValues: Record<propertyName, Json> (≥ 1; ≤ 20) } — property names must exist + accept writes (not FORMULA/ROLLUP/CREATED_*/BUTTON/RELATION — 422 PROPERTY_NOT_WRITABLE at save; at runtime, unknown name → fatal PROPERTY_UNKNOWN).
    • CREATE_PAGE: { title (1..200; interpolation allowed — see §9), parentId?: "from_trigger_page" | pageId, databaseId?: cuid (creates a record page), templateId?: cuid (Phase-19 templates — optional, validated if set), icon?: emoji }.
    • SEND_EMAIL: { to: string (email) | "trigger_user" | "automation_creator", subject (≤ 200, interpolation), body (markdown ≤ 20 000, interpolation), cc?: email[] (≤ 5) }.
    • SEND_WEBHOOK: { url (https only, ≤ 2048; no internal hostnames — see Security), headers?: Record<string,string> (≤ 20, values ≤ 512, interpolation), payload: Json (≤ 64 KB, interpolation) }.
    • CREATE_NOTIFICATION: { to: userId | "trigger_user" | "automation_creator", type: NotificationType, body (≤ 500, interpolation), link?: string (internal path only: starts with "/", no host) }.
    • ASSIGN_USER: { recordId: "from_trigger" | cuid, propertyId: <PERSON property>, userId: cuid | "trigger_user" | "automation_creator" }.
    • CHANGE_STATUS: { recordId: "from_trigger" | cuid, propertyId: <STATUS or SELECT property>, value: option-string } — syntactic specialization of UPDATE_RECORD with stricter validation (property must be STATUS or SELECT; value must be an existing option — 422 STATUS_OPTION_MISSING).
    • CALL_INTEGRATION: { integrationId: cuid, operation: string, params?: Json } — STUB: executor looks up Integration (Phase-13 model exists), returns fatal INTEGRATION_NOT_AVAILABLE (Phase 13) if the adapter registry has no adapter (registry file executors/integration-registry.ts created empty with the interface — Phase 13 fills it). Config validation still runs (schema enforced) so the UI can build them now.
    • RUN_AI_ACTION: { prompt (≤ 4 000, interpolation), task: "summarize"|"classify"|"extract" } — STUB: returns fatal AI_ACTION_NOT_AVAILABLE (Phase 12); schema validated.
  3. Execution isolation: each executor runs in its own try/catch with its configured timeoutMs (default 30 000, max 300 000 — clamped at save) via Promise.race + AbortSignal where the underlying call supports it (HTTP, DB writes via Prisma timeout where available); a timeout → fatal ACTION_TIMEOUT (timeouts are fatal: re-executing a timed-out write risks double-effects — the only retryable set is I/O below); the executor must not catch-and-swallow classification errors.
  4. Error classification (every thrown error mapped — classifyError(err): { retryable: boolean, code: string }):
    • retryable: network errors (ECONNRESET, ETIMEDOUT, EPIPE), HTTP 429/5xx from outbound calls, S3/Postgres transient (deadlock 40P01, serialization 40001), email queue full.
    • fatal: validation (schema/property/option), permission denied, 4xx HTTP (except 429), ACTION_TIMEOUT, unknown action/property, INTEGRATION/AI stubs, quota (workspace over email/webhook daily cap — see §8). Classification is per-executor (each executor declares which of its errors are retryable — the HTTP executor classifies by status code, the DB executors by Prisma error code, etc.); the pipeline (TASK-0113) consumes retryable only.
  5. UPDATE_RECORD: recordId "from_trigger" resolves to ctx.recordId (missing → fatal NO_TRIGGER_RECORD); values coerced via Phase-5 record update (reuses records.service.updateRecord coercion — server-side permission: the automation actor (see §10) must have EDIT on the record's page; denied → fatal PERMISSION_DENIED + audit automation.permission_denied); output = { recordId, changed: [...] }. Emits record.updated (with automationContext depth+1, and changedProperties = the changed names — this is how downstream PROPERTY_CHANGED automations fire, within the depth budget).
  6. CREATE_PAGE: via Phase-3 page service (title, icon, parent, position at end of parent's children); when databaseId set, also creates the record + sets propertyValues if provided (optional propertyValues added to the CREATE_PAGE schema for record pages); output = { pageId, recordId? }; emits page.created (+ record.created for record pages) with automationContext.
  7. SEND_EMAIL: builds the Phase-7 email job { to, cc, subject, bodyHtml (markdown → html via the shared renderer), template: "automation", workspaceId } and enqueues on the email queue; output = { queued: true, to }; queue enqueue failure = retryable. Daily per-workspace cap 500 automation emails (Redis counter auto:email:{wsId}:{date}; over → fatal EMAIL_DAILY_CAP).
  8. SEND_WEBHOOK: single POST, 10 s connect / timeoutMs total, Content-Type: application/json; output = { status: <code>, ms, bodyTruncated (first 2 KB) }; non-2xx → retryable iff 429/5xx else fatal WEBHOOK_REJECTED (the run still records the status in output — visibility > silence). SSRF guard (Security) before the call.
  9. Interpolation: {{record.<Property>}}, {{record.id}}, {{event.type}}, {{event.payload.<path ≤ 3>}}, {{input.<path ≤ 3>}}, {{automation.id}}, {{run.id}} — implemented in executors/interpolate.ts (single implementation, used by every executor that allows it; unknown keys → empty string + warn log; output after interpolation still within the field's max length or fatal INTERPOLATION_OVERFLOW).
  10. Automation actor: the worker runs actions as the workspace's "automation system" identity — implementation: a reserved user row type: system per workspace (created on workspace creation if the Phase-2 user service supports it; otherwise the actor is actorType: "system", actorId: "automation:<automationId>" for audit, and permission checks use the automation creator's role with an explicit onBehalfOf: automation flag recorded in audit — DECISION: use creator-role + system audit actor, no new system user; documented in docs/architecture/automation.md because it affects permission tests).

Acceptance Criteria

Backend Changes

Frontend Changes

None (action forms are TASK-0114; the ACTIONS metadata registry is the UI's data source for per-type forms).

Database Changes

None.

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done


TASK-0113

Title

Queue + workers: run pipeline, run/log recording, retries with backoff, dead-letter, loop prevention

Objective

Build the execution pipeline: the automations BullMQ queue and run processor that executes a matched trigger as a recorded run — PENDING → RUNNING → SUCCEEDED/FAILED/DEAD_LETTER — with per-step automation_logs, exponential-backoff retries (max 8) then dead-letter + admin notification, loop prevention (depth limit 5 + self-trigger block), and the 50-action guard.

Background

Queue scaffolding: the automations queue is declared in apps/worker/src/queues.ts (Phase 0/8 pattern — BullMQ over the shared Redis). Matchers (TASK-0110) enqueue run-automation jobs; conditions (TASK-0111) gate; executors (TASK-0112) run. AutomationRun / AutomationLog models exist (Cross-Phase Contracts). The run pipeline is the single place where automation side effects are observed — every run, including condition-skips and dead-letters, must be recorded (exit criterion: "every run logged").

Requirements

  1. Queue: automations (BullMQ), concurrency AUTOMATIONS_WORKER_CONCURRENCY (default 4, env), removeOnComplete: 1000, removeOnFail: 500. Jobs: run-automation { runId, automationId, workspaceId, triggerEvent, eventId } (the run row is created PENDING by the ENQUEUER — matcher or manual — so the run exists before processing starts; triggerEvent stored in the row AND passed in the job (idempotent re-read on retry)).
  2. Run state machine (enforced, only valid transitions): PENDING → RUNNING (processor start: startedAt = now) → SUCCEEDED | FAILED (terminal, finishedAt = now); RUNNING → FAILED (fatal error); a FAILED run with retryCount < maxRetries is re-enqueued as PENDING (same runId, retryCount + 1) — maxRetries = min of the action-level retries values involved (default 3 per action; the RUN-level cap is 8 — AUTO_RUN_MAX_RETRIES = 8 constant, env-overridable only in dev); when the cap is hit → DEAD_LETTER (terminal) + dead-letter handling (§4). A run is SUCCEEDED iff every action completed (or was skipped by conditions — that's recorded at run start, before actions).
  3. Execution loop: for each action in position order: log step start (automation_logs: level info, message action {i}/{n} {TYPE} started, data { actionId, config (redacted per TASK-0112) }), execute with the executor's timeout, log step end (info + output, or error + classification); on fatal error → run FAILED (error { code, message, actionIndex, actionType, attempts }), stop remaining actions; on retryable error → run re-enqueued per §2 (the retry re-executes from action 1 — idempotency note: executors must be idempotent-safe or explicitly non-retryable; SEND_EMAIL is idempotency-keyed by runId:actionIndex in the email job payload, UPDATE_RECORD/CREATE_PAGE are naturally idempotent by value-set — documented per executor).
  4. Dead-letter: run → DEAD_LETTER when retryCount reaches the cap: (a) run row terminal with full error; (b) automation.executed domain event emitted with status: "dead_letter"; (c) in-app notification to ALL workspace Admins+Owners ("Automation 'X' dead-lettered after 8 attempts: <code> — view run"); (d) audit automation.dead_lettered (actor system, metadata: automationId, runId, code, attempts); (e) metric automations:dead_letter{workspaceId}. DLQ runs are inspectable/re-runnable via the UI "Retry" button (TASK-0114) → POST /api/v1/automations/:id/runs/:runId/retry (permission: EDIT) resets retryCount to 0, status PENDING, re-enqueues.
  5. Loop prevention (the global contract from the phase header, enforced HERE): the run processor reads triggerEvent.automationContext { runId, automationId, depth } if present: depth ≥ 5 → run SUCCEEDED with output = { skipped: "depth_limit" } (no actions, logged at warn with the chain); automationContext.automationId === run's automationId → SUCCEEDED { skipped: "self_trigger" } (an automation never reacts to its own emitted events — its own record.updated from UPDATE_RECORD cannot re-fire it). Matched events from OTHER automations are allowed up to depth 5 (the matcher passes the context through). Events emitted by executors always carry depth = (incoming ?? 0) + 1 (TASK-0112 contract).
  6. Action guard: if an automation's stored action list has > 50 entries at run time (corrupt row; save-time cap exists) → run FAILED RUN_ACTION_LIMIT; likewise if the loop somehow exceeds 50 invocations (defensive).
  7. Run recording completeness (the "every run logged" criterion): every run row gets ≥ 1 automation_log (for condition-skips: the info log skipped: conditions_not_met with the failing predicates; for depth/self-skips: the warn log; for normal runs: per-step logs). Run input = { triggerEvent (truncated 64 KB), conditions: <tree>, actionCount }; output = { actions: [{ type, output, ms, status }], totalMs } (truncated 128 KB); error = the §3 shape. Truncation preserves a truncated: true flag.
  8. automation.executed event (frozen payload, used by Phase-13 webhooks + dashboards): emitted on every TERMINAL status (SUCCEEDED, FAILED, DEAD_LETTER): { workspaceId, automationId, runId, status, triggerType, durationMs, error?: { code } } — with automationContext depth-passthrough rules applied (terminal runs of depth ≥ 1 do NOT re- emit to avoid event storms: emit only when depth === 0 — document).
  9. Reliability: processor is crash-safe — a worker killed mid-run leaves the run RUNNING; a watchdog repeatable job (every 5 min) finds RUNNING runs with startedAt older than 15 min → re-enqueues (retry count +1) or marks FAILED WORKER_CRASHED at the cap (log + same notification path as dead-letter). Job lock: BullMQ lockDuration 60 s, stalledInterval 30 s (stalled → reprocess).
  10. Observability: per-run structured log line automation.run {runId, automationId, status, durationMs, retries, triggerType}; metrics: automations:runs{status}, automations:run_duration_ms (histogram), automations:actions{type, status}, automations:queue_depth.

Acceptance Criteria

Backend Changes

Frontend Changes

None (history UI is TASK-0114; the runs endpoints are its contract).

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done


TASK-0114

Title

Automation editor UI + execution history UI + Phase-11 E2E/safety suite

Objective

Deliver the user-facing half of Phase 11: the workspace-settings Automations section (list + detail + history with step logs + enable/pause + Run now) and the create/edit dialog (trigger picker with per-type config, nested condition builder, ordered action list with per-type config forms, retries/timeout, dry-run preview in dev) — plus the E2E + safety suite that proves the phase exit criteria.

Background

The engine is complete (TASKS-0110…0113): CRUD + sub-resource PUTs, conditions, executors, runs API (GET .../runs, GET .../runs/:runId, POST .../runs/:runId/retry), manual run (POST .../run), incoming webhook secret shown-once. Workspace settings live in Phase 2 (apps/web/src/app/(workspace)/settings/ — a new Automations section tab). @factory/shared/automation.ts holds the trigger/condition/action schemas + ACTIONS metadata (display names/icons) + TRIGGERS metadata — the UI renders forms FROM this metadata (single source of truth, no duplicated type lists). Status pills: DRAFT (gray), ACTIVE (green), PAUSED (amber), ARCHIVED (muted).

Requirements

  1. Automations section (settings → Automations): list (name, scope chip (workspace / database name), first trigger type + summary, status pill, last-run status dot + time, created-by avatar); toolbar: "New automation" (primary), search filter, status filter; empty state ("No automations yet" + create button + a 3-example starter card: "When a record is created → set Status", "When a form is submitted → notify owner", "Daily → send summary email" — clicking a starter pre-fills the editor). Row click → detail view.
  2. Detail view: header (name, status pill, enable/pause toggle — for DRAFT: "Activate" button; PAUSED: "Resume"; ARCHIVED: "Restore"), tabs: Overview (trigger summary card, condition summary in plain language ("Only when Priority equals High AND Status is not Done"), action list summary with type icons), History (below).
  3. History tab (runs API): runs table (status pill, trigger type + icon, started time, duration, retry count, error code when failed), newest first, 50/page cursor, filter by status; row click → run detail drawer (right slide-over): trigger event JSON (pretty, collapsible, 64 KB truncated view), each action step as a timeline item (icon, type, status, duration, expandable output JSON + error JSON), the log stream (time, level color, message, data JSON collapsible — redacted values shown as ***), and for DEAD_LETTER/FAILED: a "Retry run" button (confirm) calling the retry endpoint + toast on 202.
  4. Editor dialog (create/edit, modal 960 px, 3-step layout but all visible on one scroll — Notion-style stack): (a) Name + description + scope select ("Whole workspace" / a database — database select filtered to databases the user can EDIT; changing scope on a record-triggered automation re-validates triggers). (b) Trigger section: "Add trigger" (1..3; multiple = OR — shown as "Any of these triggers"); per-trigger-type form from TRIGGERS metadata: RECORD_CREATED (database pre-filled from scope), RECORD_UPDATED (same), PROPERTY_CHANGED (property multi-select + note "fires when any listed property changes"), PAGE_CREATED (scope note), FORM_SUBMITTED (form select for in-scope databases' forms), SCHEDULE (cron input with human preview — "Every day at 09:00" — validated via the shared cron validator, error inline; timezone select defaulting to workspace TZ), WEBHOOK (shows generated URL + secret ONCE with copy + "I have copied it" checkbox gating Save; regenerate button with confirm
    • warning that old signatures break; a curl example snippet block), MANUAL (static text "Can be run from the Run now button"). (c) Condition section: "Add condition" (optional — empty = "Always run"); builder: nested AND/OR group cards (default group OR? — no: default is a single predicate; "Add group" creates an AND/OR group with a NOT toggle on groups and predicates; each predicate row: field select (two optgroups: "Event" — type/database/record/payload fields per trigger type; "Record property" — properties of the scoped database) + operator select (filtered by the field's value type — the comparators' type knowledge from TASK-0111 metadata) + value input (type-aware: select dropdown for SELECT/STATUS options, user picker for PERSON, date input, number input, text); groups indent visually; "Add predicate", "Add group", delete per row/group; complexity guard: the 5th nesting level / 101st node → inline warning + block save (matches server). (d) Action section: ordered list (drag to reorder, 1..50; "Add action" menu from ACTIONS metadata — stubs CALL_INTEGRATION / RUN_AI_ACTION show a "(Phase 13)"/"(Phase 12)" badge and are savable as DRAFT only — activating an automation containing a stub action → warning dialog "These actions will fail until Phase 12/13; activate anyway?"); per- action card: type icon + name, config form per type (from the schemas' metadata — UPDATE_RECORD: record picker defaulting to "From trigger", property value editor with type-aware inputs; CHANGE_STATUS: property select restricted to STATUS/SELECT + option select; SEND_EMAIL: to/ subject/body with an interpolation autocomplete popup (typing {{ lists the 7 key families); SEND_WEBHOOK: url + headers editor + payload JSON editor with interpolation popup; CREATE_PAGE: title + parent select; CREATE_NOTIFICATION: to/type/body/link; ASSIGN_USER: PERSON property + user picker (or "From trigger user"); stubs: their minimal forms), per-action footer: retries (1..8 number) + timeout (seconds, 1..300) inputs. (e) Footer: "Save as draft" / "Save and activate" (validate: ≥ 1 trigger, ≥ 1 action, non-stub for activation, scope consistency — server 422 errors mapped to inline fields); on save → close, toast, list refreshes.
  5. Preview/dry-run (dev only, NEXT_PUBLIC_FEATURE_AUTOMATION_DRYRUN=true or NODE_ENV !== "production"): in the editor, "Preview with sample record" — picks the first record of the scoped database (or a synthesized one if none), shows: which trigger matched (fixed: the selected trigger), condition evaluation result with each predicate's true/false highlight (calls a test-only preview endpoint POST /api/v1/automations/:id/preview — NODE_ENV=test/dev gated — that runs TASK-0111's evaluator read-only, NO actions executed), and a dry-run of each action (executors called with a dryRun: true flag added to the executor interface in this task: real executors perform ALL validation + read steps but skip the write/emit and return { dryRun: true, would: <description> }; SEND_WEBHOOK dry-run shows the exact request that would be sent). Dry-run results render in the drawer with per-step ✓/✗.
  6. Run now: detail-view button (any status except ARCHIVED) → confirm (shows "Runs with no trigger event; conditions that reference the trigger record will not match" when the automation is record-scoped) → POST .../run → toast with run link → history tab shows the new run (poll the run endpoint 2 s × 30 s for the live status transition, then settle).
  7. States/a11y: loading skeletons (list, history), empty states (list, history for a never-run automation: "No runs yet"), error banners + retry (runs fetch), 429 on heavy polling (backoff 5 s); keyboard: editor is fully tab-navigable, condition builder rows are role="group" with labelled controls, drawer is focus-trapped with Esc close, status pills have text (not color-only), live region announces run status changes.

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

None.

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done