Platform · Data
One data model for every app, so records never drift apart
One3 is designed so that every app shares the same tenancy, identity, party and audit model. A person exists once, each record has one owner, and data can be changed, erased and restored safely.
Where data lives
| Store | What lives there | Planned stage |
|---|---|---|
| PostgreSQL 18 (primary, per cell) | All system-of-record data: platform, identity, core entities, kernel logs and app schemas | W0 |
| PostgreSQL read replica | Read-heavy lists, reports and exports | W1 (estimate) |
| PgBouncer | Connection pooling in transaction mode | W0 |
| Valkey | Session, entitlement and permission caches, rate limits, short-lived locks | W0 |
| S3-compatible object storage | Files, media, renders, exports, archived partitions and backups | W0 |
| pgvector (inside Postgres) | Knowledge and content embeddings | W0 |
| NATS JetStream | Domain events published from the outbox | W0 |
| Temporal | Durable workflow and agent-run state | W0 |
| ClickHouse, OpenSearch, Qdrant | Analytics, full-text search and large vector sets | Deferred to W3+, only if measured needs require them |
Postgres is the only system of record
Every other store is a cache, index, projection or archive. Each one can be rebuilt from Postgres, the event log or object storage. Extra stores are added only when the plan's complexity budget allows.
Tenant isolation built into the schema
The schema contract makes cross-tenant mistakes hard by design:
- Every tenant row carries
tenant_id, and primary keys start with it. A query cannot forget the tenant without also losing its index. - Foreign keys are composite
(tenant_id, x_id). The database itself makes cross-tenant references impossible, not just unlikely. - Row-level security is forced on every tenant table and fails closed. A missing tenant context returns zero rows.
- Money is stored as whole minor units with a currency, never as floats, so invoices, payroll and ROI figures stay exact.
- One Postgres schema per app area, such as
crm,desk,peopleandbilling, so ownership and reviews are clear.
The plan also defines CI checks that apply all migrations to a fresh database, compare it to the code schema, lint the contract, run a two-tenant leak test and check query plans on a large seeded tenant.
The ownership rule: only the owner module writes an entity, through its Actions. Other apps read through the owner's exported queries and views, or react to its events.
Every entity has one owner
| Entity | Owner module | Main consumers |
|---|---|---|
| Tenant, workspace, domain | platform.tenancy | Every app |
| Plan, subscription, entitlement, seat, usage | platform.subscriptions / platform.metering | Kernel Action Bus, Billing, FinOps, Admin |
| User account, membership, role, agent identity, API key, SSO | iam | Kernel, all UIs, agents |
| Party, person, organisation, contact point, address | core.parties | CRM, Desk, People, Billing, Marketing, agents, portals |
| Consent record | core.consent | Marketing, Desk, outbound agents, Avatar Rights Registry |
| Action log, audit, approvals, reversal tokens | kernel.actions / kernel.audit | Trust & Audit, Approvals Inbox, Observability |
| Leads, contacts, accounts, deals, pipelines | crm | Marketing, Desk, Billing, agents, dashboards |
| Tickets, queues, SLAs | desk | CRM, Knowledge, agents |
| Employees, candidates, attendance, leave, payroll inputs | people | IAM, Billing, HR assistants (advisory only) |
How custom fields work
Tenants can add their own fields without a schema migration. Fields that are queried often can be promoted for speed.
- 1
Define
A tenant admin, or the Apps builder, creates a field definition with type, validation, options, data classification and a translated label. No migration is needed.
- 2
Store
The owning Action writes the value into the record's custom JSON column and validates it against the definition.
- 3
Query
Filters on custom fields use parameterised queries. This is fine for small tenants and occasional filters.
- 4
Promote
When a field is marked as indexed, or usage shows frequent filtering, a job adds a generated column and an index, created without blocking traffic.
- 5
Stay within limits
The plan caps custom fields at 200 per object type per tenant, and promoted fields at 25 per object type in pooled tenancy. Larger needs move to bridge or silo tiers.
Events and the outbox
Every Action writes its business change, its action log entry, any reversal token and its outgoing events in the same Postgres transaction. A relay then publishes the events to NATS JetStream. An event is never lost because a service crashed between saving and publishing.
Events follow a CloudEvents-aligned envelope and a simple naming rule: <schema>.<entity>.<past_tense_verb>, such as billing.invoice.paid. Payloads carry IDs and the changed facts, with no more personal data than consumers need. Every event has a JSON Schema, and CI checks emitted payloads against it.
Delivery is at-least-once and the bus does not preserve order. So every consumer follows four rules: track the last applied version per record, apply only the next version, buffer a gap for a short wait (30 seconds in the plan), and reconcile from the owning app's API if the gap does not close. Gaps are reported as signals, not hidden.
An event that records an effect in another system, such as a message sent or a payment made, is published only once that effect is observed.
Retention defaults
| Data | Hot (Postgres) | Archive (object storage) |
|---|---|---|
| Action log | 13 months | Up to 7 years, by plan tier |
| Audit events | 13 months | 7 years, or longer where law requires |
| Activity timeline | 24 months | 5 years |
| Usage records | 13 months | 8 years as billing evidence (verify accounting retention) |
| Message logs | 13 months | Per tenant setting |
| Attendance punches | 24 months | Per labour-law requirement (verify) |
| LLM traces | 30–90 days (estimate) | Aggregates only |
How erasure requests are handled
DPDP data-principal erasure is designed to run as a durable workflow. Retention values above are plan defaults; tenant contracts and law may need different values, so verify with counsel.
- 1
Find everything
Resolve the party and all of its role records across apps, using the ownership matrix.
- 2
Check blockers
Look for statutory retention such as payroll and tax invoices, legal holds and open disputes. Blocked fields are minimised or pseudonymised, and the reason is recorded.
- 3
Delete or pseudonymise
Names, contact points, addresses, notes, files, custom field values, knowledge chunks, agent memory and avatar or voice likeness assets.
- 4
Handle archives
Erasure is recorded in a ledger. Archives are rewritten, or kept encrypted with the person's data key destroyed. The legal acceptability of this approach is marked for verification.
- 5
Rebuild derived data
Read models and caches are rebuilt. Per-tenant AI adapters trained on erased data are scheduled for retraining.
- 6
Keep evidence
Evidence is stored as a privacy audit event and a completion notice is sent.
Backup and disaster recovery
| Control | Planned design |
|---|---|
| Continuous backup | WAL archiving plus base backups, through the managed provider's point-in-time recovery or pgBackRest (verify provider capability) |
| Recovery design targets | RPO of 15 minutes or less and RTO of 4 hours or less at GA; tighter per enterprise tier. These are targets, not measured results. |
| Restore drills | Monthly automated restore into an isolated environment, with row-count and checksum checks |
| Single-tenant restore | Restore one tenant from a point in time into staging, then re-import. Never a full database rollback for one tenant. |
| Object storage | Versioning and cross-region replication for production buckets |
| Encryption | At rest, plus per-tenant keys for sensitive personal fields |
| Backup access | Break-glass role, with approval and an audit record for every access |
The data layer in plan numbers
- tenancy tiers on one schema: pooled, bridge, silo, sovereign
- 4
- schema validation steps in CI
- 8
- schema lint rules, L1 to L12
- 12
- rules every event consumer follows
- 4
- custom fields allowed per object type per tenant
- 200
Questions about data architecture
Does each app get its own database?
No. A tenant subscribes to apps and modules, and entitlements switch them on. No database or subdomain is created per app. All apps share one model with one schema per app area.
What happens to my data if I switch a module off?
It stays. The module becomes read-only for the retention or grace window and can be exported. It is deleted only as your contract and the retention rules allow.
Can a dedicated or on-premise tenant use the same model?
Yes, that is the design. Pooled, bridge, silo and sovereign (One3 Box) tiers all use the identical schema and migrations, with tenant keys and row-level security kept. Tenants can move between tiers without schema changes.
Will schema changes cause downtime?
They are designed not to. Changes follow expand/contract rules: add first, backfill in batches, switch reads, and remove old columns only after a full release with no use. Migrations have lock and time limits so they never block traffic.
How are duplicate records handled?
Duplicates are detected by name similarity and matching contact points, and shown as merge suggestions. Merges above risk thresholds are never automatic, and a merge can be undone.
Can AI agents act on poor-quality data?
Each party carries a data quality score. The Context Engine attaches it to context, and below the tenant's threshold, agents may not recommend or act on that data.
Talk to us about your data
Ask how One3 plans to model, isolate, retain and recover your business data, and how it fits your compliance needs.