.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-12-ai.mddocs/tasks/phase-12-ai.md← tasks104,375 B← Back to the app

Phase 12 — AI

Purpose: Task specs for Phase 12 of the AI Software Factory build. This phase completes the AI layer: provider-agnostic gateway, Ask AI chat, writing tools, database AI, RAG workspace search, and AI page generation via structured tool calls, closed out by an E2E + security suite.

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

  • Provider swap changes zero app code.
  • RAG answer cites sources.
  • Tool-call generation creates valid pages/databases (integration test).
  • Per-workspace token budget enforced.

Task range: TASK-0121 … TASK-0126 (6 tasks — this agent owns exactly TASK-0121..TASK-0126; TASK-0096..0120 are owned by other agents). Note: the phase brief lists seven work items; the AI page generation item and the cross-cutting "E2E + security suite" item are combined into TASK-0126 because the suite asserts page generation alongside every other capability.

Task Table

ID Title Estimate
TASK-0121 AI gateway: provider abstraction, streaming, tool calling, usage accounting, budgets L
TASK-0122 Ask AI chat: conversations, streaming, history, error states M
TASK-0123 Writing tools: summarize / rewrite / improve / translate with accept-diff M
TASK-0124 Database AI: generate database, extract data, generate formula L
TASK-0125 RAG workspace search: chunking, embeddings, hybrid query, cited answers L
TASK-0126 AI page generation + Phase 12 E2E & security suite L

Phase Completion Checklist


TASK-0121

Title

AI gateway: provider abstraction, streaming, tool calling, usage accounting, budgets

Objective

Complete the AiGateway in packages/ai and wire it into the API and worker so that chat (streaming), embeddings, and tool-calling are available behind one unified, provider-agnostic interface; enforce per-workspace monthly token budgets with a clean AI_BUDGET_EXCEEDED error and admin notification.

Background

packages/ai is scaffolded with a gateway stub and provider adapters for OpenAI, Anthropic, Google, and OpenAI-compatible endpoints. apps/api/src/modules/ai and the worker ai queue (apps/worker/src/queues/ai) exist but are not wired to the gateway. The Prisma schema already has AiMessage.tokensIn/tokensOut (tokens ledger) and Workspace.usageLimits Json (budgets). Provider selection must be configuration-only (env AI_PROVIDER, AI_BASE_URL, AI_API_KEY, AI_MODEL) with per-workspace override in workspace.settings.aiConfig, so swapping providers never touches app code (ROADMAP exit criterion).

Requirements

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done


TASK-0122

Title

Ask AI chat: conversations, streaming, history, error states

Objective

Ship the Ask AI experience: a conversation panel in the page toolbar plus a command-palette entry, streaming answers, page/block/database context binding, and full conversation history management with correct error states.

Background

TASK-0121 provides AiGateway. ai_conversations / ai_messages tables exist with AiConversation.context Json (the binding slot for {pageId, blockId, databaseId}) and AiMessage.toolCalls/toolResults Json (tool-call display). The command palette already has an "ask AI" stub entry (PRODUCT-SPEC §27 lists ask AI among palette actions). The search page UI shell from Phase 8 is the place for the workspace-level entry point (TASK-0125 builds the RAG answer path; this task builds the generic chat path).

Requirements

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done


TASK-0123

Title

Writing tools: summarize / rewrite / improve / translate with accept-diff

Objective

Add AI writing tools to the selection toolbar and block context menu that return a suggested-edit diff the user Accepts (applies as a normal block update) or Discards, with per-call token cost logging.

Background

The editor (Phase 4) provides the selection toolbar and block context menu. Blocks are updated through the existing block services (permission-checked, realtime-synced, fractional positions unchanged). TASK-0121/0122 provide the gateway, budget guard, and SSE plumbing. Per the product spec §21, the four actions are: summarize, rewrite, improve writing, translate (with language pick).

Requirements

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Design Decisions (recorded per AGENTS.md §29 — conflicts resolved toward the architecture)

  1. No block-level permission scope exists (authorize.ts ResourceScope is workspace/teamspace/database/page/record/file; roles are workspace-wide). The spec's "block the user cannot edit (view-only) → per-block 403" is therefore modeled as per-PAGE granularity: ai/writing accepts blockIds from one or more pages in the workspace (each page is VIEW-rechecked server-side — the context is assembled per page); pageId in the request is the primary/anchor page. Apply re-checks EDIT per block's PAGE via the normal BlockService path, so a block on a view-only page fails with 403 {reason:"requires_EDIT"} while blocks on editable pages apply. The UI (single-page selection) never exercises the cross-page path; the integration test does (it is the permission boundary's real shape).
  2. Version CAS is added to BlockService.update as an optional baseVersion field: updateMany({ where: { id, version: baseVersion } }) → count 0 → 409 CONFLICT { reason: "stale_version", actualVersion }. Omitted baseVersion keeps today's unconditional increment (full backward compatibility — the existing PATCH /blocks/:id and editor paths are unchanged). The writing apply path always sends the suggestion-time block.version, which is what makes the spec's 409-rebase contract work.
  3. Structured output = a generateBlocks tool call (the spec's named tool), sent with toolChoice: { name: "generateBlocks" }. The schema ({ blocks: [{ blockId, newText }] }) is shared between the gateway tool declaration and the server-side zod validator (one exported const). The MockProvider gets a deterministic writing mode: when req.metadata?.mode === "writing" it skips the prose echo and emits ONE toolCallDelta generateBlocks with deterministic per-block transforms (summarize → Summarized: <first 80 chars of first selected block>; rewrite → Rewritten: <text>; improve → Improved: <text>; translate → Translated[<lang>]: <text>), reusing the existing failAfterDeltas/ failStatus outage machinery so mid-stream outages work identically. Real providers return tool calls through the same chunk contract.
  4. Hidden per-call conversations use title writing:<action> — a distinct prefix from the existing HIDDEN_LEDGER_PREFIX ("ai-usage:") — and both are filtered out of the panel's conversation list (the filter becomes title NOT LIKE 'ai-usage:%' AND title NOT LIKE 'writing:%').
  5. Shared ai.calls rate limiter (spec: "30 writing calls/min per user — shares the AI cap from TASK-0122 — implemented as one shared ai.calls limiter"): the per-user Redis zset window is extracted to a shared helper (ai-rate-limit.ts, key ai:calls:rl:<userId>, 30/60 s) used by BOTH chat and writing; the chat behavior is unchanged (same key shape family, same count, same degradation-open policy).
  6. Multi-block selection is NEW UI (the Phase-4 editor has single-row selection only; the spec's "selection toolbar" does not exist yet). page-view gains a multi-select mode: ⌘/Ctrl+click (or Shift+click range) adds rows to a selection set; a floating toolbar (selection-toolbar.tsx) appears above the selection with the AI menu (the four actions + the translate language picker) and a Clear button. Single-click keeps the existing single-selection behavior (backward compatible); the block context menu's "Ask AI" becomes a submenu (Summarize / Rewrite / Improve writing / Translate → language) for a single block.
  7. Apply = per-block BlockService.update in order (NOT batch): the spec's apply contract is per-block failure isolation (one 403/409 must not abort the others) + per-block audit/realtime rows ("audit shows the block updates, not a special AI write"). batch is all-or-nothing with a page-level CAS — the wrong unit for this. Each successful block emits the normal block.updated audit + page.updated realtime event (the E2E second-tab sync comes free).
  8. Suggestion persistence: the assistant row's toolResults carries { blocks: [{ blockId, newText, version, text (original) }], action, language, droppedIds: string[] }; suggestionId = that message id (sent by the done frame). Apply re-validates each requested {blockId, newText} pair against the stored suggestion (client cannot smuggle edits — spec §Security) and re-checks EDIT per block at apply time.
  9. Block content has TWO shapes; the writing path reads and writes both. The web InlineEditor round-trips a block's content as an HTML string (its MVP storage model — toHtml returns strings verbatim and the save path sends editor.getHTML()), while the API's canonical shape is an object { text } (what the REST create/update DTOs, import, and the AI apply path use). TASK-0123 found this mismatch at the writing boundary and made the writing path shape-aware in both directions (minimal, read/shape-preserving; no storage-model change):
    • Read — prompts.ts blockText now also reads a string (HTML) content (stripHtml), so blocks typed through the web (string content) reach the writing prompt with real text instead of empty.
    • Write — the apply path preserves the block's existing shape: an object block gets { ...content, text: newText } (unchanged); a string (HTML) block gets "<p>newText</p>" (the editor's own <p> markup) — NOT the old { ...("string"), text }, which would spread a string primitive into an index-keyed object and corrupt the content.
    • Render — inline-editor.tsx toHtml now renders an object { text } / { title } block as an escaped <p>text</p> (previously it returned "" for any non-string, so every API-/import-/AI-authored block displayed blank). The stored text is escaped — it is untrusted data, never markup. These three make the writing E2E (seed via API as {text} → web renders it → apply writes back → web re-renders → realtime converges) actually pass. They do NOT unify the two content models (a larger Phase-4 concern).
  10. Pre-existing (OUT OF SCOPE for TASK-0123) content/batch divergence — recorded per §29, not fixed here. The web BlockBatchQueue flushes typing as { op:"update", blockId, patch:{content} }, but the API BatchBlocksDto only accepts op ∈ {upsert, delete, move} — so the web's debounced batch save is rejected 400 ("Invalid discriminator value"), and the InlineEditor blur fallback sends a string content that UpdateBlockDto (z.record(z.unknown())) also rejects. This predates Phase 12 (present at f9be1cd9, before any AI work) and is why the 14 pre-existing tests/e2e/specs/editor.spec.ts cases (typing / slash / drag / paste / reload) fail on a live stack independent of this task. TASK-0123 does not depend on it (it seeds via the REST API as {text} objects and applies via POST /ai/writing/apply, which writes through BlockService.update — not the web batch queue), so it is left for a dedicated Phase-4 follow-up (align the web batch op names + the content schema to one canonical model) rather than silently re-scoping the editor here.
  11. Fixed a pre-existing browser-only PresenceEmitter bug the writing entry point exposed. presence-emitter.ts defaulted its timer to a DETACHED host reference (— { setTimeout: setTimeout, clearTimeout: clearTimeout }) and called it as this.timer.setTimeout(…). In a real browser Chrome requires window as the receiver, so that call threw Illegal invocation; the unit tests never caught it because they always inject a timer (and Node's global setTimeout is receiver-permissive). It fires on setCaret — i.e. whenever a block gains focus — which is exactly what the writing flow's Ctrl+⌘ multi- select does, so Next's dev error overlay then intercepted the dialog's Accept/Discard clicks (the whole E2E red state). Fix: default the timer to window.setTimeout.bind(window) / window.clearTimeout.bind(window) (Node/SSR keeps the permissive globals). Presence is unrelated to AI; the fix is one constructor line and keeps all 15 presence unit tests green.

TASK-0124

Title

Database AI: generate database, extract data, generate formula

Objective

Add AI generation for databases (from a prompt), data extraction (pasted text/markdown → records in an existing database), and formula generation (description → validated formula expression), all executed through normal domain services under the requesting user's permissions.

Background

Databases exist with the 22 PropertyType values (Prisma enum PropertyType), records-as-pages, and the formula engine in packages/database-engine (safe AST parser/evaluator; functions per ROADMAP Phase 6). Phase 5/6 services (apps/api/src/modules/database/*) create databases/properties/records and validate formulas. The Formula model stores expressions. Per spec, generation uses structured tool calls validated before any write — never raw SQL, never elevated permissions (AGENTS rule 7/18).

Requirements

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Design Decisions (recorded per AGENTS.md §29 — conflicts resolved toward the architecture)

  1. 24-value enum vs the spec's "22 types". The spec (and docs/tasks prose) refer to 22 PropertyType values and name an extraction type. The real Prisma enum has 24 values and no EXTRACTION member. The architecture (the enum + packages/ai/src/schemas/database.ts) is the source of truth — the generated-property allowlist is expressed as GENERATABLE_PROPERTY_TYPES = all 24 minus TITLE (reserved — a database seeds exactly one title property), RELATION + ROLLUP (require a target/relation, created separately), FORMULA (the dedicated formula tool), and BUTTON (requires view-action config). The reject table NON_GENERATABLE_PROPERTY_TYPES carries a per-type reason. This is the spec's "relation/rollup/button/formula/extraction not allowed" mapped onto the real enum.
  2. POST /databases/:id/query was documented but never implemented (pre-existing gap — repaired in this task). The web table view fetches records through useDatabaseRecords → POST /databases/:id/query (documented at docs/api/databases.md §6), but the route did not exist, so every database rendered zero rows (the fetch 404'd). This task added DatabaseQueryService.query + the controller route returning the documented {data:{records|groups, totalCount, nextCursor}} shape (flat or grouped). aggregate is accepted by the DTO but returns no aggregates (no client sends it). Without this repair the E2E generate/extract "records appear" assertions cannot pass.
  3. useDatabaseRecords used the react-query v4 option name getPageParam, but the project is on @tanstack/react-query v5 (repairs a latent crash). v5 requires getNextPageParam. It was dormant while the records fetch 404'd (no successful page → the callback was never invoked). Once the §6 query endpoint (decision 2) made a successful page, react-query called the missing getNextPageParam → options.getNextPageParam is not a function → a page error that crashed the entire database page (the header, AI dropdown, and table all vanished). Fixed to getNextPageParam: (last) => (last as FlatPage).nextCursor ?? undefined (mirrors FileBrowser.tsx), which also removes 2 pre-existing tsc errors.
  4. GET /workspaces/:id returned no role (pre-existing gap — repaired in this task). The web's role gate for the DB page (readOnly = guest) and the AI write-action visibility read workspace.role, but workspaces.service.get() returned the bare workspace (no role) — so workspace.role was dead on every web role check. get() now returns { ...ws, role: membership.role } (the caller's own role — no authorization bypass; the caller is already a member). This is what makes the viewer (GUEST) UI hide the generate/extract/formula actions.
  5. GET /workspaces/:id resolved the :id param as an id only, but the web navigates by slug (pre-existing gap — repaired in this task). The web URL contract is id-OR-slug (useWorkspace passes the raw [workspace] segment; the AI "Open" button and in-app navigation push the workspace slug). workspaces.service.getWorkspace was id-only, so a slug fetch of an owned workspace failed (403 FORBIDDEN not_workspace_member / 404), the workspace layout's isError fallback bounced the user to /w/default, and the freshly-generated database was never opened. getWorkspace now falls back to a slug lookup (slugs are unique in the workspace table) and get() resolves the workspace first, then checks membership on the real id. This is what makes the E2E generate "Open → records visible" assertion pass.
  6. e2e addProperty helper is broken (pre-existing — left alone, worked around). utils/api.ts addProperty does write<{property}> then return body.property, but POST /databases/:id/properties returns the bare property (no wrapper), so the helper returns undefined. The spec uses a local addProp that returns the bare property. Recorded per §29 rather than re-scoping the shared helper here.
  7. The mock generate title derives from the rendered prompt, not the raw prompt. renderGeneratePrompt produces Create a database for: <prompt>…; the mock titles the database with its first 60 chars. The E2E therefore identifies the new database by an id-diff of the workspace database list (not by title match) — robust to the mock's title.

TASK-0125

Title

RAG workspace search: chunking, embeddings, hybrid query, cited answers

Objective

Build retrieval-augmented workspace search: worker-side chunking + embedding of pages/blocks/records into OpenSearch kNN with the ai_embeddings registry, hybrid BM25 + vector + metadata-filtered retrieval ranked by RRF, and streaming LLM answers with citation links — permission-respecting, incrementally updated, with a nightly full re-embed.

Background

packages/search is scaffolded with the chunker (≤1024 tokens, 128-token overlap) and the RRF ranker; the Phase 8 indexer already writes search_documents with a permissions visibility snapshot (query-time permission filtering is proven there). OpenSearch is available with a dense-vector-capable index; ai_embeddings (unique per [workspaceId, chunkRef], with vectorId, model, dimension, metadata Json) is the registry. Domain events from packages/shared (page/block/database/record created/updated/deleted) drive indexing. The UI home is the Phase 8 search page + a new /ask route.

Requirements

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Design Decisions (recorded per AGENTS.md §29 — conflicts resolved toward the architecture)

  1. kNN DSL is query.knn.<field> only on OpenSearch 2.15 — the Elasticsearch-style top-level knn (and a top-level field-keyed knn) are both 400. The hybrid is therefore a two-pass query: a BM25 body (the Phase-8 full-text body) and a kNN-only body (query.knn.vector), each filtered by the SAME tenant+visibility bool, fused by RRF. A query.knn body cannot share a request with a bool/full-text query.
  2. The kNN engine is FAISS, not nmslib — nmslib rejects filtered kNN (Engine [NMSLIB] does not support filters); FAISS supports a pre-filter.
  3. FAISS on 2.15 is l2-only — cosinesimil / inner_product / ip are rejected. l2 ≡ cosine ranking for unit-normalized vectors, so the semantic ordering is unchanged.
  4. num_candidates is not accepted in query.knn on 2.15; recall is tuned via the index-level knn.algo_param.ef_search (default 100).
  5. One index, not ai-chunks — RAG merges vectors onto the Phase-8 single factory-content docs (partial _update of vector + vectorModel) and reuses the Phase-8 visibility filter at query time. No second index, no divergent snapshot (the spec's Security section requires Phase-8's filter).
  6. The dev DB is named factory, and its ai_embeddings registry carried 296 old-format rows (whole-doc vectorId = page:<id>, no #c chunks, model text-embedding-3-small). The dev alias was rolled v2 (nmslib) → v3 (FAISS, dim 8) via scripts/opensearch-reindex.ts; the mirror table has NO vector column, so vectors live only in OpenSearch and the re-embed is required post-rollover. The nightly ai:reembed re-writes each doc's vector in place (partial _update), so it is parity-safe.
  7. parseRagSource used a /m flag whose $ matched EVERY line break, truncating a multi-line source block at its first newline (a whole-page body Title\n<fact> yielded only Title). Fixed: the block is anchored to the true string start/end (no /m). packages/ai/src/providers/rag.test.ts adds the multi-line regression test.
  8. assertCan("VIEW") ran before viewerForWorkspace, so a non-member got a 403 FORBIDDEN instead of the documented fail-closed 404 (the search viewer hides workspace existence, consistent with the pages API). Fixed: the viewer resolves first (404 for non-members), then assertCan("VIEW") (belt-and-braces — membership already implies ≥ VIEW).
  9. Dev node apps (API/web/worker) died mid-task; the re-embed was deferred to the worker restart — the queued embed:reembed job ran on restart (296/296 embedded, 0 drift, not blocked).

Definition of Done


TASK-0126

Title

AI page generation + Phase 12 E2E & security suite

Objective

Ship AI page generation (prompt → structured tool calls executed through domain services with the user's permissions) with cost guard, 120 s timeout, and user cancellation; then run the full Phase 12 E2E + security suite and get it green, closing the phase.

Background

Spec §21: "Create a product launch plan" → a page with Objectives, Requirements, Timeline, a Tasks database, Risks, Team, Meeting Notes — generated via structured tool calls (never raw SQL, never elevated permissions). All Phase 12 capabilities (gateway, chat, writing, database AI, RAG) now exist. The phase exit criteria require: provider swap with zero app code, RAG cites sources, tool-call generation creates valid pages/databases (integration test), per-workspace budget enforced. This task adds the last capability and the cross-cutting verification suite.

Requirements

Acceptance Criteria

Backend Changes

Frontend Changes

Database Changes

API Changes

Tests

Security

Documentation

Dependencies

Design Decisions (recorded per AGENTS.md §29 — conflicts resolved toward the architecture)

  1. The plan is a ref/alias-based structured tool list, not raw payloads. The mock (and a real provider) emits createPage / insertBlocks / createDatabase / createRelation calls where later steps reference earlier ones by a string ref (e.g. a database's pageRef). The executor resolves refs → real entity ids as it runs. This is what makes "structured tool calls, never raw SQL, never elevated permissions" concrete: every step is a domain-service call under the requester's identity.
  2. Service-level idempotency memo re-emits the terminal done. The generate route is @SkipIdempotency() (the global interceptor can't replay an SSE stream), so the service keeps a 24 h Redis memo keyed ai:generate:idem:{wsId}:{userId}:{Idempotency-Key}. On a retried key the memo re-emits a single terminal done with the original created-refs (no re-run), so a retried cancelled/slow generation cannot double-create. The memo is written only on success.
  3. Rollback is a createdRefs set walked in reverse, not a transaction. Each successful step records the entities it created; on a failure the executor walks the set in reverse and deletes them through the same domain services (databases via database.remove — which hard-deletes and falls back to pages.trash — pages via pages.trash). It is best-effort (a rollback error is logged, not thrown, so the original failure still surfaces). Only ids this run created are touched, so user-owned content is preserved.
  4. Mock generate mode lives in the single MockProvider (not a 5th adapter). The task's "fifth test-only adapter" is collapsed into the existing mock via a metadata.mode === "generate" branch that early-returns before the normal chat path — one deterministic "product launch plan" plan, a generateFailStep seam (forces an unknown-ref step → the rollback path), and a generateSlowMs seam (plan latency that outlacks the timeout → the watchdog). No production provider list change.
  5. Security suite lives in tests/e2e/specs/ai-security.spec.ts (items 5–8 appended to the existing file), with the golden API-level structure asserted in tests/e2e/specs/ai-generate.spec.ts.
  6. Web tsc baseline is 126 (the 7 pre-existing: context-menu paste never ×4, page-view DraggableAttributes, command-palette .catch ×2) — no new web type errors introduced.
  7. The executor reuses TASK-0124's validateCreateDatabase for the createDatabase step (property types limited to the 22 PropertyType values, zod) rather than a parallel validator.
  8. Timeout is a local timedOut flag raced against isAiAborted (not a promise.race that swallows the provider's own abort), so a hung provider and a client cancel both resolve to the correct terminal frame without a hang.
  9. The /internal/…/ai/mock-generate seam is single-shot per workspace (armed → consumed on the next generate call → cleared), like the TASK-0122 outage arming; the mock is built per gateway call so a running API process is not flipped via process.env.
  10. timeoutMs is a per-call AI_GENERATE_TIMEOUT_MS getter (default 120 000 ms) — the 2 s timeout test overrides it in the integration spec only; the E2E uses the real 120 s cap via the generateSlowMs seam.
  11. Exactly ONE terminal SSE frame per stream (done | cancelled | error), after any number of tool progress frames — the client contract.
  12. The generate route is @SkipIdempotency() (see #2); chat/writing/rag keep the global interceptor.
  13. Records are linked-page titles + RecordProperty rows — a sample record becomes a top-level page (the record's page) plus per-property rows; the record page's title is the record title.
  14. generate: conversations are excluded from the conversation list (a real bug the integration suite caught): the per-call hidden conversation was leaking into listConversations; the GENERATE_CONV_PREFIX filter hides it.
  15. The UI gate is GUEST-only-hidden, not "non-EDIT hidden". The permission matrix (probe-verified) gives MEMBER editPage + generate access (generate 200) while GUEST gets generate 403. So the AC "viewer 403" is satisfied by GUEST, and the UI hides generate only for GUEST via (role ?? "").toLowerCase() !== "guest" (a canGenerate prop on the context menu, default true). Hiding it from MEMBERS would be wrong.
  16. The Home empty-state card ignores the title block. Every page returns a top-level title block (properties.isTitle === true) from ?depth=1; the "empty page" check counts content blocks excluding it (contentBlockCount), or every page would look non-empty.
  17. The generated page is a CHILD of the caller's page. The dialog passes parentId: context?.parentId (the current page), and the endpoint injects it into the FIRST createPage only — so the generated page nests under the caller's page; later createPage steps nest under the generated root; the Tasks database page is a CHILD of the generated page (type "database"); record pages are TOP-LEVEL roots (created by records.createRecord). The golden E2E locates the generated page by the URL id (GET /pages/:id) and finds the Tasks db as its child.
  18. GET /workspaces/:ws/databases returns {id,title,icon} only (no pageId) (the listByWorkspace select), so the golden E2E asserts a FRESH workspace has exactly ONE database (the Tasks one) + its title, rather than matching by page id.
  19. The record DTO properties is a {[propertyId]: value} object map (not an array) — title = {text, marks}, date = {start} — so the golden E2E reads record.properties[duePropId].start for the date assertions.
  20. SELECT options live at config.options (DatabaseProperty.config Json), not a top-level options — the golden E2E asserts the Status select's config.options ⊇ [To Do, In Progress, Done].
  21. The e2e getBlocks helper is broken (it expects {blocks} but GET /pages/:id/blocks returns {data} / {data,tree,total}) — the golden E2E uses local raw-fetch helpers reading .data with ?depth=all. (The shared helper is left as-is; it is out of this task's scope.)
  22. Headless Playwright needs CI=1 / --reporter=list — the default html reporter's open:"on-failure" blocks in a headless run (no display), so the suite is driven with the list reporter for the gate.
  23. The Phase-12 E2E suite is CI-parity only with AI_MOCK_DELTA_DELAY_MS=60. CI's e2e job (.github/workflows/ci.yml) sets AI_PROVIDER: mock, SEARCH_VECTOR_DIMENSION: 8, INTERNAL_TEST_SEAM: 1, and AI_MOCK_DELTA_DELAY_MS: 60. The 60 ms mock pacing is required for TASK-0122's "streams ≥3 updates" assertion (a 7-word answer otherwise arrives in one round-trip and can't be told apart from a single-shot render); a dev API started without it makes that spec flake. The full Phase-12 AI suite was run green under these exact values (36/36, desktop + mobile): ai.spec.ts 8, ai-writing.spec.ts 8, ai-database.spec.ts 8, ai-rag.spec.ts 2, ai-generate.spec.ts 2, ai-security.spec.ts 8.
  24. The @factory/web#lint red is a recorded baseline, not a TASK-0126 regression. next lint (Next 14) fails at config-parse under the installed ESLint 9 ("Unknown options: useEslintrc, extensions, resolvePluginsRelativeTo, …") before reading any source, and apps/web has no .eslintrc; only apps/web defines a lint script in the workspace, so turbo run lint fails on web alone. It was already red before TASK-0126 (the recorded "web next lint red" baseline) and this task touched no lint/next config, so no new lint issues are introduced; fixing the Next 14 / ESLint 9 next lint incompatibility is a separate work item.
  25. The generate mode FORCES the generatePlan tool and repairs the model's non-conforming output (recorded decision 2025, OSS qwen3.8-27B field fix). Root causes found when a real OSS reasoning model replaced the mock: (a) with NO tool declared the model streamed ~323 s of prose and never emitted a tool call (the "Generation took too long and was cancelled" the user saw); (b) the plan is large (~15k completion tokens, ~9.5k of reasoning) so the gateway's budgets.maxOutputTokens clamp (default 4 096) truncated it mid-stream, and the model's ~39 s tail byte gap tripped the default 30 s stream-idle cap. Fix: chatPlan now sends the forced generatePlan tool (toolChoice:{name:"generatePlan"}) + the plan shape in the tool description/system prompt (the @factory/ai tool validator allows only type/properties/required/items/enum/description, so the discriminated-union plan can't be expressed — args is a loose object and the strict parseGeneratePlan zod is the enforcement point); and four optional env knobs override the built-ins (AI_GENERATE_TIMEOUT_MS whole run, AI_STREAM_IDLE_MS byte-gap, AI_STREAM_TIMEOUT_MS TTFB cap, AI_MAX_OUTPUT_TOKENS output clamp — see docs/api/ai.md §10). normalizeGeneratePlanArgs repairs, BEFORE validation: tools-as-JSON string, placeholder refs ($page/root/…), block text hoisted into content.{text}, and SEEDED Title/Status db properties dropped (the service always ships them — a re-declaration fails validateCreateDatabase and the whole plan). Every other shape problem still fails the strict schema. The executor resolves an omitted insertBlocks pageRef to the FIRST page even when it carries a named ref (the schema contract — before this, a model that named its first page broke body insertion with "references page (root) before it is created").
  26. The generate persist calls capture the resolved chat model ONCE up front. The earlier code re-resolved via this.gateway.resolve(workspaceId) in the post-stream persistAssistant calls, which re-reads the 30 s TTL workspace-row cache; a model call longer than 30 s expired it, resolve threw 404 ("Workspace not found"), and — thrown from inside the SSE catch block AFTER the headers were already sent — the exception filter's JSON write crashed the process with an uncaught ERR_HTTP_HEADERS_SENT (the "server hang" the user hit on retry). The model name is a static property of the resolved config for the call's lifetime, so it is captured once before the model call; the same fix made the ai.generation_failed audit + a step-failure warn log carry the step error for diagnosis.

Definition of Done