Marvia-05
Persistence is a contract, not a database
PostgreSQL first. The host is a hosting provider — nothing is built around it. Applications call services, services call repositories, repositories call Drizzle. Nothing else touches the database.
Domains
17
Tables
32
Repositories
17
Schema version
1.1.0
Applications → Services → Repositories → Drizzle ORM → PostgreSQL
Base columns
Ten columns on every table, no exceptions
Soft delete, audit and versioning are structural. A table without these columns is a defect, not a shortcut.
Domains
One domain, one repository
Every resource belongs to exactly one workspace. workspaceId is an explicit argument on every repository method, so cross-workspace leakage is impossible by construction.
| Domain | Tables | Repository | Isolation |
|---|---|---|---|
| Authentication | users, sessions | UserRepository | agnostic |
| Workspace | workspaces, workspace_members | WorkspaceRepository | workspace |
| Project | projects | ProjectRepository | workspace |
| Snapshot | snapshots | SnapshotRepository | workspace |
| Report | reports, report_history | ReportRepository | workspace |
| Credits | credit_wallets, credit_transactions | CreditRepository | workspace |
| Billing | payments, subscriptions | BillingRepository | workspace |
| Content | content_entries | ContentRepository | workspace |
| Extension | extensions, extension_versions, installed_extensions, extension_configs | ExtensionRepository | workspace |
| Marketplace | marketplace_extensions, marketplace_reviews, marketplace_purchases | MarketplaceRepository | global |
| Connector | connector_accounts, connector_sync_history | ConnectorRepository | workspace |
| Knowledge | knowledge_objects, knowledge_categories, knowledge_tags, knowledge_relations | KnowledgeRepository | workspace |
| Settings | settings | SettingsRepository | workspace |
| AI Provider | ai_providers, ai_settings | AiRepository | workspace |
| Activity | activity_timeline | ActivityRepository | workspace |
| Audit | audit_logs | HistoryRepository | workspace |
| Notification | notifications | NotificationRepository | workspace |
Strategies
How the data layer defends itself
Soft delete first
deleted_at plus deleted_by. Features call softDelete and restore; only the retention job purges.
Audit first
Every write carries an AuditContext and appends a hash-chained audit_logs row. The chain is verifiable.
Versioning
A version column on every row, plus a history table where rollback matters.
JSONB discipline
Metadata, AI outputs and extension configs only. Structured data stays relational.
Workspace isolation
workspace_id is an explicit argument on every repository method — never inferred from ambient state.
Provider agnostic
Billing and connector providers are data, not branches. PostgreSQL is the contract; the host is replaceable.
Migrations
Append only, forever
Every migration is committed. A shipped migration is never edited — schema change means a new file and a semantic version bump.
Foundation schema: auth, workspace, project, snapshot, report, credits, billing, content, extension, marketplace, connector, knowledge, settings, AI, activity, audit and notification tables with base columns and isolation indexes.
Production data layer: schema_migrations audit, auth sessions, workspace members, extension installations and generic settings, credit wallets and ledger, plans and subscriptions, integration connections with vault references, admin audit logs, plus workspace-isolation RLS policies and indexes.
Retention (days)
- auditLogs30
- softDeleted7
- syncHistory30
- notifications90
Query budget (ms)
- workspaceOverview100
- usageSeries150
- latestReportPerProject100
Reads
Materialised views and seeds
Reads that cannot meet their latency budget move to a view refreshed by the cron worker, never to ad-hoc caching in the request path.
mv_workspace_usage_daily
Daily credit and snapshot usage per workspace.
refresh hourly
mv_marketplace_rankings
Downloads and rating rollup for marketplace listings.
refresh daily
mv_report_freshness
Latest report per project with age in hours.
refresh hourly
development seed
Minimum viable data to boot the app locally.
- users: one owner account
- workspaces: one workspace owned by that user
- workspace_members: owner membership row
- credit_wallets: wallet with a 1 000 credit grant
- settings: workspace defaults namespace
demo seed
A populated workspace for demos and screenshots.
- everything in the development seed
- workspace_members: admin, member and viewer accounts
- projects: three projects with realistic URLs
- snapshots: five completed snapshots per project
- reports: one seo-audit report per project with metrics and issues
- content_entries: blog, docs and faq entries
- extensions + installed_extensions: two installed extensions
- credit_transactions: grants, usage and one refund
- notifications: a mixed unread inbox
- activity_timeline: thirty events across domains
Security
Secrets never live in tables
- API keys and tokens are stored as references to a secrets manager, never as plaintext columns.
- Sensitive fields are encrypted at rest by the driver, not by feature code.
- RBAC is enforced through workspace_members.role — never on the user or profile record.
- audit_logs is append only and hash chained, so tampering is detectable.