Skip to content

Commit 0a81a44

Browse files
hotlongCopilot
andcommitted
docs: add architecture, API access, webhooks, email, backup pages
Round out the customer-facing docs with the topics integrators and operators ask about most. All facts cross-checked against ../framework source: - architecture. three-layer model (framework/control plane/mdx ObjectOS), request lifecycle, kernel cache behaviour, data ownership table - configure/api-access. generated REST surface under /api/v1,mdx session/bearer/API-key auth options, sys_api_key hashing model - configure/webhooks. outbox model, X-Objectstack-Signaturemdx (sha256=HMAC) verification, at-least-once delivery, retry semantics - configure/email. log/resend/postmark transports, OS_EMAIL_*mdx env vars, fallback behaviour when API key is missing - operate/backup. what to back up (database, artifact, secrets),mdx driver-specific strategies, RPO/RTO planning, failure rehearsals Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7546afe commit 0a81a44

9 files changed

Lines changed: 476 additions & 3 deletions

File tree

content/docs/architecture.mdx

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
title: Architecture
3+
description: How ObjectOS, the control plane, and the framework fit together.
4+
---
5+
6+
# Architecture
7+
8+
ObjectOS is one of three layers in the ObjectStack platform. Customers
9+
usually deploy only ObjectOS; understanding the boundary between the
10+
three layers makes deployment, security review, and incident response
11+
easier.
12+
13+
## The three layers
14+
15+
```text
16+
+--------------------------+ +--------------------------+ +--------------------------+
17+
| ObjectStack framework | | Control plane | | ObjectOS |
18+
| (npm packages, source) | -> | (authors, publishes | -> | (customer-hosted |
19+
| | | artifacts) | | runtime distribution) |
20+
+--------------------------+ +--------------------------+ +--------------------------+
21+
runtime, plugins, projects, environments, executes artifacts,
22+
drivers, services metadata authoring, owns runtime HTTP,
23+
artifact storage business database
24+
```
25+
26+
- **Framework** — the open-source `@objectstack/*` packages: kernel,
27+
ObjectQL, plugins, drivers, services. Customers do not need to
28+
understand or modify framework source to operate ObjectOS.
29+
- **Control plane** — where applications are authored and where the
30+
compiled `objectstack.json` artifact is published. May be the hosted
31+
ObjectStack Cloud or a customer-operated control plane.
32+
- **ObjectOS** — the runtime container customers deploy. It consumes
33+
artifacts, builds a per-project kernel, talks to the customer-managed
34+
business database, and serves application APIs and UI.
35+
36+
## What runs inside ObjectOS
37+
38+
```text
39+
+-------------------------------------+
40+
HTTP --> | HTTP dispatcher |
41+
| /health · /api/v1/* · /api/v1/auth |
42+
+-----------------+-------------------+
43+
|
44+
+-----------------v-------------------+
45+
| KernelManager (LRU cache) |
46+
| per-project ObjectKernel instances |
47+
+-----------------+-------------------+
48+
|
49+
+-----------------v-------------------+
50+
| Base plugins (always loaded) |
51+
| ObjectQL · Security · Auth · i18n |
52+
+-----------------+-------------------+
53+
|
54+
+-----------------v-------------------+
55+
| Optional capabilities (per app) |
56+
| audit · storage · jobs · queue · |
57+
| realtime · feed · automation · |
58+
| analytics · ai · settings |
59+
+-------------------------------------+
60+
```
61+
62+
Key properties:
63+
64+
- One ObjectOS host can serve many projects. Each project gets its own
65+
`ObjectKernel` instance, isolated database connection, and auth scope.
66+
- Kernels are cached in an LRU (`OS_KERNEL_CACHE_SIZE`, default 32) with
67+
an idle TTL (`OS_KERNEL_TTL_MS`, default 15 min) so cold-start cost is
68+
amortised.
69+
- Capabilities listed in the artifact's `requires` array are loaded
70+
lazily from optional `@objectstack/*` packages bundled in the image.
71+
72+
## Boot modes
73+
74+
| Mode | When to use | Hostname resolution |
75+
|---|---|---|
76+
| Cloud-connected (`OS_CLOUD_URL`) | Multi-project deployments, hosted control plane, dynamic project lifecycle | Resolved from the control plane's `/cloud/*` API |
77+
| File-backed (`controlPlaneUrl: 'file'` + `OS_ARTIFACT_FILE`) | Single project, demo, air-gapped, smoke tests | Every hostname resolves to the same packaged project |
78+
79+
The ObjectOS app wrapper (`apps/objectos/objectstack.config.ts`) picks
80+
the mode based on the presence of `OS_CLOUD_URL`.
81+
82+
## Request lifecycle
83+
84+
```text
85+
1. Ingress / load balancer (TLS termination, X-Forwarded-For)
86+
2. ObjectOS HTTP dispatcher (security headers, request id)
87+
3. Hostname → environment (cached for OS_ENV_CACHE_TTL_MS)
88+
4. KernelManager.acquire (build on miss, reuse on hit)
89+
5. AuthPlugin (session/cookie or bearer/API key)
90+
6. SecurityPlugin (RBAC + RLS + FLS)
91+
7. Route handler (generated REST, custom action, auth route)
92+
8. Driver (ObjectQL → SQL / Mongo / memory)
93+
9. Response (X-Request-Id propagated)
94+
```
95+
96+
## Data ownership
97+
98+
| Data | Where it lives | Owned by |
99+
|---|---|---|
100+
| Compiled artifact (`objectstack.json`) | Control plane or local file | Application/release team |
101+
| Business records (your domain data) | Customer-managed database | Customer |
102+
| Identity (`sys_user`, `sys_session`, …) | Same database as business records | Customer |
103+
| Settings (`sys_setting`) | Same database as business records | Customer |
104+
| Secrets baseline (`OS_AUTH_SECRET`, OIDC client secret, DB creds) | Customer secret manager | Customer |
105+
| Audit log (`sys_audit_log`) | Same database (when `audit` capability is enabled) | Customer |
106+
107+
ObjectOS never copies business data back to the control plane.
108+
109+
## Security boundary
110+
111+
- ObjectOS only needs to make one outbound connection during operation
112+
(to the control plane Artifact API) in cloud-connected mode, plus any
113+
customer-configured integrations (SMTP, object storage, webhook
114+
targets, identity providers).
115+
- Air-gapped mode removes the control-plane call entirely.
116+
- See [Production readiness](/docs/operate/production) for the
117+
go-live checklist.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
title: API Access
3+
description: Generated REST APIs, authentication, and API keys for integrations.
4+
---
5+
6+
# API Access
7+
8+
Every object declared in the artifact is exposed through a generated
9+
REST API. The same endpoints power the UI, integrations, and customer
10+
ETL scripts — there is no separate "integration API" to maintain.
11+
12+
## Base URL
13+
14+
```text
15+
https://<project-domain>/api/v1
16+
```
17+
18+
Notable surfaces:
19+
20+
| Path | Purpose |
21+
|---|---|
22+
| `/api/v1/data/<object>` | Generated CRUD for each object (e.g. `/api/v1/data/account`) |
23+
| `/api/v1/data/<object>/{id}` | Read/update/delete a single record |
24+
| `/api/v1/data/<object>/actions/<action>` | Invoke a declarative action |
25+
| `/api/v1/auth/*` | Authentication (sign-in, sessions, OAuth, OIDC) |
26+
| `/api/v1/meta/*` | Metadata APIs (objects, fields, views) when enabled |
27+
| `/health` | Liveness/readiness probe (no auth) |
28+
29+
The `apiPrefix` defaults to `/api/v1` and is configurable on the
30+
ObjectOS stack.
31+
32+
## Authentication options
33+
34+
Callers can authenticate in three ways:
35+
36+
| Method | Best for | How |
37+
|---|---|---|
38+
| Session cookie | Browser/UI traffic | Sign in through `/api/v1/auth/*`; cookies are scoped to the project hostname |
39+
| Bearer access token | Mobile, SPA, short-lived server jobs | Exchange credentials at `/api/v1/auth/sign-in/email` and pass `Authorization: Bearer <token>` |
40+
| API key | Server-to-server, ETL, long-lived integrations | Create a `sys_api_key`, pass it as a bearer token |
41+
42+
All three pass through the same `AuthPlugin` and resolve to a `sys_user`
43+
context that the `SecurityPlugin` evaluates against permissions and
44+
record access.
45+
46+
## API keys
47+
48+
API keys are first-class `sys_api_key` records. The key value itself is
49+
stored hashed — only the `prefix` and metadata remain queryable, so a
50+
leaked key cannot be reconstructed from the database.
51+
52+
To issue a key:
53+
54+
1. Sign in to **Setup → API Keys** as an administrator.
55+
2. Choose the owning user (the API call inherits that user's
56+
permissions and record access).
57+
3. Optionally set an expiration date.
58+
4. Copy the displayed secret **once** — it is not shown again.
59+
60+
Use the key as a bearer token:
61+
62+
```bash
63+
curl https://app.example.com/api/v1/data/account \
64+
-H "Authorization: Bearer os_pk_live_…"
65+
```
66+
67+
To revoke a key, run the `revoke_api_key` action on the corresponding
68+
`sys_api_key` record (also available in the Setup UI). Revocation takes
69+
effect immediately on the next request.
70+
71+
## Pagination, filtering, and sorting
72+
73+
Generated list endpoints accept the standard ObjectStack query
74+
parameters:
75+
76+
| Parameter | Meaning |
77+
|---|---|
78+
| `?limit=` | Page size (capped by the object's `maxPageSize`) |
79+
| `?offset=` or `?cursor=` | Pagination |
80+
| `?filter=` | Server-side filtering using the ObjectQL filter syntax |
81+
| `?sort=` | Sort expression (e.g. `-created_at`) |
82+
| `?fields=` | Sparse field selection |
83+
84+
Refer to the framework documentation for the full filter grammar — the
85+
runtime grammar is the source of truth.
86+
87+
## Rate limiting
88+
89+
Use the framework's `RateLimiter` primitive (or your ingress / API
90+
gateway) to apply per-IP and per-identity limits. Recommended starting
91+
buckets are documented in [Production readiness](/docs/operate/production).
92+
93+
## CORS
94+
95+
If callers run in a browser on a different origin, configure CORS at
96+
the ingress or via the runtime's middleware. Do **not** combine
97+
wildcard origins with credentialed requests.
98+
99+
## OpenAPI / discovery
100+
101+
The runtime can serve an OpenAPI document for the generated REST API
102+
when the corresponding capability is included in the image. Customer
103+
integrations should generate clients from the per-deployment OpenAPI
104+
document rather than hand-rolling URLs, because object names and
105+
generated routes follow the deployed artifact.

content/docs/configure/email.mdx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
title: Email
3+
description: Configure transactional email delivery providers and templates.
4+
---
5+
6+
# Email
7+
8+
ObjectOS sends transactional email through the framework's email plugin
9+
when the application requires it (password reset, invitations,
10+
approval notifications, scheduled reports). The plugin ships with three
11+
transports.
12+
13+
## Transports
14+
15+
| Provider | Use when | Required env |
16+
|---|---|---|
17+
| `log` | Local development; logs the email to stdout instead of sending | none |
18+
| `resend` | SaaS deliverability via Resend | `OS_EMAIL_API_KEY` |
19+
| `postmark` | SaaS deliverability via Postmark | `OS_EMAIL_API_KEY` |
20+
21+
The default is `log`. ObjectOS falls back to the log transport when a
22+
real provider is configured but no API key is supplied — useful to keep
23+
non-production environments from accidentally sending mail.
24+
25+
## Environment variables
26+
27+
| Variable | Purpose |
28+
|---|---|
29+
| `OS_EMAIL_PROVIDER` | `log`, `resend`, or `postmark` |
30+
| `OS_EMAIL_API_KEY` | Provider API key (Resend or Postmark) |
31+
| `OS_EMAIL_FROM` | Default from address. Supports both `addr@x` and `Name <addr@x>` formats |
32+
| `OS_EMAIL_RETRIES` | Number of transport retry attempts on send failure (default `0`) |
33+
34+
Environment variables override matching values in the artifact's
35+
`email` config block, so operators can re-point delivery without
36+
rebuilding the artifact.
37+
38+
## Templates
39+
40+
Reusable templates live in `sys_email_template`. Templates support
41+
variable interpolation evaluated by the framework's template engine.
42+
Application code (or a flow) calls into the email service with a
43+
template id and a variable bundle; the service materialises the
44+
subject/body and hands it to the configured transport.
45+
46+
## Verifying delivery
47+
48+
For Resend / Postmark, verify that the sending domain is configured in
49+
the provider dashboard (SPF, DKIM, optionally DMARC). The fastest
50+
end-to-end check is the Setup app's **Send test email** action on the
51+
email settings page — it uses the live transport and surfaces transport
52+
errors inline.
53+
54+
## Operational guidance
55+
56+
- Treat the API key as a secret. Store it in the customer's secret
57+
manager, never in the artifact.
58+
- Watch transport-error logs: provider rate limits, suppressions, and
59+
bounces all surface there.
60+
- Audit-sensitive transactional mail (password reset, MFA challenge)
61+
should be retained according to the customer's policy — set
62+
retention on the audit log, not the transport.
63+
- Outbound mail does not block business transactions: send failures
64+
are surfaced as errors but do not roll back the originating record
65+
change.

content/docs/configure/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"title": "Configure",
3-
"pages": ["runtime", "authentication", "permissions", "system-settings"]
3+
"pages": ["runtime", "authentication", "permissions", "system-settings", "api-access", "webhooks", "email"]
44
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
title: Webhooks
3+
description: Outbound webhook delivery, signing, and retries.
4+
---
5+
6+
# Webhooks
7+
8+
ObjectOS uses a persistent **outbox** model for outbound webhooks. When
9+
the webhook plugin is enabled, business changes enqueue a delivery row
10+
and a background dispatcher delivers it with retries — so a slow or
11+
unavailable receiver never blocks the originating transaction.
12+
13+
## Enabling webhooks
14+
15+
Webhooks are an optional capability. The deployed ObjectOS image must
16+
include `@objectstack/plugin-webhooks`, and the application artifact
17+
must register webhook subscriptions (typically as records of a
18+
`sys_webhook` object).
19+
20+
When enabled, two objects show up in the Setup app:
21+
22+
| Object | Purpose |
23+
|---|---|
24+
| `sys_webhook` | Webhook subscription (target URL, event filter, secret, status) |
25+
| `sys_webhook_delivery` | Delivery log (URL, response code, attempt count, retry timestamp) |
26+
27+
## Delivery semantics
28+
29+
- **At-least-once.** A delivery may be retried after a transient
30+
failure; receivers must be idempotent.
31+
- **Persistent.** Deliveries survive ObjectOS restarts because they are
32+
stored in the business database.
33+
- **Partitioned.** Each dispatcher worker claims a partition of the
34+
outbox so deployments can horizontally scale dispatch without double
35+
delivery.
36+
- **Bounded retries.** Failed deliveries are retried with backoff until
37+
a configurable cap; exhausted rows stay in `sys_webhook_delivery` for
38+
inspection.
39+
40+
## Signing
41+
42+
When a webhook subscription has a `secret`, ObjectOS signs every
43+
request:
44+
45+
```text
46+
X-Objectstack-Signature: sha256=<hex hmac>
47+
```
48+
49+
The signature is `HMAC-SHA256(secret, body)` computed over the raw
50+
request body. Verify it on the receiver before trusting the payload:
51+
52+
```js
53+
import { createHmac, timingSafeEqual } from 'node:crypto';
54+
55+
function verify(body, signatureHeader, secret) {
56+
const expected = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
57+
return timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
58+
}
59+
```
60+
61+
Rotate secrets by issuing a new subscription with the new secret,
62+
running both for a transition window, then disabling the old one.
63+
64+
## Receiver expectations
65+
66+
- Respond with `2xx` within a few seconds. Anything else is treated as
67+
a failure and retried.
68+
- Treat any non-2xx as "do not commit." The dispatcher does **not**
69+
consume the row until you ack.
70+
- Be idempotent — deduplicate on the delivery id header or your own
71+
event id in the payload.
72+
73+
## Failure handling
74+
75+
When something fails:
76+
77+
1. Check `sys_webhook_delivery` for the row — `response_code`,
78+
`response_body`, and `attempt` are recorded.
79+
2. Confirm outbound network access from ObjectOS to the receiver.
80+
3. If the receiver was permanently changed, update the subscription URL
81+
and re-deliver the row from Setup.
82+
4. For incident review, audit logs (`sys_audit_log`) capture
83+
subscription edits but not payloads — payloads stay in the outbox.
84+
85+
## Operational tips
86+
87+
- Do not put secrets in webhook URLs (query strings get logged).
88+
- Use a dedicated receiver hostname so you can shed load by blocking it
89+
at the edge without affecting the main app.
90+
- Watch the dispatcher lag — a growing outbox usually means the
91+
receiver is degraded.

0 commit comments

Comments
 (0)