Database Schemas
Database schema reference for all NNO services using Cloudflare D1.
Database Schemas
NNO uses Cloudflare D1 (SQLite) for persistence. Each service owns its own isolated database. Two services use Drizzle ORM with a typed schema file; the remaining services manage their schema via raw SQL migration files applied through wrangler.
| Service | Database Name | ORM |
|---|---|---|
| Registry | registry-db | Drizzle ORM |
| IAM | iam-db | Drizzle ORM |
| Billing | billing-db | Raw D1 migrations |
| Provisioning | provisioning-db | Raw D1 migrations |
| Stack Registry | marketplace-db | Raw D1 migrations |
Registry Database (Drizzle ORM)
Source: services/registry/src/db/schema.ts
The Registry is the authoritative store for all platform catalog data: platforms, their tenant entities, provisioned stacks, Cloudflare resources, feature activations, DNS routing, and audit history.
erDiagram
platforms ||--o{ entities : "has"
platforms ||--o{ stacks : "owns"
platforms ||--o{ platform_build_state : "tracks"
platforms ||--o{ provision_jobs : "spawns"
platforms ||--o{ audit_log : "logs"
platforms ||--o{ platform_lifecycle_events : "records"
platforms ||--o{ onboarding_sessions : "tracks"
entities ||--o{ stacks : "tenantId"
entities ||--o{ resources : "assigned to"
entities ||--o{ feature_activations : "activates"
stacks ||--o{ resources : "contains"
stacks ||--o{ dns_records : "routes"
resources ||--o{ secrets : "holds"
resources ||--o{ dns_records : "target of"
platforms ||--o{ custom_domains : "owns"
dns_records ||--o{ custom_domains : "backed by"platforms
The top-level tenant record. Every platform has a unique slug and maps to a Cloudflare account.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
name | TEXT | NOT NULL | Display name |
slug | TEXT | NOT NULL, UNIQUE | URL-safe identifier |
status | TEXT | NOT NULL, default active | active | suspended | cancelled | deleted |
tier | TEXT | NOT NULL, default starter | starter | growth | scale |
cf_account_id | TEXT | Linked Cloudflare account ID | |
repo_name | TEXT | GitHub repository name | |
stripe_customer_id | TEXT | Stripe customer reference | |
owner_user_id | TEXT | IAM user who owns the platform | |
cancelled_at | INTEGER | Unix ms timestamp of cancellation | |
cancellation_reason | TEXT | Human-readable cancellation reason | |
suspended_at | INTEGER | Unix ms timestamp of suspension | |
suspension_reason | TEXT | Human-readable suspension reason | |
trial_ends_at | INTEGER | Unix ms timestamp when trial expires | |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
deleted_at | INTEGER | Unix ms soft-delete timestamp |
Indexes: idx_platforms_slug (slug), idx_platforms_status (status)
entities
Tenant-scoped sub-units within a platform (e.g. organisations, projects, workspaces). Entities can be hierarchical via parent_id.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Parent platform reference |
parent_id | TEXT | Parent entity for nesting (nullable) | |
type | TEXT | NOT NULL | Entity type (e.g. tenant, workspace) |
name | TEXT | NOT NULL | Display name |
slug | TEXT | NOT NULL | Platform-scoped slug |
status | TEXT | NOT NULL, default active | active | inactive | deleted |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
deleted_at | INTEGER | Unix ms soft-delete timestamp |
Indexes: idx_entities_platform_slug UNIQUE (platform_id, slug), idx_entities_platform (platform_id), idx_entities_parent (parent_id)
stacks
A named grouping of Cloudflare resources deployed for a tenant entity. Each platform has at most one default stack.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
tenant_id | TEXT | NOT NULL, FK → entities.id | Owning entity |
platform_id | TEXT | NOT NULL, FK → platforms.id | Owning platform |
name | TEXT | NOT NULL | Stack display name |
is_default | INTEGER | NOT NULL, default 0 | Boolean — 1 for the platform's default stack |
template_id | TEXT | Source stack template ID (nullable) | |
repo_name | TEXT | Linked GitHub repository | |
status | TEXT | NOT NULL, default active | active | inactive | deleted |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_stacks_tenant (tenant_id), idx_stacks_platform (platform_id), idx_stacks_platform_tenant (platform_id, tenant_id), idx_stacks_platform_default UNIQUE (platform_id, is_default) WHERE is_default = 1
resources
Individual Cloudflare resources (Workers, D1 databases, R2 buckets, KV namespaces, etc.) provisioned for a stack.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Owning platform |
entity_id | TEXT | NOT NULL | Owning entity |
stack_id | TEXT | NOT NULL, FK → stacks.id | Parent stack |
resource_type | TEXT | NOT NULL | worker | d1 | r2 | kv | pages | etc. |
service_name | TEXT | NOT NULL | Logical service name within the stack |
environment | TEXT | NOT NULL | production | staging | development |
cf_name | TEXT | NOT NULL, UNIQUE | Cloudflare resource name |
cf_id | TEXT | Cloudflare-assigned resource ID (nullable until provisioned) | |
cf_url | TEXT | Deployed URL (nullable) | |
status | TEXT | NOT NULL, default pending | pending | provisioning | active | failed | deleted |
provision_job_id | TEXT | Linked provisioning job (nullable) | |
config | TEXT | JSON resource configuration blob (nullable) | |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
deleted_at | INTEGER | Unix ms soft-delete timestamp |
Indexes: idx_resources_platform, idx_resources_entity, idx_resources_stack, idx_resources_cf_name, idx_resources_status, idx_resources_type_env (resource_type, environment)
feature_activations
Tracks which feature packages are active for a given entity and environment. Optionally managed by a parent stack instance.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Owning platform |
entity_id | TEXT | NOT NULL | Target entity |
feature_id | TEXT | NOT NULL | Feature package identifier |
feature_version | TEXT | NOT NULL | Semver version of the activated feature |
environment | TEXT | NOT NULL | production | staging | development |
status | TEXT | NOT NULL, default activating | activating | active | deactivating | inactive | failed |
activated_at | INTEGER | Unix ms activation timestamp | |
deactivated_at | INTEGER | Unix ms deactivation timestamp | |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
stack_instance_id | TEXT | Parent stack instance (NULL = standalone activation) |
Indexes: idx_feature_activations_unique UNIQUE (entity_id, feature_id, environment), idx_feature_activations_entity, idx_feature_activations_feature, idx_feature_activations_status, idx_feature_activations_stack
secrets
Tracks the presence (not value) of secrets bound to provisioned resources.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
resource_id | TEXT | NOT NULL | Parent resource reference |
secret_name | TEXT | NOT NULL | Cloudflare secret binding name |
status | TEXT | NOT NULL, default missing | missing | set | rotating |
last_set_at | INTEGER | Unix ms timestamp when last updated | |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_secrets_resource_secret UNIQUE (resource_id, secret_name), idx_secrets_resource, idx_secrets_status
platform_build_state
One row per platform per environment — tracks the latest Cloudflare Pages deployment state.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Owning platform |
environment | TEXT | NOT NULL | production | staging |
repo_name | TEXT | NOT NULL | GitHub repository name |
last_commit_sha | TEXT | SHA of the last deployed commit | |
cf_deployment_id | TEXT | Cloudflare Pages deployment ID | |
build_status | TEXT | NOT NULL, default unknown | unknown | queued | building | success | failed |
build_url | TEXT | Cloudflare Pages deployment URL | |
triggered_by | TEXT | Actor who triggered the build | |
triggered_at | INTEGER | Unix ms trigger timestamp | |
completed_at | INTEGER | Unix ms completion timestamp | |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_build_state_platform_env UNIQUE (platform_id, environment)
provision_jobs
Registry-side record of provisioning operations delegated to the Provisioning service.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Target platform |
entity_id | TEXT | NOT NULL | Target entity |
operation | TEXT | NOT NULL | PROVISION_PLATFORM | ACTIVATE_FEATURE | DEACTIVATE_FEATURE | DEPROVISION_PLATFORM |
status | TEXT | NOT NULL, default pending | pending | running | completed | failed |
payload | TEXT | NOT NULL | JSON job input |
steps | TEXT | JSON array of step results | |
error | TEXT | Error message if failed | |
retry_count | INTEGER | NOT NULL, default 0 | Number of retry attempts |
max_retries | INTEGER | NOT NULL, default 3 | Maximum allowed retries |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
started_at | INTEGER | Unix ms start timestamp | |
completed_at | INTEGER | Unix ms completion timestamp | |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_provision_jobs_platform, idx_provision_jobs_status, idx_provision_jobs_created
dns_records
Cloudflare DNS routing rules that map hostnames to provisioned resources within a stack.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL, FK → platforms.id | Owning platform |
stack_id | TEXT | NOT NULL, FK → stacks.id | Parent stack |
hostname | TEXT | NOT NULL, UNIQUE | Fully-qualified hostname |
target_type | TEXT | NOT NULL | worker | pages | etc. |
resource_id | TEXT | NOT NULL, FK → resources.id | Target resource |
environment | TEXT | NOT NULL | production | staging |
cf_route_id | TEXT | Cloudflare Workers route ID (nullable) | |
status | TEXT | NOT NULL, default pending | pending | active | failed | deleted |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_dns_platform, idx_dns_stack, idx_dns_status
custom_domains
Custom hostnames added by platform operators, backed by a dns_records entry.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL, FK → platforms.id | Owning platform |
dns_record_id | TEXT | NOT NULL, FK → dns_records.id | Backing DNS record |
hostname | TEXT | NOT NULL, UNIQUE | Custom hostname |
target_dns | TEXT | NOT NULL | DNS target (e.g. Workers subdomain) |
cf_hostname_id | TEXT | Cloudflare custom hostname ID | |
ssl_status | TEXT | default pending | pending | active | failed |
status | TEXT | NOT NULL, default pending | pending | active | failed | deleted |
verified_at | INTEGER | Unix ms timestamp of SSL verification | |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_custom_domains_platform, idx_custom_domains_status, idx_custom_domains_dns_record
audit_log
Immutable append-only log of all state-changing operations in the Registry.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Target platform |
actor_id | TEXT | NOT NULL | User or service that performed the action |
actor_type | TEXT | NOT NULL | user | service | system |
action | TEXT | NOT NULL | Action verb (e.g. platform.create, resource.delete) |
entity_type | TEXT | NOT NULL | Type of affected resource |
entity_id | TEXT | NOT NULL | ID of affected resource |
before | TEXT | JSON snapshot before change | |
after | TEXT | JSON snapshot after change | |
metadata | TEXT | Additional JSON context | |
actor_email | TEXT | Resolved actor email | |
ip_address | TEXT | Request IP address | |
user_agent | TEXT | Request user-agent string | |
created_at | INTEGER | NOT NULL | Unix ms event timestamp |
Indexes: idx_audit_log_platform, idx_audit_log_entity_id, idx_audit_log_created, idx_audit_log_action
platform_lifecycle_events
Immutable record of every platform status transition.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Target platform |
from_status | TEXT | NOT NULL | Previous status |
to_status | TEXT | NOT NULL | New status |
triggered_by | TEXT | NOT NULL | Actor ID who triggered the transition |
trigger_type | TEXT | NOT NULL | user | system | stripe |
reason | TEXT | Human-readable reason | |
metadata | TEXT | Additional JSON context | |
created_at | INTEGER | NOT NULL | Unix ms event timestamp |
Indexes: idx_lifecycle_platform, idx_lifecycle_created
onboarding_sessions
Tracks the multi-step onboarding flow for new platforms.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Target platform |
user_id | TEXT | NOT NULL | IAM user completing onboarding |
status | TEXT | NOT NULL, default in_progress | in_progress | completed | expired | failed |
steps | TEXT | NOT NULL | JSON array of step completion state |
stack_template_id | TEXT | Selected stack template (nullable) | |
tier | TEXT | NOT NULL | Selected billing tier |
provision_job_id | TEXT | Linked provisioning job (nullable) | |
current_step | TEXT | Identifier of the active step | |
metadata | TEXT | Additional JSON context | |
started_at | INTEGER | NOT NULL | Unix ms session start timestamp |
completed_at | INTEGER | Unix ms completion timestamp | |
expires_at | INTEGER | Unix ms session expiry timestamp | |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_onboarding_platform, idx_onboarding_user, idx_onboarding_status, idx_onboarding_expires
IAM Database (Drizzle ORM)
Source: services/iam/src/core/schema.ts
The IAM database backs the Better Auth authentication library with extensions for the Admin, Organisation, Two-Factor, and API Key plugins. It also stores NNO-specific roles, permission grants, and auth audit logs.
erDiagram
user ||--o{ session : "authenticates via"
user ||--o{ account : "linked to"
user ||--o{ two_factor : "secures with"
user ||--o{ api_key : "issues"
user ||--o{ member : "joins org via"
user ||--o{ invitation : "sends"
user ||--o{ nno_permission_grants : "granted to"
organization ||--o{ member : "has"
organization ||--o{ invitation : "has"
session ||--o{ audit_authentication : "references"
audit_authentication }o--|| user : "userId"
audit_authorization }o--|| user : "userId"user
Core Better Auth user record, extended with Admin plugin fields, Two-Factor status, and NNO metadata.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Better Auth user ID |
name | TEXT | Display name (nullable) | |
email | TEXT | NOT NULL, UNIQUE | User email address |
email_verified | INTEGER | NOT NULL, default false | Boolean — email verification status |
image | TEXT | Avatar URL (nullable) | |
created_at | INTEGER | NOT NULL | Creation timestamp |
updated_at | INTEGER | NOT NULL | Last-updated timestamp |
role | TEXT | NOT NULL, default user | Admin plugin: user | admin | superadmin |
banned | INTEGER | NOT NULL, default false | Admin plugin: account ban flag |
ban_reason | TEXT | Admin plugin: reason for ban | |
ban_expires | INTEGER | Admin plugin: ban expiry timestamp (nullable = permanent) | |
two_factor_enabled | INTEGER | NOT NULL, default false | Two-Factor plugin: TOTP enabled flag |
metadata | TEXT | JSON user preferences (theme, density, notifications) |
session
Active session tokens. Extended with Admin impersonation and Organisation active-org context.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Session ID |
expires_at | INTEGER | NOT NULL | Session expiry timestamp |
token | TEXT | NOT NULL, UNIQUE | Opaque session token |
created_at | INTEGER | NOT NULL | Creation timestamp |
updated_at | INTEGER | NOT NULL | Last-updated timestamp |
ip_address | TEXT | Client IP address | |
user_agent | TEXT | Client user-agent string | |
user_id | TEXT | NOT NULL, FK → user.id (CASCADE) | Owning user |
impersonated_by | TEXT | Admin plugin: actor user ID for impersonation sessions | |
active_organization_id | TEXT | Organisation plugin: currently active org |
account
OAuth provider tokens and credential storage for external identity providers.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Account ID |
account_id | TEXT | Provider-specific account identifier | |
provider_id | TEXT | NOT NULL | OAuth provider (e.g. github, google, credential) |
user_id | TEXT | NOT NULL, FK → user.id (CASCADE) | Owning user |
access_token | TEXT | OAuth access token | |
refresh_token | TEXT | OAuth refresh token | |
id_token | TEXT | OpenID Connect ID token | |
access_token_expires_at | INTEGER | Access token expiry timestamp | |
refresh_token_expires_at | INTEGER | Refresh token expiry timestamp | |
scope | TEXT | Granted OAuth scopes | |
password | TEXT | Hashed password for credential provider | |
created_at | INTEGER | NOT NULL | Creation timestamp |
updated_at | INTEGER | NOT NULL | Last-updated timestamp |
verification
Short-lived tokens used for email verification and password reset flows.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Verification ID |
identifier | TEXT | NOT NULL | Email address or other identifier |
value | TEXT | NOT NULL | One-time verification code or token |
expires_at | INTEGER | NOT NULL | Expiry timestamp |
created_at | INTEGER | NOT NULL | Creation timestamp |
updated_at | INTEGER | NOT NULL | Last-updated timestamp |
organization
Multi-tenant workspace records. Each Better Auth organisation maps to an NNO platform.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Organisation ID |
name | TEXT | NOT NULL | Organisation display name |
slug | TEXT | NOT NULL, UNIQUE | URL-safe slug |
logo | TEXT | Logo URL (nullable) | |
metadata | TEXT | JSON metadata | |
created_at | INTEGER | NOT NULL | Creation timestamp |
updated_at | INTEGER | NOT NULL | Last-updated timestamp |
member
Organisation membership records linking users to organisations with a role.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Member ID |
organization_id | TEXT | NOT NULL, FK → organization.id (CASCADE) | Parent organisation |
user_id | TEXT | NOT NULL, FK → user.id (CASCADE) | Member user |
role | TEXT | NOT NULL, default member | owner | admin | member |
created_at | INTEGER | NOT NULL | Creation timestamp |
updated_at | INTEGER | NOT NULL | Last-updated timestamp |
invitation
Pending invitations for users to join an organisation.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Invitation ID |
organization_id | TEXT | NOT NULL, FK → organization.id (CASCADE) | Target organisation |
email | TEXT | NOT NULL | Invitee email address |
role | TEXT | NOT NULL, default member | Role to assign upon acceptance |
status | TEXT | NOT NULL, default pending | pending | accepted | cancelled | expired |
expires_at | INTEGER | NOT NULL | Invitation expiry timestamp |
inviter_id | TEXT | NOT NULL, FK → user.id (CASCADE) | User who sent the invitation |
created_at | INTEGER | NOT NULL | Creation timestamp |
two_factor
TOTP secrets and backup codes for two-factor authentication.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Record ID |
secret | TEXT | NOT NULL | Encrypted TOTP secret |
backup_codes | TEXT | NOT NULL | JSON array of hashed backup codes |
user_id | TEXT | NOT NULL, FK → user.id (CASCADE) | Owning user |
created_at | INTEGER | NOT NULL | Creation timestamp |
updated_at | INTEGER | NOT NULL | Last-updated timestamp |
api_key
API keys for programmatic access, with optional rate limiting and refill policies.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Key ID |
name | TEXT | NOT NULL | Human-readable key name |
prefix | TEXT | Display prefix (e.g. nno_) | |
hashed_key | TEXT | NOT NULL | Bcrypt-hashed key value |
user_id | TEXT | NOT NULL, FK → user.id (CASCADE) | Owning user |
rate_limit_enabled | INTEGER | NOT NULL, default false | Whether rate limiting is active |
rate_limit_time_window | INTEGER | Rate limit window in milliseconds | |
rate_limit_max | INTEGER | Max requests per window | |
request_count | INTEGER | NOT NULL, default 0 | Lifetime request count |
remaining | INTEGER | Remaining requests in current window | |
expires_at | INTEGER | Key expiry timestamp (nullable = never) | |
last_request | INTEGER | Timestamp of last request | |
refill_interval | INTEGER | Refill interval in milliseconds | |
refill_amount | INTEGER | Requests to add per refill interval | |
last_refill_at | INTEGER | Timestamp of last refill | |
metadata | TEXT | JSON metadata | |
permissions | TEXT | JSON permissions array | |
created_at | INTEGER | NOT NULL | Creation timestamp |
updated_at | INTEGER | NOT NULL | Last-updated timestamp |
audit_authentication
Security audit log for all authentication events. Retained for 90 days.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Event ID |
user_id | TEXT | Authenticated user (nullable for failed logins) | |
organization_id | TEXT | Active organisation at event time | |
event_type | TEXT | NOT NULL | login | logout | refresh | registration | etc. |
authentication_method | TEXT | email_password | api_key | sso | etc. | |
result | TEXT | NOT NULL | success | failure | error |
ip_address | TEXT | Client IP address | |
user_agent | TEXT | Client user-agent string | |
device_info | TEXT | JSON device context (browser, OS, device type) | |
failure_reason | TEXT | Why authentication failed | |
session_id | TEXT | Associated session ID | |
timestamp | INTEGER | NOT NULL | Event timestamp |
audit_authorization
Security audit log for all authorisation decisions. Retained for 90 days.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Event ID |
user_id | TEXT | NOT NULL | Actor user |
organization_id | TEXT | Tenant context | |
action | TEXT | NOT NULL | create | read | update | delete | invite | etc. |
resource_type | TEXT | NOT NULL | organization | member | invitation | etc. |
resource_id | TEXT | Specific resource ID | |
result | TEXT | NOT NULL | granted | denied | error |
reason | TEXT | Reason for denial or error | |
ip_address | TEXT | Client IP address | |
user_agent | TEXT | Client user-agent string | |
session_id | TEXT | Associated session ID | |
timestamp | INTEGER | NOT NULL | Event timestamp |
nno_roles
Organisation-scoped roles with associated permission arrays. Unique per (org_id, role) pair.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Role ID |
org_id | TEXT | NOT NULL | Scoping organisation |
role | TEXT | NOT NULL | Role name (e.g. admin, developer, viewer) |
permissions | TEXT | NOT NULL | JSON array of permission strings |
created_at | INTEGER | NOT NULL | Creation timestamp |
updated_at | INTEGER | NOT NULL | Last-updated timestamp |
nno_permission_grants
Explicit per-user permission overrides within an organisation. Can grant or revoke a permission independently of role membership.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Grant ID |
user_id | TEXT | NOT NULL, FK → user.id (CASCADE) | Target user |
org_id | TEXT | NOT NULL | Scoping organisation |
permission | TEXT | NOT NULL | Permission string being granted/revoked |
granted | INTEGER | NOT NULL, default true | 1 = grant, 0 = revoke |
granted_by | TEXT | NOT NULL, FK → user.id | Actor who created the grant |
expires_at | INTEGER | Expiry timestamp (nullable = permanent) | |
created_at | INTEGER | NOT NULL | Creation timestamp |
auth_failed_attempts
Tracks failed login attempts per email for progressive brute-force protection.
Policy: 5–9 failures apply exponential delay; 10+ failures lock the account for 30 minutes.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | Record ID |
user_email | TEXT | NOT NULL, UNIQUE | Email address being tracked |
attempt_count | INTEGER | NOT NULL, default 0 | Cumulative failed attempt count |
last_attempt_at | INTEGER | NOT NULL | Timestamp of the most recent failure |
locked_until | INTEGER | Lockout expiry timestamp (nullable = not locked) |
Billing Database (D1 Migrations)
Source: services/billing/migrations/
The Billing database stores Stripe subscription records, usage snapshots (per platform and optionally per entity), invoices, usage alerts, and Stripe webhook idempotency state.
subscriptions
One row per platform. Mirrors the active Stripe subscription.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL, UNIQUE | Owning platform |
tier | TEXT | NOT NULL | starter | growth | scale |
status | TEXT | NOT NULL | active | trialing | past_due | canceled |
stripe_customer_id | TEXT | NOT NULL | Stripe customer ID |
stripe_sub_id | TEXT | NOT NULL | Stripe subscription ID |
billing_email | TEXT | NOT NULL | Email for billing notifications |
current_period_start | INTEGER | NOT NULL | Billing period start (Unix ms) |
current_period_end | INTEGER | NOT NULL | Billing period end (Unix ms) |
cancel_at_period_end | INTEGER | NOT NULL, default 0 | Boolean — scheduled for cancellation |
trial_start | INTEGER | Trial period start (Unix ms) | |
trial_end | INTEGER | Trial period end (Unix ms) | |
auto_finalize | INTEGER | NOT NULL, default 1 | Boolean — auto-generate invoice at period end |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
usage_snapshots
Daily Cloudflare resource consumption snapshots. A NULL entity_id represents the platform-level aggregate; a non-null value represents a per-entity (tenant) breakdown.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Owning platform |
entity_id | TEXT | Entity scope (NULL = platform aggregate) | |
snapshot_date | TEXT | NOT NULL | Date in YYYY-MM-DD format |
worker_invocations | INTEGER | NOT NULL, default 0 | Cloudflare Worker invocations |
worker_errors | INTEGER | NOT NULL, default 0 | Cloudflare Worker errors |
d1_read_rows | INTEGER | NOT NULL, default 0 | D1 read row operations |
d1_write_rows | INTEGER | NOT NULL, default 0 | D1 write row operations |
r2_storage_bytes | INTEGER | NOT NULL, default 0 | R2 storage in bytes |
r2_class_a_ops | INTEGER | NOT NULL, default 0 | R2 Class A operations (PUT, COPY, POST) |
r2_class_b_ops | INTEGER | NOT NULL, default 0 | R2 Class B operations (GET, HEAD) |
kv_reads | INTEGER | NOT NULL, default 0 | KV read operations |
kv_writes | INTEGER | NOT NULL, default 0 | KV write operations |
collected_at | INTEGER | NOT NULL | Unix ms collection timestamp |
Indexes: idx_snapshots_platform_entity_date UNIQUE (platform_id, COALESCE(entity_id, ''), snapshot_date), idx_snapshots_entity (entity_id, snapshot_date) WHERE entity_id IS NOT NULL
invoices
Monthly invoice records generated from usage snapshots.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Owning platform |
entity_id | TEXT | Entity scope (nullable) | |
stripe_invoice_id | TEXT | Stripe invoice ID (nullable until synced) | |
period_start | INTEGER | NOT NULL | Billing period start (Unix ms) |
period_end | INTEGER | NOT NULL | Billing period end (Unix ms) |
status | TEXT | NOT NULL | draft | open | paid | void | uncollectible |
tier | TEXT | NOT NULL | Tier at time of invoice |
base_amount | INTEGER | NOT NULL | Base subscription amount (cents) |
overage_amount | INTEGER | NOT NULL | Usage overage amount (cents) |
total_amount | INTEGER | NOT NULL | Total charged (cents) |
line_items | TEXT | NOT NULL | JSON array of line items |
pdf_url | TEXT | Stripe PDF URL | |
due_date | INTEGER | Payment due date (Unix ms) | |
paid_at | INTEGER | Payment timestamp (Unix ms) | |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_invoices_platform (platform_id, status), idx_invoices_entity (entity_id, status) WHERE entity_id IS NOT NULL
usage_alerts
Records when a platform crosses a usage threshold (50%, 75%, 90%, 100%) for any metered resource in a billing period.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
platform_id | TEXT | NOT NULL | Owning platform |
resource | TEXT | NOT NULL | Resource type (e.g. worker_invocations, d1_read_rows) |
threshold_pct | INTEGER | NOT NULL | Threshold that was crossed: 50 | 75 | 90 | 100 |
triggered_at | INTEGER | NOT NULL | Unix ms trigger timestamp |
notified_at | INTEGER | Unix ms notification send timestamp (NULL = not yet sent) | |
period | TEXT | NOT NULL | Billing period in YYYY-MM format |
Indexes: idx_alerts_platform (platform_id, period)
stripe_events
Idempotency log for Stripe webhook events.
| Column | Type | Constraints | Description |
|---|---|---|---|
stripe_event_id | TEXT | PK | Stripe event ID (used as idempotency key) |
event_type | TEXT | NOT NULL | Stripe event type (e.g. invoice.paid) |
processed_at | INTEGER | NOT NULL | Unix ms processing timestamp |
payload | TEXT | NOT NULL | Full Stripe event JSON |
Provisioning Database (D1 Migrations)
Source: services/provisioning/migrations/
The Provisioning database tracks all Cloudflare resource lifecycle jobs — creation, rollback, and the dead-letter queue for failed operations.
provisioning_jobs
One row per provisioning operation. Jobs progress through a state machine; failed jobs that exhaust retries are routed to the DLQ.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
type | TEXT | NOT NULL | PROVISION_PLATFORM | ACTIVATE_FEATURE | DEACTIVATE_FEATURE | DEPROVISION_PLATFORM |
platform_id | TEXT | NOT NULL | Target platform |
entity_id | TEXT | Target entity (nullable for platform-scoped jobs) | |
feature_id | TEXT | Target feature (nullable) | |
environment | TEXT | Target environment (nullable) | |
billing_email | TEXT | Billing email captured at job creation | |
plan_tier | TEXT | Billing tier captured at job creation | |
status | TEXT | NOT NULL, default PENDING | PENDING | RUNNING | COMPLETED | FAILED | TIMED_OUT | ROLLING_BACK | ROLLED_BACK |
steps | TEXT | NOT NULL, default [] | JSON array of ProvisioningStep results |
error | TEXT | Error message on failure | |
stack_instance_id | TEXT | Linked stack instance ID (nullable) | |
stack_id | TEXT | Stack template ID (nullable) | |
stack_version | TEXT | Stack template version (nullable) | |
shared_resources | TEXT | JSON map of shared resource IDs (nullable) | |
sub_jobs | TEXT | JSON array of child job IDs (nullable) | |
workspace_stack_id | TEXT | Workspace-level stack reference (nullable) | |
rolled_back_at | INTEGER | Unix ms rollback completion timestamp | |
rollback_error | TEXT | Error message during rollback | |
dlq_entry_id | TEXT | DLQ entry ID for failed jobs | |
started_at | INTEGER | Unix ms job start timestamp | |
completed_at | INTEGER | Unix ms job completion timestamp | |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_jobs_platform (platform_id, status), idx_jobs_status (status, created_at), idx_provisioning_jobs_stack_instance (stack_instance_id) WHERE stack_instance_id IS NOT NULL
Stack Registry Database (D1 Migrations)
Source: services/stack-registry/migrations/
The Stack Registry stores versioned stack template definitions. Each template describes a named set of Cloudflare resources and features; versions are immutable once published.
stack_templates
A named, versioned stack blueprint available for platform provisioning.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
name | TEXT | NOT NULL, UNIQUE | Machine-readable template name (e.g. saas-starter) |
display_name | TEXT | NOT NULL | Human-readable display name |
description | TEXT | NOT NULL | Template description |
icon | TEXT | Icon URL or identifier (nullable) | |
domain | TEXT | Target domain category (nullable) | |
latest_version | TEXT | Semver of the latest published version | |
status | TEXT | NOT NULL, default active | active | deprecated | removed |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
updated_at | INTEGER | NOT NULL | Unix ms last-updated timestamp |
Indexes: idx_stack_templates_status (status)
stack_versions
Immutable versioned snapshots of a stack template definition.
| Column | Type | Constraints | Description |
|---|---|---|---|
id | TEXT | PK | NanoID identifier |
template_id | TEXT | NOT NULL, FK → stack_templates.id | Parent template |
version | TEXT | NOT NULL | Semver version string |
stack_definition | TEXT | NOT NULL | JSON stack definition payload |
nno_sdk_version | TEXT | NOT NULL | Minimum NNO SDK version required |
status | TEXT | NOT NULL, default active | active | deprecated |
published_at | INTEGER | NOT NULL | Unix ms publication timestamp |
created_at | INTEGER | NOT NULL | Unix ms creation timestamp |
Constraints: UNIQUE (template_id, version)
Indexes: idx_stack_versions_template (template_id), idx_stack_versions_status (template_id, status)
Timestamp Conventions
All services use Unix millisecond integers for timestamps unless otherwise noted. The IAM service uses Drizzle's { mode: "timestamp" } option, which maps to Unix seconds at the SQLite layer.
| Convention | Services | Format |
|---|---|---|
| Unix milliseconds | Registry, Billing, Provisioning, Stack Registry | INTEGER (ms since epoch) |
| Unix seconds (timestamp mode) | IAM | INTEGER (seconds since epoch, via Drizzle) |
Integer Boolean Conventions
SQLite has no native boolean type. All boolean columns use INTEGER with values 0 (false) and 1 (true). In the IAM service Drizzle schema, { mode: "boolean" } is used so the ORM handles coercion transparently.