diff --git a/apps/docs/components/mermaid.tsx b/apps/docs/components/mermaid.tsx
new file mode 100644
index 0000000000..cf7e4788d2
--- /dev/null
+++ b/apps/docs/components/mermaid.tsx
@@ -0,0 +1,60 @@
+'use client';
+
+import { useEffect, useId, useState } from 'react';
+import { useTheme } from 'next-themes';
+
+/**
+ * Client-side Mermaid renderer. Code fences with the `mermaid` language are
+ * rewritten to by the remark plugin in source.config.ts,
+ * so authors keep writing standard ```mermaid blocks.
+ */
+export function Mermaid({ chart }: { chart: string }) {
+ const id = useId();
+ const [svg, setSvg] = useState('');
+ const { resolvedTheme } = useTheme();
+
+ useEffect(() => {
+ let cancelled = false;
+
+ async function render() {
+ const { default: mermaid } = await import('mermaid');
+ mermaid.initialize({
+ startOnLoad: false,
+ securityLevel: 'strict',
+ fontFamily: 'inherit',
+ theme: resolvedTheme === 'dark' ? 'dark' : 'default',
+ });
+ try {
+ const rendered = await mermaid.render(
+ // mermaid requires a DOM-safe element id
+ `mermaid-${id.replace(/[^a-zA-Z0-9]/g, '')}`,
+ chart,
+ );
+ if (!cancelled) setSvg(rendered.svg);
+ } catch {
+ // Leave the diagram source visible instead of a blank hole on bad syntax.
+ if (!cancelled) setSvg('');
+ }
+ }
+
+ void render();
+ return () => {
+ cancelled = true;
+ };
+ }, [chart, id, resolvedTheme]);
+
+ if (!svg) {
+ return (
+
+ {chart}
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/apps/docs/mdx-components.tsx b/apps/docs/mdx-components.tsx
index 20beb4cd77..2691bc287a 100644
--- a/apps/docs/mdx-components.tsx
+++ b/apps/docs/mdx-components.tsx
@@ -1,9 +1,11 @@
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
+import { Mermaid } from '@/components/mermaid';
export function getMDXComponents(components?: MDXComponents): MDXComponents {
return {
...defaultMdxComponents,
+ Mermaid,
...components,
};
}
diff --git a/apps/docs/package.json b/apps/docs/package.json
index f5788bc45c..c28e36d47c 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -17,10 +17,13 @@
"fumadocs-mdx": "15.0.12",
"fumadocs-ui": "16.10.5",
"lucide-react": "^1.22.0",
+ "mermaid": "^11.16.0",
"next": "16.2.9",
+ "next-themes": "^0.4.6",
"react": "^19.2.7",
"react-dom": "^19.2.7",
- "tailwind-merge": "^3.6.0"
+ "tailwind-merge": "^3.6.0",
+ "unist-util-visit": "^5.1.0"
},
"devDependencies": {
"@objectstack/spec": "workspace:*",
diff --git a/apps/docs/source.config.ts b/apps/docs/source.config.ts
index af739d36bc..67bb441e2b 100644
--- a/apps/docs/source.config.ts
+++ b/apps/docs/source.config.ts
@@ -2,6 +2,25 @@ import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
import { metaSchema, pageSchema } from 'fumadocs-core/source/schema';
import { z } from 'zod';
import path from 'node:path';
+import { visit } from 'unist-util-visit';
+
+/**
+ * Rewrite ```mermaid code fences into elements so
+ * diagrams render as SVG (components/mermaid.tsx) instead of code blocks.
+ */
+function remarkMermaid() {
+ return (tree: any) => {
+ visit(tree, 'code', (node: any, index: number | undefined, parent: any) => {
+ if (node.lang !== 'mermaid' || !parent || index === undefined) return;
+ parent.children[index] = {
+ type: 'mdxJsxFlowElement',
+ name: 'Mermaid',
+ attributes: [{ type: 'mdxJsxAttribute', name: 'chart', value: node.value }],
+ children: [],
+ };
+ });
+ };
+}
export const docs = defineDocs({
dir: path.resolve(process.cwd(), '../../content/docs'),
@@ -31,6 +50,6 @@ export const blog = defineDocs({
export default defineConfig({
mdxOptions: {
- // MDX options
+ remarkPlugins: (v) => [...v, remarkMermaid],
},
});
diff --git a/content/docs/ai/actions-as-tools.mdx b/content/docs/ai/actions-as-tools.mdx
index b3608e55a0..40bd3ff0af 100644
--- a/content/docs/ai/actions-as-tools.mdx
+++ b/content/docs/ai/actions-as-tools.mdx
@@ -116,21 +116,13 @@ The queue is exposed via four REST endpoints (`GET`, `GET/:id`, `POST/:id/approv
### End-to-end example
-Two runnable demos live in `examples/app-todo/test/`:
-
-- **`ai-hitl.test.ts`** — drives the tool registry directly (no LLM dependency). Asserts the full lifecycle: `variant:'danger'` action registered as a tool → invocation returns `pending_approval` without executing → row persisted → `approvePendingAction(id, actor)` re-runs the handler → row transitions to `executed`. Reject path is also covered.
-
- ```bash
- pnpm --filter @example/app-todo test:hitl
- ```
-
-- **`ai-hitl-llm.test.ts`** — same scenario but with a real model behind Vercel AI Gateway. The LLM is given the auto-generated tool description and asked to "delete all completed tasks"; the framework returns `pending_approval` so the model summarises the wait instead of retrying. After we call approve from the test, the deletion completes.
-
- ```bash
- AI_GATEWAY_API_KEY=vck_... pnpm --filter @example/app-todo test:hitl:llm
- ```
-
- Gated on `AI_GATEWAY_API_KEY` (or `OPENAI_API_KEY`); exits 0 with a notice if no key is provided, so it can be wired into CI without leaking spend.
+The full lifecycle: a `variant:'danger'` action registered as a tool →
+invocation returns `pending_approval` without executing → row persisted →
+`approvePendingAction(id, actor)` re-runs the handler → row transitions to
+`executed` (the reject path mirrors it). The todo example's
+`delete_completed` action (`examples/app-todo/src/actions/task.actions.ts`)
+is authored exactly for this: `variant: 'danger'` + `confirmText` route it
+through the approval queue.
A trimmed view of the integration path:
diff --git a/content/docs/ai/agents.mdx b/content/docs/ai/agents.mdx
index 2be563d757..3f56f5ccf4 100644
--- a/content/docs/ai/agents.mdx
+++ b/content/docs/ai/agents.mdx
@@ -18,7 +18,8 @@ is in — the user never picks from a roster:
Both agents are part of the **cloud / Enterprise** in-UI AI runtime (cloud ADR-0025).
The **open edition** ships neither — it uses `@objectstack/mcp` (BYO-AI) for data
-query and source-mode authoring instead (see the note above).
+query and source-mode authoring instead (see the callout in the
+[AI Overview](/docs/ai)).
Within the cloud / EE runtime there is no per-turn intent classifier and no agent
dropdown: the surface binds the agent (data console → `ask`, Studio → `build`). A
@@ -64,7 +65,7 @@ them via the chat endpoint.
Agent tools are **references** to existing Actions, Flows, or queries — you do
not define ad-hoc tool names with inline parameter schemas here. See
-[Actions as Tools](#actions-as-tools-explicit-opt-in) for how an Action becomes
+[Actions as Tools](/docs/ai/actions-as-tools) for how an Action becomes
LLM-callable.
@@ -219,7 +220,7 @@ Use the data tools to query records and aggregate metrics.`,
},
// Built-in data tools (query_records / get_record / aggregate_data) are
- // available to agents automatically once registered — see "Actions as Tools".
+ // available to agents automatically once registered — see "Natural Language Queries".
tools: [
{ type: 'flow', name: 'analyze_pipeline', description: 'Analyze sales pipeline health' },
],
diff --git a/content/docs/ai/chatbot-integration.mdx b/content/docs/ai/chatbot-integration.mdx
index 5953999dfe..f863efe123 100644
--- a/content/docs/ai/chatbot-integration.mdx
+++ b/content/docs/ai/chatbot-integration.mdx
@@ -26,12 +26,13 @@ This guide maps every chatbot configuration knob to its framework endpoint
so you can drop the plugin into an app without reverse-engineering the
contract.
-## 1. Enable the AI tier on the framework side
+## 1. Enable the AI tier on the backend
-The `default` and `full` plugin tier presets both include the `ai`
-capability, so `objectstack dev` boots the AI services unless you opt out
-with `--preset minimal`. Provide a Vercel AI Gateway model and key via env
-vars:
+On a cloud / EE dev host (where `@objectstack/service-ai` is available —
+see the callout above), the `default` and `full` plugin tier presets both
+include the `ai` capability, so `objectstack dev` boots the AI services
+unless you opt out with `--preset minimal`. Provide a Vercel AI Gateway
+model and key via env vars:
```bash
AI_GATEWAY_API_KEY=vck_*** \
diff --git a/content/docs/ai/knowledge-rag.mdx b/content/docs/ai/knowledge-rag.mdx
index 31caee0eb8..0edb580c18 100644
--- a/content/docs/ai/knowledge-rag.mdx
+++ b/content/docs/ai/knowledge-rag.mdx
@@ -40,7 +40,10 @@ kernel.use(new KnowledgeMemoryPlugin());
// kernel.use(new KnowledgeRagflowPlugin({ endpoint, apiKey }));
```
-Then register the AI tool so agents can call it:
+Then register the AI tool so agents can call it. (This step uses
+`@objectstack/service-ai`, i.e. the **cloud / Enterprise** in-UI AI runtime —
+see the callout in the [AI Overview](/docs/ai). The Knowledge Protocol itself
+and the adapter plugins above ship in the open framework.)
```ts
import { registerKnowledgeTools } from '@objectstack/service-ai';
diff --git a/content/docs/ai/natural-language-queries.mdx b/content/docs/ai/natural-language-queries.mdx
index 101abde7e6..cac5f36b84 100644
--- a/content/docs/ai/natural-language-queries.mdx
+++ b/content/docs/ai/natural-language-queries.mdx
@@ -7,6 +7,11 @@ description: How agents query live data through the built-in data tools (query_r
Part of the [AI module](/docs/ai) — how natural-language questions become ObjectQL queries at runtime.
+The data tools described here ship with `@objectstack/service-ai`, i.e. the
+**cloud / Enterprise** in-UI AI runtime (see the callout in the
+[AI Overview](/docs/ai)). On the open edition, point your own AI at the same
+objects and queries through `@objectstack/mcp` instead.
+
Agents query your data through the built-in **data tools** —
`query_records`, `get_record`, and `aggregate_data` — which the LLM calls with
structured arguments. These run as ordinary [ObjectQL](/docs/protocol/objectql) queries over
diff --git a/content/docs/api/client-sdk.mdx b/content/docs/api/client-sdk.mdx
index 1ccf28de4f..7f091c683f 100644
--- a/content/docs/api/client-sdk.mdx
+++ b/content/docs/api/client-sdk.mdx
@@ -122,7 +122,7 @@ The `@objectstack/client` SDK aims to implement the ObjectStack API protocol spe
| **storage** | ✅ | 2 | File upload & download |
| **i18n** | ✅ | 3 | Internationalization |
| **notifications** | 🟡 | 7 | Legacy notification helpers; receipt/inbox cut-over pending |
-| **realtime** | ✅ | 6 | WebSocket subscriptions |
+| **realtime** | ✅ | 6 | Connection/subscription/presence calls (server transport is plugin-provided) |
| **ai** | ✅ | 3 | AI services (NLQ, suggest, insights) |
@@ -553,18 +553,18 @@ For detailed information about the client's protocol implementation:
diff --git a/content/docs/api/environment-routing.mdx b/content/docs/api/environment-routing.mdx
index 4cc050bb3c..49640173e1 100644
--- a/content/docs/api/environment-routing.mdx
+++ b/content/docs/api/environment-routing.mdx
@@ -73,7 +73,7 @@ const client = new ObjectStackClient({
const env = client.project('env_prod');
-await env.data.find('customer', { top: 20 });
+await env.data.find('customer', { limit: 20 });
await env.meta.getItems('object');
await env.packages.list();
```
diff --git a/content/docs/api/error-catalog.mdx b/content/docs/api/error-catalog.mdx
index 25ddd34508..58837f70e4 100644
--- a/content/docs/api/error-catalog.mdx
+++ b/content/docs/api/error-catalog.mdx
@@ -5,7 +5,7 @@ description: Complete reference for all ObjectStack error codes with causes, fix
# Error Code Catalog
-ObjectStack uses a structured error system with **9 error categories** and **41+ standardized error codes**. Every error includes a machine-readable code, HTTP status mapping, and retry guidance.
+ObjectStack uses a structured error system with **9 error categories** and **51 standardized error codes**. Every error includes a machine-readable code, HTTP status mapping, and retry guidance.
**Source:** `packages/spec/src/api/errors.zod.ts`
@@ -391,7 +391,7 @@ import type { EnhancedApiError } from '@objectstack/spec/api';
async function handleApiCall() {
try {
- const result = await client.records.create('task', { title: 'New Task' });
+ const result = await client.data.create('task', { title: 'New Task' });
return result;
} catch (error) {
const apiError = error as EnhancedApiError;
diff --git a/content/docs/api/error-handling-client.mdx b/content/docs/api/error-handling-client.mdx
index 10051297ff..597186f04b 100644
--- a/content/docs/api/error-handling-client.mdx
+++ b/content/docs/api/error-handling-client.mdx
@@ -8,7 +8,7 @@ description: Best practices for handling ObjectStack errors in client applicatio
This guide covers best practices for handling ObjectStack API errors in client applications — from parsing error responses to displaying validation errors in forms and implementing retry strategies.
-**Error Format:** All ObjectStack API errors follow the `ErrorResponseSchema` structure. See [Wire Format](/docs/api/wire-format#7-error-response-format) for the full JSON shape.
+**Error Format:** The patterns in this guide target the spec error envelope (`ErrorResponseSchema`). Note that today's kernel REST data routes emit a flatter `{ error, code }` shape — see the [API Overview](/docs/api#error-handling) for the two wire formats in use and [Wire Format](/docs/api/wire-format#7-error-response-format) for full JSON examples; adapt the parser accordingly.
---
diff --git a/content/docs/api/error-handling-server.mdx b/content/docs/api/error-handling-server.mdx
index d2855b15d1..e51de23aa1 100644
--- a/content/docs/api/error-handling-server.mdx
+++ b/content/docs/api/error-handling-server.mdx
@@ -77,8 +77,8 @@ object (imported from `@objectstack/spec/data`) with `object`, `events`
(an array of lifecycle events such as `beforeInsert` / `afterUpdate`), and a
`handler: async (ctx) => { ... }`. Errors thrown from a hook handler abort the
operation and propagate to the API layer. The handler receives a `HookContext`:
-the pending record lives on `ctx.input.doc`, the pre-change snapshot on
-`ctx.previous`, and the persisted result (after hooks) on `ctx.result`.
+the pending record's fields are exposed directly on `ctx.input`, the pre-change
+snapshot on `ctx.previous`, and the persisted result (after hooks) on `ctx.result`.
### Before Hooks (Validation / Guard)
@@ -90,7 +90,7 @@ export const ValidateStatusTransition: Hook = {
object: 'task',
events: ['beforeUpdate'],
handler: async (ctx) => {
- const changes = ctx.input.doc as Record;
+ const changes = ctx.input as Record;
const previous = ctx.previous ?? {};
// Guard: prevent modifying completed tasks
@@ -182,7 +182,7 @@ export const ValidateTaskData: Hook = {
object: 'task',
events: ['beforeInsert'],
handler: async (ctx) => {
- const data = ctx.input.doc as Record;
+ const data = ctx.input as Record;
const schema = z.object({
title: z.string().min(1, 'Title is required').max(200),
priority: z.enum(['low', 'medium', 'high', 'critical']),
@@ -218,7 +218,7 @@ export const TaskWriteWithLogging: Hook = {
onError: 'abort', // failure aborts the write (default); use 'log' to continue
handler: async (ctx) => {
try {
- await runBusinessRules(ctx.input.doc);
+ await runBusinessRules(ctx.input);
} catch (error) {
const httpStatus = (error as AppError).httpStatus ?? 500;
const logEntry = {
@@ -496,7 +496,7 @@ export const ValidateBusinessRules: Hook = {
object: 'task',
events: ['beforeInsert'],
handler: async (ctx) => {
- const result = safeParsePretty(TaskBusinessRules, ctx.input.doc);
+ const result = safeParsePretty(TaskBusinessRules, ctx.input);
if (!result.success) {
throw new AppError(
'validation_error',
diff --git a/content/docs/api/index.mdx b/content/docs/api/index.mdx
index 4bd6c4939e..075e2f9d71 100644
--- a/content/docs/api/index.mdx
+++ b/content/docs/api/index.mdx
@@ -14,7 +14,7 @@ ObjectStack exposes a fully typed REST API. All endpoints use JSON request/respo
| Surface | Status in this repo |
| :--- | :--- |
| **REST** | ✅ Auto-generated from the protocol (`@objectstack/rest`) — CRUD, query, batch, metadata, packages |
-| **Realtime** | ✅ WebSocket subscriptions plus SSE streaming (`@objectstack/service-realtime`) |
+| **Realtime** | ⚠️ In-process pub/sub service (`@objectstack/service-realtime`, single-instance); the `/realtime/*` REST routes and WebSocket/SSE transport are plugin-provided — none ships in the open framework |
| **MCP** | ✅ Objects and opted-in actions exposed as Model Context Protocol tools ([AI module](/docs/ai)) |
| **GraphQL** | ⚠️ Route is wired but **bring-your-own service**: `/graphql` returns 501 unless an implementation of the `IGraphQLService` contract is registered — none ships in the open framework |
| **OData** | ⚠️ Vocabulary only: REST list endpoints accept OData-style operators (e.g. `$top`), but there is no standalone OData endpoint |
diff --git a/content/docs/api/plugin-endpoints.mdx b/content/docs/api/plugin-endpoints.mdx
index f449bad69f..55ee4d12dd 100644
--- a/content/docs/api/plugin-endpoints.mdx
+++ b/content/docs/api/plugin-endpoints.mdx
@@ -33,14 +33,16 @@ The following endpoints become available when the corresponding plugin is instal
| GET | `/workflow/:object/config` | Get workflow configuration |
| GET | `/workflow/:object/:recordId/state` | Get record's workflow state |
| POST | `/workflow/:object/:recordId/transition` | Execute state transition |
-| POST | `/workflow/:object/:recordId/approve` | Approve workflow step |
-| POST | `/workflow/:object/:recordId/reject` | Reject workflow step |
+
+Approve/reject are **not** workflow routes (ADR-0019): approval is a flow node, and decisions are recorded on the approvals runtime via `POST /approvals/requests/:id/approve` and `POST /approvals/requests/:id/reject`.
### Automation (`/automation`) — Plugin Required
| Method | Endpoint | Description |
|:-------|:---------|:------------|
-| POST | `/automation/trigger/:name` | Trigger an automation flow by name |
+| POST | `/automation/:name/trigger` | Trigger an automation flow by name (legacy alias: `/automation/trigger/:name`) |
+
+The automation dispatcher also exposes flow CRUD (`GET`/`POST /automation`, `GET`/`PUT`/`DELETE /automation/:name`) and run observability/resume routes — see [Durable pause & resume](/docs/automation/flows#durable-pause--resume-adr-0019).
### Views (`/ui`) — Plugin Required
@@ -60,7 +62,7 @@ The following endpoints become available when the corresponding plugin is instal
| POST | `/realtime/disconnect` | Close connection |
| POST | `/realtime/subscribe` | Subscribe to channel |
| POST | `/realtime/unsubscribe` | Unsubscribe |
-| POST | `/realtime/presence` | Set presence |
+| PUT | `/realtime/presence/:channel` | Set presence |
| GET | `/realtime/presence/:channel` | Get channel presence |
### Notifications (`/notifications`) — Plugin Required
diff --git a/content/docs/automation/approvals.mdx b/content/docs/automation/approvals.mdx
index 175285a35e..e538ee99ea 100644
--- a/content/docs/automation/approvals.mdx
+++ b/content/docs/automation/approvals.mdx
@@ -130,5 +130,5 @@ Separating *who configures* from *who approves* from *as whom it runs* is the sa
## See also
- Decision records: **ADR-0049** (`runAs`), **ADR-0066** (unified authorization).
-- Guide: [Business Logic](/docs/automation/hooks) (Flows, Approval Nodes); [Who can see data / automation / interface](/docs/permissions/access-recipes).
+- Guide: [Hooks](/docs/automation/hooks) and [Flows](/docs/automation/flows); [Who can see data / automation / interface](/docs/permissions/access-recipes).
- Reference: [Flow](/docs/references/automation/flow), [Approval](/docs/references/automation/approval).
diff --git a/content/docs/automation/flows.mdx b/content/docs/automation/flows.mdx
index e5b83b3ffa..667e001d46 100644
--- a/content/docs/automation/flows.mdx
+++ b/content/docs/automation/flows.mdx
@@ -109,6 +109,7 @@ Each node performs a specific action in the flow.
| `delete_record` | Delete records |
| `get_record` | Query records |
| `http` | Make an HTTP API call |
+| `notify` | Send an outbound notification via the messaging service |
| `script` | Run a custom script action (dispatched by `config.actionType`) |
| `screen` | Display a user form/screen (durable pause) |
| `wait` | Pause for a timer or named signal (durable pause; timers auto-resume) |
diff --git a/content/docs/automation/hook-bodies.mdx b/content/docs/automation/hook-bodies.mdx
index c3c6282ef4..814043c8ba 100644
--- a/content/docs/automation/hook-bodies.mdx
+++ b/content/docs/automation/hook-bodies.mdx
@@ -238,7 +238,7 @@ objectstack.config.ts
## See also
- [Formula Reference](/docs/data-modeling/formulas) — the L1 expression engine.
-- [Business Logic](/docs/automation/hooks) — broader patterns for hooks, actions, flows.
+- [Hooks](/docs/automation/hooks) and [Flows](/docs/automation/flows) — broader patterns for hooks, actions, flows.
- [Cloud Deployment](/docs/deployment) — how artifacts travel from Studio to objectos.
- `packages/spec/src/data/hook-body.zod.ts` — canonical Zod schema.
- `packages/runtime/src/sandbox/script-runner.ts` — engine decision rationale.
diff --git a/content/docs/automation/hooks.mdx b/content/docs/automation/hooks.mdx
index 5d11601910..25b204f933 100644
--- a/content/docs/automation/hooks.mdx
+++ b/content/docs/automation/hooks.mdx
@@ -16,8 +16,10 @@ such as `beforeFind`/`afterFind` are also available).
## Before Hook
-Mutate the incoming record before it is saved. The pending record lives on
-`ctx.input` (`ctx.input.doc` for insert/update):
+Mutate the incoming record before it is saved. The engine exposes the pending
+record's fields **directly on `ctx.input`** (a flat view over the internal
+`{ data, options }` wrapper — reads and writes of record fields route through
+`ctx.input.data`):
```typescript
import { Hook } from '@objectstack/spec/data';
@@ -28,7 +30,7 @@ export const AccountBeforeWrite: Hook = {
events: ['beforeInsert', 'beforeUpdate'],
handler: async (ctx) => {
- const record = ctx.input.doc as Record;
+ const record = ctx.input as Record;
// Normalize phone numbers
if (record.phone) {
@@ -80,7 +82,7 @@ ctx = {
id, // tracing id
object, // target object name
event, // e.g. 'beforeInsert' | 'afterUpdate'
- input, // mutable input (e.g. input.doc for insert/update)
+ input, // mutable input — record fields exposed flat (raw wrapper: input.data, input.options)
result, // mutable operation result (after* events)
previous, // record state before the operation (update/delete)
session, // { userId, tenantId, roles, accessToken, isSystem }
diff --git a/content/docs/automation/index.mdx b/content/docs/automation/index.mdx
index 46e863b4eb..572fcd9979 100644
--- a/content/docs/automation/index.mdx
+++ b/content/docs/automation/index.mdx
@@ -1,6 +1,6 @@
---
title: Automation
-description: 18 hook events, 20+ extensible flow node types, scheduled flows, approvals, and durable webhooks — the process engine that reacts to your data.
+description: Hooks, flows, workflows, approvals, scheduled jobs, and durable webhooks — the process engine that reacts to your data.
---
# Automation
@@ -27,8 +27,8 @@ export const OpportunityStageHook: Hook = {
## The building blocks
-- **Hooks** intercept **18 lifecycle events** — before/after each of find, findOne, count, aggregate, insert, update, delete, updateMany, deleteMany. Hooks support priority ordering, fire-and-forget `async` mode, CEL `condition` guards, and a `retryPolicy` with backoff ([Hooks](/docs/automation/hooks)).
-- **Flows** are node-based processes with **20 built-in node types** (decision, loop, create/update/delete record, http, notify, script, screen, wait, subflow, parallel/join gateways, …) — and the set is **registry-extensible**: plugins can contribute new node types via `registerNodeExecutor`. Flows launch on record changes, on a schedule, from a screen, or via API ([Flows](/docs/automation/flows)).
+- **Hooks** intercept every record operation with before/after events — reads, writes, deletes, and their bulk variants. Hooks support priority ordering, fire-and-forget `async` mode, CEL `condition` guards, and a `retryPolicy` with backoff ([Hooks](/docs/automation/hooks)).
+- **Flows** are node-based processes built from nodes like decision, loop, record operations, http, notify, script, screen, wait, subflow, and parallel/join gateways — and the set is **registry-extensible**: plugins can contribute new node types via `registerNodeExecutor`. Flows launch on record changes, on a schedule, from a screen, or via API ([Flows](/docs/automation/flows)).
- **Workflows** model a record's lifecycle as a **finite state machine**: states, transitions, and guards ([Workflows](/docs/automation/workflows)).
- **Approvals** are flow nodes with approver resolution, approve/reject decisions, and escalation ([Approvals](/docs/automation/approvals)).
- **Webhooks** deliver events to external systems through a **durable outbox** — exponential/linear/fixed retry with dead-lettering, HMAC signing, and an admin redeliver endpoint ([Webhook Delivery](/docs/automation/webhooks)).
@@ -39,7 +39,7 @@ Rule of thumb: model *state* with workflows, model *steps* with flows, use hooks
## What's in this module
-
+
diff --git a/content/docs/automation/webhooks.mdx b/content/docs/automation/webhooks.mdx
index c312df08f3..4b939355b3 100644
--- a/content/docs/automation/webhooks.mdx
+++ b/content/docs/automation/webhooks.mdx
@@ -373,15 +373,17 @@ better-auth session (any authenticated user). Responses: `200` on success,
## 9. Concurrency & rate-limiting
-Each dispatcher partition runs at most `maxConcurrency` (default 16) HTTP
-requests in flight. Beyond that, claimed rows wait in an in-memory
-queue. This caps per-node outbound load.
-
-For per-receiver rate limiting (some receivers like Slack publish strict
-limits), the dispatcher tracks `requests_in_last_second` per
-`hostname(url)` and delays further requests to that host with the same
-exponential backoff used for retries. This shields well-behaved
-receivers from being driven into 429s by our bulk traffic.
+Outbound load is shaped by the claim loop, not an explicit concurrency knob:
+within a held partition the dispatcher claims up to `batchSize` rows (default
+32) per tick and POSTs them **sequentially**, so at most one request per
+partition is in flight at a time (default 8 partitions per node). Both
+`batchSize`, `partitionCount`, and the tick `intervalMs` are configurable via
+`HttpDispatcherOptions`.
+
+> **Not yet implemented.** Per-receiver rate limiting (tracking request rate
+> per `hostname(url)` and delaying further requests to a busy host) is future
+> work — see Phase 4 of the plan below. Today nothing shields a receiver from
+> bulk traffic beyond the sequential-per-partition send loop.
## 10. Observability
@@ -469,7 +471,7 @@ Every design choice matches one or more established systems:
- **GitHub webhooks** — `X-…-Signature: sha256=` signing
scheme, redelivery, per-delivery detail with request/response bytes.
- **Shopify webhooks** — HMAC-SHA256 header, `X-Shopify-Topic` event
- hint (we use `type` in payload instead — simpler).
+ hint (we carry the event hint in the `X-Objectstack-Event` header instead).
- **AWS EventBridge / SQS** — at-least-once durability, dead-letter
queue model (our `status = dead`).
- **Kubernetes admission webhooks** — egress validation and TLS-only
@@ -477,7 +479,7 @@ Every design choice matches one or more established systems:
## 15. Migration plan
-Three phases. Each phase delivers visible user value.
+Four phases. Each phase delivers visible user value.
> **How it actually shipped.** The implementation did **not** introduce new
> `system/*` Zod schemas. There is no `system/webhook-delivery.zod.ts` and no
diff --git a/content/docs/concepts/architecture.mdx b/content/docs/concepts/architecture.mdx
index 665cb8f242..074094bc41 100644
--- a/content/docs/concepts/architecture.mdx
+++ b/content/docs/concepts/architecture.mdx
@@ -183,7 +183,7 @@ export const HighValueCustomerFlow = defineFlow({
nodes: [
{ id: 'start', type: 'start', label: 'Customer created' },
{ id: 'assign', type: 'update_record', label: 'Assign owner' },
- { id: 'notify', type: 'send_email', label: 'Alert leadership' },
+ { id: 'notify', type: 'notify', label: 'Alert leadership' },
{ id: 'end', type: 'end', label: 'End' },
],
edges: [
@@ -430,7 +430,7 @@ export const OpportunityWonFlow = defineFlow({
nodes: [
{ id: 'start', type: 'start', label: 'Stage changed' },
{ id: 'invoice', type: 'create_record', label: 'Create invoice' },
- { id: 'notify', type: 'send_email', label: 'Notify sales team' },
+ { id: 'notify', type: 'notify', label: 'Notify sales team' },
{ id: 'end', type: 'end', label: 'End' },
],
edges: [
diff --git a/content/docs/concepts/metadata-lifecycle.mdx b/content/docs/concepts/metadata-lifecycle.mdx
index ed95d0ba6e..26d168225c 100644
--- a/content/docs/concepts/metadata-lifecycle.mdx
+++ b/content/docs/concepts/metadata-lifecycle.mdx
@@ -5,7 +5,7 @@ description: How metadata flows through Repository → Change Log → Cache →
# Metadata Lifecycle & HMR
-This page documents the metadata data path introduced by [ADR-0008](/adr/0008-metadata-repository-and-change-log) and refined by [ADR-0005](/adr/0005-metadata-customization-overlay). It is the canonical event stream that powers Studio Hot Module Replacement (HMR), REST writes, and future cloud editing.
+This page documents the metadata data path introduced by [ADR-0008](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0008-metadata-repository-and-change-log.md) and refined by [ADR-0005](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0005-metadata-customization-overlay.md). It is the canonical event stream that powers Studio Hot Module Replacement (HMR), REST writes, and future cloud editing.
**TL;DR** — Every write goes through a single `MetadataRepository.put()` call. The repository appends to a change log, emits a watch event with a monotonic `seq`, and broadcasts over SSE to the Studio UI. The Studio's `useMetadataHmr` hook displays the latest `seq` in the status badge and re-fetches affected views.
@@ -54,7 +54,7 @@ Reads walk top-to-bottom: the first non-null layer wins. Writes always route to
| **Cache** | `@objectstack/metadata` | In-memory snapshot of the registry, keyed by `MetaRef`. Invalidated by change-log events. |
| **Registry** | `@objectstack/metadata` | Typed registry the Kernel and plugins query. Built from the cache. |
-`MetaRef = (type, name, org)`. As of [ADR-0008 §0 amendment (2026-04-13)](/adr/0008-metadata-repository-and-change-log#0-2026-04-13-amendment--drop-project-and-branch-from-metaref), `project` and `branch` are removed from the runtime tuple. Project survives only as an artifact-packaging concept on the `objectstack.json` envelope; branching is left to Git.
+`MetaRef = (type, name, org)`. As of [ADR-0008 §0 amendment (2026-04-13)](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0008-metadata-repository-and-change-log.md#0-2026-04-13-amendment--drop-project-and-branch-from-metaref), `project` and `branch` are removed from the runtime tuple. Project survives only as an artifact-packaging concept on the `objectstack.json` envelope; branching is left to Git.
---
@@ -102,9 +102,9 @@ In shared-database multi-tenancy, **most metadata types must not be per-org cust
| `object`, `field` | ❌ | Defines the table schema. Overriding would break existing data. |
| `datasource` | ❌ | Connection strings; multi-tenant isolation is enforced at a higher layer. |
-There is no `workflow` metadata type (per [ADR-0020](/adr/0020-state-machine-converge-and-enforce), record state machines are a `state_machine` validation). The runtime gate is implemented in `OVERLAY_ALLOWED_TYPES` (derived from the registry) and enforced by `SysMetadataRepository.put()`. Denied types return `403 not_overridable`.
+There is no `workflow` metadata type (per [ADR-0020](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0020-state-machine-converge-and-enforce.md), record state machines are a `state_machine` validation). The runtime gate is implemented in `OVERLAY_ALLOWED_TYPES` (derived from the registry) and enforced by `SysMetadataRepository.put()`. Denied types return `403 not_overridable`.
-See [ADR-0005](/adr/0005-metadata-customization-overlay) for the full design and amendments.
+See [ADR-0005](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0005-metadata-customization-overlay.md) for the full design and amendments.
---
@@ -126,11 +126,11 @@ The hash is `sha256:` + 64-hex of a canonical (sorted-keys, no-undefined) JSON s
## Cross-references
-- [ADR-0005: Metadata customization overlay](/adr/0005-metadata-customization-overlay) — overlay semantics, whitelist, conflict rules.
-- [ADR-0008 §0 amendment (2026-04): project/branch removal](/adr/0008-metadata-repository-and-change-log#0-2026-04-13-amendment--drop-project-and-branch-from-metaref) — why `MetaRef.project` and `MetaRef.branch` are dead.
-- [ADR-0008: Metadata Repository & Change Log](/adr/0008-metadata-repository-and-change-log) — the four-primitive architecture.
-- [IMetadataService Contract](/guides/contracts/metadata-service) — the runtime API plugins call.
-- [North Star](/concepts/north-star) — the product shape these primitives serve.
+- [ADR-0005: Metadata customization overlay](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0005-metadata-customization-overlay.md) — overlay semantics, whitelist, conflict rules.
+- [ADR-0008 §0 amendment (2026-04): project/branch removal](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0008-metadata-repository-and-change-log.md#0-2026-04-13-amendment--drop-project-and-branch-from-metaref) — why `MetaRef.project` and `MetaRef.branch` are dead.
+- [ADR-0008: Metadata Repository & Change Log](https://github.com/objectstack-ai/framework/blob/main/docs/adr/0008-metadata-repository-and-change-log.md) — the four-primitive architecture.
+- [IMetadataService Contract](/docs/kernel/contracts/metadata-service) — the runtime API plugins call.
+- [North Star](/docs/concepts/north-star) — the product shape these primitives serve.
---
diff --git a/content/docs/data-modeling/field-types.mdx b/content/docs/data-modeling/field-types.mdx
index a4861246b9..c956eb5224 100644
--- a/content/docs/data-modeling/field-types.mdx
+++ b/content/docs/data-modeling/field-types.mdx
@@ -1,11 +1,11 @@
---
title: Field Type Gallery
-description: Complete reference for all 48 ObjectStack field types with per-type configuration properties
+description: Complete reference for every ObjectStack field type with per-type configuration properties
---
# Field Type Gallery
-ObjectStack provides **49 field types** covering every data modeling need — from basic text and numbers to AI vectors and rich media. This guide organizes them by category with per-type configuration details.
+ObjectStack provides a **comprehensive set of field types** covering every data modeling need — from basic text and numbers to AI vectors and rich media. This guide organizes them by category with per-type configuration details.
**Source:** `packages/spec/src/data/field.zod.ts`
@@ -24,7 +24,6 @@ Single-line plain text input.
| `maxLength` | `number` | — | Maximum character length |
| `minLength` | `number` | — | Minimum character length |
| `format` | `string` | — | Validation format pattern |
-| `caseSensitive` | `boolean` | — | Whether text comparisons are case-sensitive |
```typescript
{ name: 'first_name', label: 'First Name', type: 'text', maxLength: 100 }
@@ -303,10 +302,19 @@ Reference to a record in another object (foreign key).
|:---|:---|:---|:---|
| `reference` | `string` | **required** | Target object name (snake_case) |
| `referenceFilters` | `string[]` | — | Filters applied to lookup dialogs (e.g. `"active = true"`) |
-| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted |
+| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted (a *required* lookup left at the default `set_null` is escalated to `restrict`, since a NOT NULL foreign key cannot be cleared) |
```typescript
-{ name: 'assigned_to', label: 'Assigned To', type: 'lookup', reference: 'user' }
+{ name: 'company', label: 'Company', type: 'lookup', reference: 'account' }
+```
+
+### `user`
+Person picker — a lookup specialized to the built-in `sys_user` object. Stored identically to `lookup` (foreign key to `sys_user.id`; `multiple: true` stores an array) and resolved through the same `$expand` machinery. Supports `defaultValue: 'current_user'` to stamp the acting user's id on insert.
+
+```typescript
+Field.user({ label: 'Assignee' })
+Field.user({ label: 'Watchers', multiple: true })
+Field.user({ label: 'Owner', defaultValue: 'current_user' })
```
### `master_detail`
@@ -419,15 +427,16 @@ Audio file attachment.
## Computed Types
### `formula`
-Calculated field using an expression.
+Calculated field using a CEL expression (see [Expressions](/docs/data-modeling/formulas)).
| Property | Type | Default | Description |
|:---|:---|:---|:---|
-| `expression` | `string` | **required** | Calculation expression |
-| `cached` | `object` | — | Cache settings for computed result |
+| `expression` | `string \| Expression` | **required** | CEL calculation expression |
+| `returnType` | `'number' \| 'text' \| 'boolean' \| 'date'` | — | Declared value type of the computed result |
+| `dependencies` | `string[]` | — | Fields the formula depends on |
```typescript
-{ name: 'total', label: 'Total', type: 'formula', expression: 'quantity * unit_price' }
+{ name: 'total', label: 'Total', type: 'formula', expression: 'record.quantity * record.unit_price', returnType: 'number' }
```
### `summary`
@@ -497,23 +506,14 @@ Name-keyed map of embedded sub-objects (`Record`). Insertion
## Enhanced Types
### `location`
-Geographic coordinates with optional map display.
-
-| Property | Type | Default | Description |
-|:---|:---|:---|:---|
-| `displayMap` | `boolean` | — | Show interactive map |
-| `allowGeocoding` | `boolean` | — | Enable address-to-coordinates conversion |
+Geographic coordinates. Stored as `{ latitude, longitude, altitude?, accuracy? }` (latitude −90..90, longitude −180..180). No per-type config properties.
```typescript
-{ name: 'headquarters', label: 'Location', type: 'location', displayMap: true, allowGeocoding: true }
+{ name: 'headquarters', label: 'Location', type: 'location' }
```
### `address`
-Structured postal address.
-
-| Property | Type | Default | Description |
-|:---|:---|:---|:---|
-| `addressFormat` | `'us' \| 'uk' \| 'international'` | — | Address format template |
+Structured postal address. Stored as `{ street, city, state, postalCode, country, countryCode, formatted }` (all parts optional). No per-type config properties.
```typescript
{ name: 'billing_address', label: 'Billing Address', type: 'address' }
@@ -525,11 +525,9 @@ Source code with syntax highlighting.
| Property | Type | Default | Description |
|:---|:---|:---|:---|
| `language` | `string` | — | Programming language for highlighting |
-| `theme` | `string` | — | Editor theme |
-| `lineNumbers` | `boolean` | — | Show line numbers |
```typescript
-{ name: 'snippet', label: 'Code Snippet', type: 'code', language: 'typescript', lineNumbers: true }
+{ name: 'snippet', label: 'Code Snippet', type: 'code', language: 'typescript' }
```
### `json`
@@ -540,16 +538,10 @@ Raw JSON data.
```
### `color`
-Color picker with format options.
-
-| Property | Type | Default | Description |
-|:---|:---|:---|:---|
-| `colorFormat` | `'hex' \| 'rgb' \| 'rgba' \| 'hsl'` | — | Color value format |
-| `allowAlpha` | `boolean` | — | Allow alpha transparency |
-| `presetColors` | `string[]` | — | Preset color palette |
+Color picker. Stores the color value as a string. No per-type config properties.
```typescript
-{ name: 'brand_color', label: 'Brand Color', type: 'color', colorFormat: 'hex', presetColors: ['#FF0000', '#00FF00', '#0000FF'] }
+{ name: 'brand_color', label: 'Brand Color', type: 'color' }
```
### `rating`
@@ -557,11 +549,12 @@ Star rating input.
| Property | Type | Default | Description |
|:---|:---|:---|:---|
-| `maxRating` | `number` | `5` | Maximum rating value |
-| `allowHalf` | `boolean` | `false` | Allow half-star ratings |
+| `max` | `number` | `5` | Maximum rating value (set by the `Field.rating(max)` factory) |
```typescript
-{ name: 'satisfaction', label: 'Rating', type: 'rating', maxRating: 5, allowHalf: true }
+{ name: 'satisfaction', label: 'Rating', type: 'rating', max: 5 }
+// or with the factory:
+// satisfaction: Field.rating(5, { label: 'Rating' })
```
### `slider`
@@ -571,12 +564,10 @@ Range slider input.
|:---|:---|:---|:---|
| `min` | `number` | — | Minimum value |
| `max` | `number` | — | Maximum value |
-| `step` | `number` | — | Step increment |
-| `showValue` | `boolean` | — | Display current value |
-| `marks` | `object` | — | Tick marks on slider |
+| `step` | `number` | `1` | Step increment |
```typescript
-{ name: 'confidence', label: 'Confidence', type: 'slider', min: 0, max: 100, step: 5, showValue: true }
+{ name: 'confidence', label: 'Confidence', type: 'slider', min: 0, max: 100, step: 5 }
```
### `signature`
@@ -587,17 +578,10 @@ Digital signature capture.
```
### `qrcode`
-QR/barcode generator and scanner.
-
-| Property | Type | Default | Description |
-|:---|:---|:---|:---|
-| `barcodeFormat` | `'qr' \| 'ean13' \| 'ean8' \| 'code128' \| 'code39' \| 'upca' \| 'upce'` | — | Barcode format type |
-| `qrErrorCorrection` | `'L' \| 'M' \| 'Q' \| 'H'` | — | QR error correction level (only when `barcodeFormat` is `'qr'`) |
-| `displayValue` | `boolean` | — | Display human-readable value below barcode/QR code |
-| `allowScanning` | `boolean` | — | Enable camera scanning |
+QR/barcode value rendered as a scannable code. No per-type config properties.
```typescript
-{ name: 'asset_tag', label: 'Asset Tag', type: 'qrcode', allowScanning: true }
+{ name: 'asset_tag', label: 'Asset Tag', type: 'qrcode' }
```
### `progress`
@@ -623,17 +607,14 @@ Vector embeddings for semantic search and similarity.
| Property | Type | Default | Description |
|:---|:---|:---|:---|
-| `vectorConfig.dimensions` | `number` | **required** | Vector dimensionality (e.g., 1536 for OpenAI) |
-| `vectorConfig.distanceMetric` | `'cosine' \| 'euclidean' \| 'dotProduct' \| 'manhattan'` | `'cosine'` | Distance calculation method |
-| `vectorConfig.normalized` | `boolean` | `false` | Whether vectors are pre-normalized |
-| `vectorConfig.indexed` | `boolean` | `true` | Enable vector indexing |
-| `vectorConfig.indexType` | `'hnsw' \| 'ivfflat' \| 'flat'` | — | Index algorithm |
+| `dimensions` | `number` | **required** | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
+
+The nested `vectorConfig` object (with `distanceMetric`, `indexed`, `indexType`, …) is retained for back-compat only and is a runtime no-op — set the flat `dimensions` property instead.
```typescript
-{
- name: 'embedding', label: 'Embedding', type: 'vector',
- vectorConfig: { dimensions: 1536, distanceMetric: 'cosine', indexed: true, indexType: 'hnsw' }
-}
+{ name: 'embedding', label: 'Embedding', type: 'vector', dimensions: 1536 }
+// or with the factory:
+// embedding: Field.vector(1536, { label: 'Embedding' })
```
---
@@ -647,7 +628,7 @@ These properties are available on **all** field types:
| `name` | `string` | **required** | Machine name (snake_case) |
| `label` | `string` | — | Human-readable display label |
| `description` | `string` | — | Field description / help text |
-| `type` | `FieldType` | **required** | One of the 49 field types |
+| `type` | `FieldType` | **required** | One of the supported field types |
| `required` | `boolean` | `false` | Whether the field is required |
| `unique` | `boolean` | `false` | Enforce uniqueness |
| `multiple` | `boolean` | `false` | Allow array of values |
@@ -660,11 +641,9 @@ These properties are available on **all** field types:
| `defaultValue` | `any` | — | Default value for new records |
| `group` | `string` | — | Field grouping / section |
| `inlineHelpText` | `string` | — | Inline help tooltip |
-| `trackFeedHistory` | `boolean` | — | Track changes in activity feed |
-| `encryptionConfig` | `object` | — | Field-level encryption settings |
-| `maskingRule` | `object` | — | Data masking configuration |
+| `trackHistory` | `boolean` | — | Render this field's value changes as entries on the record activity timeline |
+| `requiredPermissions` | `string[]` | — | Capabilities required to read/edit this field (masked on read, denied on write) |
| `dependencies` | `string[]` | — | Names of fields this field depends on (formulas, visibility rules, etc.) |
-| `dataQuality` | `object` | — | Data quality validation rules |
| `visibleWhen` | `Expression` | — | Show field only when predicate is true |
| `readonlyWhen` | `Expression` | — | Make field read-only when predicate is true |
| `requiredWhen` | `Expression` | — | Require field when predicate is true |
@@ -672,71 +651,6 @@ These properties are available on **all** field types:
---
-## Field Type Decision Tree
-
-Use this guide to choose the right field type:
-
-```
-Is it text?
-├── Short text (< 255 chars) → text
-├── Long text → textarea
-├── Formatted text → markdown | html | richtext
-├── Email address → email
-├── URL → url
-├── Phone number → phone
-├── Login password (one-way hash) → password
-├── Reversible secret (API key/token) → secret
-└── Source code → code
-
-Is it a number?
-├── Integer or decimal → number
-├── Money amount → currency
-└── Percentage → percent
-
-Is it a date/time?
-├── Date only → date
-├── Date + time → datetime
-└── Time only → time
-
-Is it a choice?
-├── Single choice (dropdown) → select
-├── Single choice (visible) → radio
-├── Multiple choices (dropdown) → multiselect
-└── Multiple choices (visible) → checkboxes
-
-Is it a reference?
-├── Simple foreign key → lookup
-├── Parent-child (cascade delete) → master_detail
-└── Self-referential hierarchy → tree
-
-Is it a file?
-├── Any file → file
-├── Image → image
-├── Profile picture → avatar
-├── Video → video
-└── Audio → audio
-
-Is it computed?
-├── Formula calculation → formula
-├── Roll-up summary → summary
-└── Auto-incrementing ID → autonumber
-
-Is it embedded structured data (stored as JSON, no separate table)?
-├── Single embedded sub-object → composite
-├── Repeating embedded array → repeater
-└── Name-keyed map of sub-objects → record
-
-Is it specialized?
-├── Geographic location → location
-├── Postal address → address
-├── Yes/No toggle → boolean | toggle
-├── Star rating → rating
-├── Range slider → slider
-├── Color picker → color
-├── Raw JSON → json
-├── Tags/Labels → tags
-├── Progress bar → progress
-├── Signature → signature
-├── QR/Barcode → qrcode
-└── AI embedding → vector
-```
+## Choosing a Field Type
+
+Not sure which type fits your data? Follow the [Field Type Decision Tree](/docs/data-modeling/field-type-decision-tree) — a flowchart, quick-reference tables, and a use-case mapping for every type in this gallery.
diff --git a/content/docs/data-modeling/fields.mdx b/content/docs/data-modeling/fields.mdx
index fdeb4bdd62..bfa4d1075f 100644
--- a/content/docs/data-modeling/fields.mdx
+++ b/content/docs/data-modeling/fields.mdx
@@ -1,11 +1,11 @@
---
title: Field Metadata
-description: Configure field types and properties — 49 field types for text, numbers, dates, relationships, and more
+description: Configure field types and properties — text, numbers, dates, relationships, files, and more
---
# Field Metadata
-A **Field** defines an individual property within an Object. ObjectStack provides 49 field types covering text, numbers, dates, selections, relationships, files, calculations, and specialized types like vectors and QR codes.
+A **Field** defines an individual property within an Object. ObjectStack provides a comprehensive set of field types covering text, numbers, dates, selections, relationships, files, calculations, and specialized types like vectors and QR codes.
## Basic Usage
@@ -172,7 +172,7 @@ order: Field.masterDetail('order', {
| `reference` (1st arg) | `string` | Target object name |
| `referenceFilters` | `string[]` | Filter conditions for the lookup |
| `deleteBehavior` | `enum` | `'set_null'`, `'cascade'`, `'restrict'` |
-| `inlineEdit` | `boolean` | Render child records inline on the parent create/edit form |
+| `inlineEdit` | `boolean \| 'grid' \| 'form'` | Render child records inline on the parent create/edit form (`true` = auto-pick, `'grid'`, or `'form'`) |
| `inlineColumns` | `array` | Optional explicit columns for the inline grid |
| `inlineAmountField` | `string` | Optional numeric child field for the inline running total |
@@ -248,9 +248,9 @@ when multiple relationships target the same parent object.
| `progress` | — | Progress bar |
```typescript
-office: Field.address({ label: 'Office Address', addressFormat: 'international' }),
-coordinates: Field.location({ label: 'Location', displayMap: true }),
-brand_color: Field.color({ label: 'Color', colorFormat: 'hex' }),
+office: Field.address({ label: 'Office Address' }),
+coordinates: Field.location({ label: 'Location' }),
+brand_color: Field.color({ label: 'Color' }),
satisfaction: Field.rating(5, { label: 'Rating' }),
approval_signature: Field.signature({ label: 'Signature' }),
embedding: Field.vector(1536, { label: 'Embedding' }),
@@ -296,9 +296,11 @@ These properties are available on all field types:
| Property | Type | Default | Description |
| :--- | :--- | :--- | :--- |
-| `encryptionConfig` | `object` | — | Field-level encryption settings |
-| `maskingRule` | `object` | — | Data masking rules for display |
-| `auditTrail` | `boolean` | `false` | Track all changes to this field |
+| `requiredPermissions` | `string[]` | — | Capabilities required to read/edit this field — masked on read, denied on write unless the caller holds all of them (ADR-0066 D3) |
+| `trackHistory` | `boolean` | — | Render this field's value changes as entries on the record activity timeline |
+
+For values that must be encrypted at rest (API keys, tokens, DB passwords), use the
+`secret` field type — see the [Field Type Gallery](/docs/data-modeling/field-types#secret).
### Conditional Logic
diff --git a/content/docs/data-modeling/formulas.mdx b/content/docs/data-modeling/formulas.mdx
index 323a75dcea..9a152b3a51 100644
--- a/content/docs/data-modeling/formulas.mdx
+++ b/content/docs/data-modeling/formulas.mdx
@@ -153,13 +153,22 @@ All functions are pure given a pinned `now`, which is what makes
| Function | Returns | Description |
|:---|:---|:---|
| `now()` | timestamp | Pinned wall-clock at evaluation start |
-| `today()` | timestamp | `now()` truncated to UTC start-of-day |
-| `daysFromNow(n)` | timestamp | `now() + n` days (preserves wall-clock time, not start-of-day) |
-| `daysAgo(n)` | timestamp | `now() - n` days |
+| `today()` | timestamp | Calendar day in the business timezone, as UTC midnight |
+| `daysFromNow(n)` / `daysAgo(n)` | timestamp | `today() ± n` days (calendar-day, not wall-clock) |
+| `addDays(d, n)` / `addMonths(d, n)` | timestamp | Shift a *given* date; `addMonths` clamps to month end (Jan 31 + 1mo → Feb 28) |
+| `date(s)` / `datetime(s)` | timestamp | Parse an ISO date / date-time string (aliases) |
+| `daysBetween(a, b)` | int | Whole days from `a` to `b` (negative when `b` is earlier) |
| `isBlank(v)` | bool | True for `null`, `undefined`, `''`, `[]` |
+| `isEmpty(v)` | bool | True for `null` or zero-length string/list/map |
| `coalesce(v, fallback)` | dyn | `v` when non-null, else `fallback` |
| `trim(v)` | string | `v` with leading/trailing whitespace removed |
| `joinNonEmpty(list, sep)` | string | `list` joined by `sep`, skipping blank entries |
+| `upper(s)` / `lower(s)` | string | Case conversion |
+| `contains(s, sub)` / `startsWith(s, p)` / `endsWith(s, p)` | bool | Substring checks (free-function form) |
+| `matches(s, regex)` | bool | Regex test |
+| `len(v)` | int | Length of a string / list / map (mirrors CEL's built-in `size()`) |
+| `abs(x)` / `round(x)` | number | Numeric helpers |
+| `min(a, b)` / `max(a, b)` | dyn | Smaller / larger operand (numeric comparison) |
Add new helpers in
[`packages/formula/src/stdlib.ts`](https://github.com/objectstack-ai/framework/blob/main/packages/formula/src/stdlib.ts).
@@ -213,7 +222,7 @@ Building the string by hand with `+` would also work, but CEL throws on
```ts
{
name: 'rating',
- type: 'picklist',
+ type: 'select',
visibleWhen: P`record.status == 'qualified'`,
}
@@ -232,13 +241,13 @@ identical SHA-1 across builds while still being "fresh" relative to whoever
installs the package.
```ts
-import { defineDataset, cel } from '@objectstack/spec';
+import { defineSeed } from '@objectstack/spec/data';
+import { cel } from '@objectstack/spec';
+import { Opportunity } from './objects/opportunity.object';
-export const opportunityDataset = defineDataset({
- object: 'opportunity',
+export const opportunitySeed = defineSeed(Opportunity, {
records: [
{
- id: 'opp_acme',
name: 'Acme Q3 Renewal',
close_date: cel`daysFromNow(45)`,
created_at: cel`now()`,
diff --git a/content/docs/data-modeling/index.mdx b/content/docs/data-modeling/index.mdx
index 3e0d63dd3d..4fa4713aff 100644
--- a/content/docs/data-modeling/index.mdx
+++ b/content/docs/data-modeling/index.mdx
@@ -1,6 +1,6 @@
---
title: Data Modeling
-description: Objects, 49 field types, relationships, validation, CEL formulas, and a compiled query AST — the ObjectQL layer every other module builds on.
+description: Objects, fields, relationships, validation, CEL formulas, and a compiled query AST — the ObjectQL layer every other module builds on.
---
# Data Modeling
@@ -31,7 +31,7 @@ That one definition is enough to get a persisted table, CRUD + query endpoints,
## What the data layer actually gives you
-- **49 field types** — from `text`, `currency`, and `lookup`/`master_detail` relationships to `formula`, `summary`, `signature`, `qrcode`, and `vector` (with HNSW/IVFFlat index config for AI embeddings). See the [Field Types gallery](/docs/data-modeling/field-types).
+- **A full spectrum of field types** — from `text`, `currency`, and `lookup`/`master_detail` relationships to `formula`, `summary`, `signature`, and `vector` (with index config for AI embeddings). See the [Field Types gallery](/docs/data-modeling/field-types).
- **Validation as metadata** — required/format rules, CEL script validation with access to `previous.`, uniqueness via indexes, and severity levels — enforced identically in API, UI, and automation.
- **CEL expressions** — formula fields and computed defaults share one expression language ([Expressions](/docs/data-modeling/formulas)).
- **A compiled query AST** — queries are JSON documents validated against the protocol, then compiled by a driver into native queries with joins, aggregations, window functions, HAVING, and subqueries. The [query cheat sheet](/docs/data-modeling/queries) covers the syntax; the [spec](/docs/protocol/objectql/query-syntax) is normative.
@@ -44,7 +44,7 @@ That one definition is enough to get a persisted table, CRUD + query endpoints,
-
+
diff --git a/content/docs/data-modeling/objects.mdx b/content/docs/data-modeling/objects.mdx
index 7aafbd7a7d..82f84db261 100644
--- a/content/docs/data-modeling/objects.mdx
+++ b/content/docs/data-modeling/objects.mdx
@@ -30,7 +30,7 @@ export const Account = ObjectSchema.create({
],
}),
annual_revenue: Field.currency({ label: 'Annual Revenue', scale: 2 }),
- owner: Field.lookup('user', { label: 'Owner', required: true }),
+ owner: Field.user({ label: 'Owner', required: true }),
},
enable: {
@@ -66,8 +66,8 @@ export const Account = ObjectSchema.create({
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
-| `displayNameField` | `string` | optional | Field used as record display name (defaults to `'name'`) |
-| `titleFormat` | `string` | optional | Title expression (e.g. `'{name} - {code}'`) |
+| `nameField` | `string` | optional | The stored field used as the record display name, e.g. `'name'` or `'title'` (ADR-0079). The deprecated alias `displayNameField` is still accepted. |
+| `titleFormat` | `string` | optional | Deprecated (ADR-0079 → `nameField`). Render-only title template (e.g. `'{{record.name}} - {{record.code}}'`); an explicit `nameField` takes precedence |
| `highlightFields` | `string[]` | optional | Most-important fields in priority order — default list columns, cards, previews, detail highlight strip (ADR-0085; formerly `compactLayout` — the old spelling was retired and is now rejected) |
| `stageField` | `string \| false` | optional | Linear lifecycle field; `false` declares the status field non-linear and suppresses stage heuristics (ADR-0085) |
| `recordName` | `object` | optional | Record name auto-generation config |
@@ -138,27 +138,6 @@ versioning: {
}
```
-#### Change Data Capture
-
-```typescript
-cdc: {
- enabled: true,
- events: ['insert', 'update', 'delete'],
- destination: 'kafka://my-topic',
-}
-```
-
-#### Table Partitioning
-
-```typescript
-partitioning: {
- enabled: true,
- strategy: 'range', // 'range' | 'hash' | 'list'
- key: 'created_at',
- interval: '1 month', // Required for range strategy
-}
-```
-
### Record Name
Auto-generate unique record identifiers:
@@ -206,7 +185,6 @@ indexes: [
| `abstract` | `boolean` | Abstract base, cannot be instantiated (default: `false`) |
| `sharingModel` | `enum` | Org-Wide Default record visibility (ADR-0055/0056). Canonical: `'private'`, `'public_read'`, `'public_read_write'`, `'controlled_by_parent'` (detail visibility derived from its master). Legacy aliases: `'read'`=public_read, `'read_write'`/`'full'`=public_read_write |
| `keyPrefix` | `string` | Short prefix for record IDs (e.g. `'001'`) |
-| `recordTypes` | `string[]` | Record type names for this object |
| `validations` | `ValidationRule[]` | Object-level validation rules (see [Validation](/docs/data-modeling/validation)) |
## Naming Conventions
@@ -249,7 +227,7 @@ export const ProjectTask = ObjectSchema.create({
],
}),
due_date: Field.date({ label: 'Due Date' }),
- assignee: Field.lookup('user', { label: 'Assignee' }),
+ assignee: Field.user({ label: 'Assignee' }),
project: Field.lookup('project', { label: 'Project', required: true }),
estimated_hours: Field.number({ label: 'Estimated Hours', min: 0 }),
},
@@ -273,7 +251,7 @@ export const ProjectTask = ObjectSchema.create({
type: 'script',
severity: 'warning',
message: 'Due date should be in the future',
- condition: 'due_date < today()',
+ condition: 'record.due_date < today()',
events: ['insert'],
},
],
diff --git a/content/docs/data-modeling/queries.mdx b/content/docs/data-modeling/queries.mdx
index 4c69076512..b010f605df 100644
--- a/content/docs/data-modeling/queries.mdx
+++ b/content/docs/data-modeling/queries.mdx
@@ -201,6 +201,33 @@ position from the previous page. There is no `keyset`/`after` query property.
---
+## Expand (Related Records)
+
+Load related records through `lookup` / `master_detail` fields with `expand`.
+Each key is a relationship field name; the value is a nested query that can
+select fields, filter, and expand further (default max depth: 3).
+
+```typescript
+{
+ object: 'task',
+ fields: ['title', 'assignee'],
+ expand: {
+ assignee: { object: 'user', fields: ['name', 'email'] },
+ project: {
+ object: 'project',
+ where: { is_active: { $eq: true } }, // AND-merged with the batch lookup
+ expand: { org: { object: 'org' } } // nested expand
+ }
+ }
+}
+```
+
+The engine resolves `expand` via batch `$in` queries (driver-agnostic), so it
+works on every driver. Per-parent `limit` / `offset` / `orderBy` are **not**
+applied on this path.
+
+---
+
## Aggregations
### Available Functions
diff --git a/content/docs/data-modeling/relationships.mdx b/content/docs/data-modeling/relationships.mdx
index 19925f8b8f..632d61237c 100644
--- a/content/docs/data-modeling/relationships.mdx
+++ b/content/docs/data-modeling/relationships.mdx
@@ -50,6 +50,14 @@ Child records automatically appear in related lists when a lookup points to the
- Account has many Contacts (Contact.account → Account)
- Account detail page shows "Contacts" related list
+### Other relationship types
+
+- **Master-detail** (`Field.masterDetail`) — parent-child ownership with cascade delete and optional inline line-item editing on the parent form; see [`master_detail`](/docs/data-modeling/field-types#master_detail).
+- **Tree** (`type: 'tree'`) — self-referential hierarchies (categories, org charts); see [`tree`](/docs/data-modeling/field-types#tree).
+- **User** (`Field.user`) — a person picker, i.e. a lookup specialized to the built-in `sys_user` object; see [`user`](/docs/data-modeling/field-types#user).
+- **Roll-ups over children** — aggregate child records onto the parent with a [`summary` field](/docs/data-modeling/fields#calculation-types).
+- **Querying across relationships** — load related records with `expand`; see the [query cheat sheet](/docs/data-modeling/queries#expand-related-records).
+
---
## See also
diff --git a/content/docs/data-modeling/schema-design.mdx b/content/docs/data-modeling/schema-design.mdx
index cceddbf273..16c7f9bcc6 100644
--- a/content/docs/data-modeling/schema-design.mdx
+++ b/content/docs/data-modeling/schema-design.mdx
@@ -119,12 +119,16 @@ Generates sequential numbers automatically:
```typescript
Field.autonumber({
label: 'Account Number',
- format: 'ACC-{0000}', // ACC-0001, ACC-0002, ...
+ autonumberFormat: 'ACC-{0000}', // ACC-0001, ACC-0002, ...
})
-// The format string supports a single zero-pad token, e.g.:
-// 'INV-{000}' // INV-001, INV-002, ...
-// Date tokens like {YYYY}/{MM}/{DD} are not interpolated.
+// The format string is literal text plus {…} tokens:
+// {0000} — the sequence counter, zero-padded (at most one)
+// {YYYY} {YY} {MM} {DD} {YYYYMMDD} — generation date in the business timezone
+// {field_name} — another field on the same record
+// The counter is scoped to whatever renders before the {0000} slot, so
+// 'AD{YYYYMMDD}{0000}' resets daily and '{plan_no}{000}' counts per parent —
+// no separate reset config. A fixed prefix like 'INV-{000}' keeps one global counter.
```
### User Fields
@@ -200,7 +204,7 @@ export const Account = ObjectSchema.create({
vat_id: { type: 'text', group: 'billing' },
billing_address: { type: 'address', group: 'billing' },
created_at: { type: 'datetime', readonly: true, group: 'system' },
- created_by: { type: 'lookup', reference: 'user', readonly: true, group: 'system' },
+ created_by: { type: 'user', reference: 'sys_user', readonly: true, group: 'system' },
},
});
```
@@ -311,7 +315,7 @@ export const Account = ObjectSchema.create({
fields: {
account_number: Field.autonumber({
label: 'Account Number',
- format: 'ACC-{0000}',
+ autonumberFormat: 'ACC-{0000}',
}),
name: Field.text({
@@ -338,10 +342,9 @@ export const Account = ObjectSchema.create({
billing_address: Field.address({
label: 'Billing Address',
- addressFormat: 'international',
}),
- owner: Field.lookup('user', {
+ owner: Field.user({
label: 'Account Owner',
required: true,
}),
@@ -367,7 +370,7 @@ export const Account = ObjectSchema.create({
type: 'script',
severity: 'error',
message: 'Revenue must be positive',
- condition: 'annual_revenue < 0',
+ condition: 'record.annual_revenue < 0',
},
],
});
diff --git a/content/docs/data-modeling/validation-rules.mdx b/content/docs/data-modeling/validation-rules.mdx
index ad540d00e7..02d221e72f 100644
--- a/content/docs/data-modeling/validation-rules.mdx
+++ b/content/docs/data-modeling/validation-rules.mdx
@@ -5,7 +5,7 @@ description: Default validation behavior, required properties, and constraints f
# Field Validation Rules
-Every ObjectStack field type has built-in validation behavior that runs automatically at the schema level. This reference documents the **default constraints**, **required properties**, and **validation semantics** for each of the 48 field types.
+Every ObjectStack field type has built-in validation behavior that runs automatically at the schema level. This reference documents the **default constraints**, **required properties**, and **validation semantics** for each field type.
**Source:** `packages/spec/src/data/field.zod.ts`
@@ -22,7 +22,7 @@ These properties apply to **all** field types and are validated by the base `Fie
|:---|:---|:---|:---|
| `name` | `string` | — | Must match `^[a-z_][a-z0-9_]*$` (snake_case) |
| `label` | `string` | — | Human-readable display name |
-| `type` | `FieldType` | — | Must be one of the 48 defined types |
+| `type` | `FieldType` | — | Must be a member of the `FieldType` enum |
| `required` | `boolean` | `false` | Rejects `null`/`undefined` at runtime when `true` |
| `unique` | `boolean` | `false` | Enforces database-level uniqueness constraint |
| `multiple` | `boolean` | `false` | Stores value as array (applicable for select, lookup, file, image) |
@@ -31,7 +31,7 @@ These properties apply to **all** field types and are validated by the base `Fie
| `sortable` | `boolean` | `true` | Whether field appears in list view sort options |
| `index` | `boolean` | `false` | Creates standard database index |
| `externalId` | `boolean` | `false` | Marks field as external ID for upsert operations |
-| `auditTrail` | `boolean` | `false` | Tracks all changes with user and timestamp |
+| `trackHistory` | `boolean` | — | Render this field's value changes as entries on the record activity timeline |
| `visibleWhen` | `string \| Expression` | — | CEL predicate; field is shown only when `TRUE` |
| `readonlyWhen` | `string \| Expression` | — | CEL predicate; field is read-only when `TRUE` |
| `requiredWhen` | `string \| Expression` | — | CEL predicate; field is required when `TRUE` |
@@ -48,7 +48,6 @@ These properties apply to **all** field types and are validated by the base `Fie
| `maxLength` | `number` | — | Rejects values exceeding character count |
| `minLength` | `number` | — | Rejects values below character count |
| `format` | `string` | — | Validates against format pattern (e.g., regex) |
-| `caseSensitive` | `boolean` | — | Controls case-sensitivity in uniqueness checks |
**Default constraints:** None. Unbounded text unless `maxLength` is set.
@@ -66,7 +65,6 @@ These properties apply to **all** field types and are validated by the base `Fie
| Property | Type | Default | Validation Behavior |
|:---|:---|:---|:---|
| `format` | `string` | `email` | Validates RFC 5322 email format |
-| `caseSensitive` | `boolean` | — | Controls case-sensitivity in uniqueness checks |
**Default constraints:** Must conform to valid email format.
@@ -93,7 +91,7 @@ These properties apply to **all** field types and are validated by the base `Fie
| `maxLength` | `number` | — | Maximum password length |
| `minLength` | `number` | — | Minimum password length |
-**Default constraints:** Value is never returned in read operations. Stored encrypted.
+**Default constraints:** Value is never returned in read operations. Stored as a one-way hash owned by the auth subsystem — for reversible encrypted-at-rest secrets, use the `secret` type instead.
---
@@ -257,10 +255,10 @@ These properties apply to **all** field types and are validated by the base `Fie
| Property | Type | Default | Validation Behavior |
|:---|:---|:---|:---|
| `reference` | `string` | — | **Required.** Parent object name |
-| `deleteBehavior` | `enum` | `set_null` | Typically `cascade` for master-detail |
+| `deleteBehavior` | `enum` | `cascade` | Master-detail cascades at runtime unless set to `restrict` |
| `writeRequiresMasterRead` | `boolean` | — | Require read access to master record |
-**Default constraints:** Enforces parent-child ownership. Child records cascade-delete with parent by convention.
+**Default constraints:** Enforces parent-child ownership. Child records cascade-delete with the parent by default.
### `tree`
@@ -337,7 +335,6 @@ These properties apply to **all** field types and are validated by the base `Fie
|:---|:---|:---|:---|
| `expression` | `string` | — | **Required.** Formula expression |
| `dependencies` | `string[]` | — | Fields this formula depends on |
-| `cached` | `object` | — | Caching configuration with TTL and invalidation triggers |
**Default constraints:** Read-only. Value computed at runtime from `expression`. Not directly writable.
@@ -347,8 +344,7 @@ These properties apply to **all** field types and are validated by the base `Fie
label: 'Full Name',
type: 'formula',
expression: 'record.first_name + " " + record.last_name',
- dependencies: ['first_name', 'last_name'],
- cached: { enabled: true, ttl: 3600, invalidateOn: ['first_name', 'last_name'] }
+ dependencies: ['first_name', 'last_name']
}
```
@@ -377,28 +373,17 @@ These properties apply to **all** field types and are validated by the base `Fie
### `location`
-| Property | Type | Default | Validation Behavior |
-|:---|:---|:---|:---|
-| `displayMap` | `boolean` | — | Show map widget in UI |
-| `allowGeocoding` | `boolean` | — | Enable address-to-coordinate conversion |
-
-**Default constraints:** Stored as `{ latitude, longitude, altitude?, accuracy? }`. Latitude: -90 to 90. Longitude: -180 to 180.
+**Default constraints:** Stored as `{ latitude, longitude, altitude?, accuracy? }`. Latitude: -90 to 90. Longitude: -180 to 180. No per-type config properties.
### `address`
-| Property | Type | Default | Validation Behavior |
-|:---|:---|:---|:---|
-| `addressFormat` | `enum` | — | `us`, `uk`, or `international` |
-
-**Default constraints:** Stored as structured object with `street`, `city`, `state`, `postalCode`, `country`, `countryCode`, `formatted`.
+**Default constraints:** Stored as structured object with `street`, `city`, `state`, `postalCode`, `country`, `countryCode`, `formatted` (all parts optional). No per-type config properties.
### `code`
| Property | Type | Default | Validation Behavior |
|:---|:---|:---|:---|
| `language` | `string` | — | Programming language for syntax highlighting |
-| `theme` | `string` | — | Editor theme (e.g., `dark`, `monokai`) |
-| `lineNumbers` | `boolean` | — | Show line numbers in editor |
**Default constraints:** Stored as plain text. Language used for UI syntax highlighting only.
@@ -408,22 +393,15 @@ These properties apply to **all** field types and are validated by the base `Fie
### `color`
-| Property | Type | Default | Validation Behavior |
-|:---|:---|:---|:---|
-| `colorFormat` | `enum` | — | `hex`, `rgb`, `rgba`, or `hsl` |
-| `allowAlpha` | `boolean` | — | Allow transparency channel |
-| `presetColors` | `string[]` | — | Predefined color swatches |
-
-**Default constraints:** Validated against the specified `colorFormat`. Defaults to hex if not specified.
+**Default constraints:** Stores the color value as a string. No per-type config properties.
### `rating`
| Property | Type | Default | Validation Behavior |
|:---|:---|:---|:---|
-| `maxRating` | `number` | `5` | Maximum rating value |
-| `allowHalf` | `boolean` | — | Allow half-step increments |
+| `max` | `number` | `5` | Maximum rating value (set by the `Field.rating(max)` factory) |
-**Default constraints:** Integer between 0 and `maxRating`. Half-steps allowed when `allowHalf` is true.
+**Default constraints:** Integer between 0 and `max`.
### `slider`
@@ -432,8 +410,6 @@ These properties apply to **all** field types and are validated by the base `Fie
| `min` | `number` | — | Minimum slider value |
| `max` | `number` | — | Maximum slider value |
| `step` | `number` | `1` | Step increment |
-| `showValue` | `boolean` | — | Display current value |
-| `marks` | `Record` | — | Custom labels at specific values |
**Default constraints:** Numeric value between `min` and `max` in `step` increments.
@@ -443,14 +419,7 @@ These properties apply to **all** field types and are validated by the base `Fie
### `qrcode`
-| Property | Type | Default | Validation Behavior |
-|:---|:---|:---|:---|
-| `barcodeFormat` | `enum` | — | `qr`, `ean13`, `ean8`, `code128`, `code39`, `upca`, `upce` |
-| `qrErrorCorrection` | `enum` | — | `L` (7%), `M` (15%), `Q` (25%), `H` (30%) — only for `qr` format |
-| `displayValue` | `boolean` | — | Show human-readable value below code |
-| `allowScanning` | `boolean` | — | Enable camera scanning input |
-
-**Default constraints:** `qrErrorCorrection` only applies when `barcodeFormat` is `qr`. Value validated against barcode format rules.
+**Default constraints:** Stores the encoded value; rendered as a scannable code. No per-type config properties.
### `progress`
@@ -473,51 +442,16 @@ These properties apply to **all** field types and are validated by the base `Fie
| Property | Type | Default | Validation Behavior |
|:---|:---|:---|:---|
-| `vectorConfig.dimensions` | `number` | — | **Required.** Vector size (1–10,000) |
-| `vectorConfig.distanceMetric` | `enum` | `cosine` | `cosine`, `euclidean`, `dotProduct`, `manhattan` |
-| `vectorConfig.normalized` | `boolean` | `false` | Whether vectors are unit-length |
-| `vectorConfig.indexed` | `boolean` | `true` | Create vector index for similarity search |
-| `vectorConfig.indexType` | `enum` | — | `hnsw`, `ivfflat`, or `flat` |
+| `dimensions` | `number` | — | **Required.** Vector size (1–10,000) |
-**Default constraints:** Must be a numeric array of exactly `dimensions` length. Indexed by default for fast similarity search.
+**Default constraints:** Must be a numeric array of exactly `dimensions` length. The nested `vectorConfig` object is retained for back-compat only and is a runtime no-op — set the flat `dimensions` property instead.
```typescript
{
name: 'content_embedding',
label: 'Content Embedding',
type: 'vector',
- vectorConfig: {
- dimensions: 1536,
- distanceMetric: 'cosine',
- indexed: true,
- indexType: 'hnsw'
- }
-}
-```
-
----
-
-## Data Quality Rules
-
-Any field can optionally include `dataQuality` rules for governance and monitoring:
-
-| Property | Type | Default | Validation Behavior |
-|:---|:---|:---|:---|
-| `dataQuality.uniqueness` | `boolean` | `false` | Enforce unique values across all records |
-| `dataQuality.completeness` | `number` | `0` | Minimum ratio of non-null values (0–1) |
-| `dataQuality.accuracy.source` | `string` | — | Reference data source for validation |
-| `dataQuality.accuracy.threshold` | `number` | — | Minimum accuracy threshold (0–1) |
-
-```typescript
-{
- name: 'social_security',
- label: 'SSN',
- type: 'text',
- dataQuality: {
- uniqueness: true,
- completeness: 0.95,
- accuracy: { source: 'government_db', threshold: 0.98 }
- }
+ dimensions: 1536
}
```
@@ -525,13 +459,16 @@ Any field can optionally include `dataQuality` rules for governance and monitori
## Security & Compliance Properties
-Fields supporting sensitive data can leverage encryption and masking:
+For sensitive data, use the properties and types the platform actually enforces:
| Property | Type | Default | Description |
|:---|:---|:---|:---|
-| `encryptionConfig` | `EncryptionConfig` | — | Field-level encryption (GDPR/HIPAA/PCI-DSS) |
-| `maskingRule` | `MaskingRule` | — | Data masking rules for PII protection |
-| `auditTrail` | `boolean` | `false` | Track all changes with user and timestamp |
+| `requiredPermissions` | `string[]` | — | Capabilities required to read/edit the field — masked on read, denied on write unless the caller holds all of them (ADR-0066 D3) |
+| `trackHistory` | `boolean` | — | Render the field's value changes as entries on the record activity timeline |
+
+For reversible encrypted-at-rest values (API keys, tokens, DB passwords), use the
+`secret` field type; for credentials, use `password` (one-way hash). See the
+[Field Type Gallery](/docs/data-modeling/field-types).
---
@@ -544,7 +481,7 @@ Fields supporting sensitive data can leverage encryption and masking:
| `email` | — | RFC 5322 email format |
| `url` | — | Valid URL with protocol |
| `phone` | — | E.164 / national format |
-| `password` | — | Stored encrypted, never returned |
+| `password` | — | One-way hash, never returned |
| `markdown` | — | `maxLength` |
| `html` | — | Sanitized, `maxLength` |
| `richtext` | — | Sanitized, `maxLength` |
@@ -572,14 +509,14 @@ Fields supporting sensitive data can leverage encryption and masking:
| `summary` | `summaryOperations` | Read-only, roll-up from children |
| `autonumber` | — | Read-only, auto-incremented |
| `location` | — | Lat: -90–90, Lng: -180–180 |
-| `address` | — | Structured object, `addressFormat` |
+| `address` | — | Structured object (street, city, …) |
| `code` | — | Plain text, `language` for highlighting |
| `json` | — | Must be valid JSON |
-| `color` | — | Validated against `colorFormat` |
-| `rating` | — | 0 to `maxRating` (default 5) |
+| `color` | — | Stored as a string |
+| `rating` | — | 0 to `max` (default 5) |
| `slider` | — | `min` to `max` in `step` increments |
| `signature` | — | Base64 image, typically immutable |
| `qrcode` | — | Format-specific validation |
| `progress` | — | Numeric, typically 0–100 |
| `tags` | — | String array, trimmed, deduplicated |
-| `vector` | `vectorConfig` | Numeric array of exact `dimensions` length |
+| `vector` | `dimensions` | Numeric array of exact `dimensions` length |
diff --git a/content/docs/deployment/cloud-artifact-api.mdx b/content/docs/deployment/cloud-artifact-api.mdx
index 8727d5187b..4f5bcf3edc 100644
--- a/content/docs/deployment/cloud-artifact-api.mdx
+++ b/content/docs/deployment/cloud-artifact-api.mdx
@@ -110,8 +110,7 @@ calls, not data-plane calls.
| File | Purpose |
|:---|:---|
-| `packages/cli/src/commands/publish.ts` | CLI publish command and endpoint construction. |
-| `packages/cli/src/commands/rollback.ts` | CLI revision activation command. |
+| `packages/cli/src/commands/package/publish.ts` | CLI `os package publish` command and endpoint construction (the legacy `publish.ts` / `rollback.ts` commands were removed — #2237). |
| `packages/runtime/src/http-dispatcher.ts` | Kernel-resolution seam for per-request environment resolution. The concrete id/hostname registry ships in the host distribution `@objectstack/objectos-runtime` (not part of this open-source repo). |
| `packages/cloud-connection/src/runtime-config-plugin.ts` | Console runtime-config for active/default environment state. |
| `packages/spec/src/system/environment-artifact.zod.ts` | Normative artifact envelope schema. |
diff --git a/content/docs/deployment/environment-variables.mdx b/content/docs/deployment/environment-variables.mdx
index 4e93e18aea..3945318e18 100644
--- a/content/docs/deployment/environment-variables.mdx
+++ b/content/docs/deployment/environment-variables.mdx
@@ -28,6 +28,9 @@ read at startup unless noted otherwise. Boolean variables accept `true` / `false
| `OS_MODE` | enum | `standalone` | Runtime mode. `standalone` \| `cloud`. |
| `OS_BOOT_EMPTY` | flag | `0` | When `1`, boot the kernel with no metadata artifact (CLI internal). |
| `OS_MIGRATE_AND_EXIT` | flag | `0` | When `1`, run pending migrations and exit (suitable for one-shot jobs). |
+| `OS_DISABLE_CONSOLE` | flag | `0` | When `1`, do not mount the Console portal under `/_console`. |
+| `OS_EAGER_SCHEMAS` | flag | `0` | When `1`, eagerly materialise all object schemas at boot (slower start, faster first request). |
+| `OS_SKIP_SCHEMA_SYNC` | flag | `0` | When `1`, skip the implicit `db:sync` on boot. Use after running migrations manually. |
> **Port conflicts behave differently by mode.** In **dev** (`os dev`, or
> `NODE_ENV=development`) a busy port auto-hops to the next free one (up to
@@ -35,11 +38,8 @@ read at startup unless noted otherwise. Boolean variables accept `true` / `false
> (`os start`) a busy port is a hard error (exit 1) — it never silently
> drifts, because a shifted port breaks reverse-proxy upstreams, `OS_AUTH_URL`
> callbacks, and `OS_TRUSTED_ORIGINS` (CORS). Pin the port explicitly in
-> production (`PORT=8080 os start`) and keep `OS_AUTH_URL` / `OS_TRUSTED_ORIGINS`
+> production (`OS_PORT=8080 os start`) and keep `OS_AUTH_URL` / `OS_TRUSTED_ORIGINS`
> in sync with it.
-| `OS_DISABLE_CONSOLE` | flag | `0` | When `1`, do not mount the Console portal under `/_console`. |
-| `OS_EAGER_SCHEMAS` | flag | `0` | When `1`, eagerly materialise all object schemas at boot (slower start, faster first request). |
-| `OS_SKIP_SCHEMA_SYNC` | flag | `0` | When `1`, skip the implicit `db:sync` on boot. Use after running migrations manually. |
---
@@ -54,6 +54,17 @@ read at startup unless noted otherwise. Boolean variables accept `true` / `false
---
+## Secrets & Clustering
+
+| Variable | Type | Default | Description |
+|:---|:---|:---|:---|
+| `OS_SECRET_KEY` | string | — | 32-byte master key (64 hex chars or base64) for `sys_secret` encryption — encrypted settings, `secret` fields, datasource credentials. **Required** for containerized or multi-node deployments; on a single durable host `os start` mints and persists a dev key instead. See [Deployment Modes](/docs/deployment#environment-variables). |
+| `OS_DEV_CRYPTO_KEY` | string | — | Development convenience crypto key, consulted after `OS_SECRET_KEY`. Do not use in production. |
+| `OS_CLUSTER_DRIVER` | string | `memory` | Cluster coordination driver id. When set to anything other than `memory`, the runtime treats the deployment as multi-node (and requires `OS_SECRET_KEY`). Non-memory drivers (e.g. `redis`) ship in the EE distribution. |
+| `OS_REDIS_URL` | url | — | Connection URL passed to a non-memory cluster driver (e.g. `OS_CLUSTER_DRIVER=redis`). |
+
+---
+
## Authentication
| Variable | Type | Default | Description |
diff --git a/content/docs/deployment/troubleshooting.mdx b/content/docs/deployment/troubleshooting.mdx
index c2570180af..dca477db9e 100644
--- a/content/docs/deployment/troubleshooting.mdx
+++ b/content/docs/deployment/troubleshooting.mdx
@@ -125,7 +125,7 @@ The custom error map provides "Did you mean?" suggestions for common typos.
```
$eq, $ne, $gt, $gte, $lt, $lte,
$in, $nin, $between,
-$contains, $startsWith, $endsWith,
+$contains, $notContains, $startsWith, $endsWith,
$null, $exists,
$and, $or, $not
```
diff --git a/content/docs/deployment/vercel.mdx b/content/docs/deployment/vercel.mdx
index 3e8cdfd78c..7145b59613 100644
--- a/content/docs/deployment/vercel.mdx
+++ b/content/docs/deployment/vercel.mdx
@@ -171,7 +171,7 @@ storage for artifacts.
- [ ] `api/_kernel.ts` boots the kernel with the correct driver
- [ ] `vercel.json` sets `VITE_USE_MOCK_SERVER=false` and `VITE_SERVER_URL=` (empty)
- [ ] Rewrite rule routes `/api/*` to `/api` and excludes `/api/` from SPA rewrite
-- [ ] `DATABASE_URL` is configured in Vercel environment variables (for production drivers)
+- [ ] `OS_DATABASE_URL` is configured in Vercel environment variables (for production drivers)
- [ ] CORS is configured if frontend and API are on different origins
---
diff --git a/content/docs/getting-started/cli.mdx b/content/docs/getting-started/cli.mdx
index 01129678fc..3e96df2ae8 100644
--- a/content/docs/getting-started/cli.mdx
+++ b/content/docs/getting-started/cli.mdx
@@ -13,7 +13,7 @@ Command Line Interface for building metadata-driven applications with the Object
pnpm add -D @objectstack/cli
```
-The CLI is available as `objectstack` or the shorter alias `os`.
+The CLI is available as `objectstack` or the shorter alias `os`. Installed as a dev dependency, the bins are project-local — invoke them as `npx os …` / `pnpm exec os …` or via your package scripts.
## Your First App in 2 Minutes
@@ -67,12 +67,6 @@ os compile # Build production artifact → dist/objectstack.json
| `os dev [package]` | | Start development mode with hot reload |
| `os serve [config]` | | Start the ObjectStack server with plugin auto-detection |
-### Production
-
-| Command | Description |
-|---------|-------------|
-| `os start` | Serve a pre-compiled `objectstack.json` artifact (no `objectstack.config.ts` required) |
-
#### `os init`
Scaffolds a new ObjectStack project with configuration, TypeScript setup, and initial metadata files.
@@ -269,6 +263,7 @@ os start
| `--environment-id ` | `OS_ENVIRONMENT_ID` | Environment identifier (default `env_local`) |
| `-p, --port ` | `PORT` / `OS_PORT` | Listen port (default `3000`). **Production fails loudly if the port is busy** — see note below. |
| `--ui` / `--no-ui` | — | Mount the Console portal at `/_console/`. Enabled by default (so you can install marketplace apps); pass `--no-ui` to disable it. |
+| `-v, --verbose` | — | Verbose output |
> **Port conflicts: production never auto-shifts.** Unlike `os dev` (which
> hops to the next free port for local convenience), `os start` exits with an
@@ -276,7 +271,6 @@ os start
> your reverse-proxy upstream, `OS_AUTH_URL` callbacks, and `OS_TRUSTED_ORIGINS`
> (CORS). **Pin the port explicitly** (`PORT=8080 os start`) and keep
> `OS_AUTH_URL` / `OS_TRUSTED_ORIGINS` in sync when you change it.
-| `-v, --verbose` | — | Verbose output |
**Resolution priority (artifact):** `--artifact` > `OS_ARTIFACT_PATH` > `/dist/objectstack.json`.
**Resolution priority (database):** `--database` > `OS_DATABASE_URL` > `DATABASE_URL` (legacy) > `file:/data/objectstack.db`.
diff --git a/content/docs/getting-started/common-patterns.mdx b/content/docs/getting-started/common-patterns.mdx
index cb5b139421..5780732a93 100644
--- a/content/docs/getting-started/common-patterns.mdx
+++ b/content/docs/getting-started/common-patterns.mdx
@@ -406,41 +406,35 @@ Restrict field visibility and editability based on user profiles.
name: { label: 'Name', type: 'text', required: true },
email: { label: 'Email', type: 'email', required: true },
department: { label: 'Department', type: 'lookup', reference: 'department' },
- // Sensitive fields with encryption
salary: { label: 'Salary', type: 'currency',
hidden: true, // Hidden from default views
- encryptionConfig: {
- enabled: true,
- algorithm: 'aes-256-gcm',
- scope: 'field',
- keyManagement: {
- provider: 'local',
- rotationPolicy: { enabled: true, frequencyDays: 90 }
- }
- }
- },
- ssn: { label: 'SSN', type: 'text',
- hidden: true,
- maskingRule: {
- field: 'ssn',
- strategy: 'partial',
- pattern: '\\d{4}$'
- },
- encryptionConfig: {
- enabled: true,
- algorithm: 'aes-256-gcm',
- scope: 'field',
- keyManagement: {
- provider: 'local',
- rotationPolicy: { enabled: true, frequencyDays: 90 }
- }
- }
},
+ // `secret` values are stored encrypted and never returned in plain reads
+ api_token: { label: 'API Token', type: 'secret' },
}
}]
}
```
+Who can actually read or edit a field is governed by permission sets — grant per-object CRUD, then narrow individual fields:
+
+```typescript
+import { definePermissionSet } from '@objectstack/spec';
+
+export const HrRepPermission = definePermissionSet({
+ name: 'hr_rep',
+ label: 'HR Rep',
+ isProfile: true,
+ objects: {
+ employee: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: false },
+ },
+ fields: {
+ //
-### Launch the Studio
+### Launch the Console
```bash
os dev --ui
@@ -144,7 +146,7 @@ Follow these guides in order for the best experience:
## Project Structure
-A typical ObjectStack application (generated by `os init`):
+A typical ObjectStack application layout (`os init` scaffolds only the minimal core of this — see [Example Apps](/docs/getting-started/examples)):
```
my-app/
diff --git a/content/docs/index.mdx b/content/docs/index.mdx
index a9fb7a0fc3..389830bcef 100644
--- a/content/docs/index.mdx
+++ b/content/docs/index.mdx
@@ -5,9 +5,7 @@ description: Technical documentation for ObjectStack.
# ObjectStack Documentation
-ObjectStack is an AI-native business backend protocol for structured, auditable business applications. You author your application — data model, logic, permissions, and UI — as typed metadata in TypeScript (`defineStack()`), compile it to a self-contained artifact (`objectstack.json`), and the runtime derives everything else: the database schema across four interchangeable drivers, a generated REST + realtime API, permission-checked automation, and server-driven UI. AI agents act through the same typed, permission-aware surface — never through raw SQL or scraped UI.
-
-One protocol definition, 15 namespaces, 49 field types, one artifact — every engine (SQL, React, MCP) is a consumer of the same source of truth.
+ObjectStack is an AI-native business backend protocol for structured, auditable business applications. You author your application — data model, logic, permissions, and UI — as typed metadata in TypeScript (`defineStack()`), compile it to a self-contained artifact (`objectstack.json`), and the runtime derives everything else: the database schema on interchangeable drivers, a generated REST + realtime API, permission-checked automation, and server-driven UI. AI agents act through the same typed, permission-aware surface — never through raw SQL or scraped UI. Every engine (SQL, React, MCP) is a consumer of the same source of truth.
## Start here
diff --git a/content/docs/kernel/contracts/cache-service.mdx b/content/docs/kernel/contracts/cache-service.mdx
index a28c28fc36..50fe0aa532 100644
--- a/content/docs/kernel/contracts/cache-service.mdx
+++ b/content/docs/kernel/contracts/cache-service.mdx
@@ -147,8 +147,10 @@ async function updateObjectDefinition(
name: string,
changes: Partial
): Promise {
- // Update source of truth
- const updated = await metadataService.updateObject(name, changes);
+ // Update source of truth — register() saves the full definition
+ const current = await metadataService.getObject(name);
+ const updated = { ...(current as ObjectDefinition), ...changes };
+ await metadataService.register('object', name, updated);
// Update cache immediately
await cacheService.set(`meta:object:${name}`, updated, 3600);
@@ -160,11 +162,12 @@ async function updateObjectDefinition(
### Cache Invalidation on Events
```typescript
-metadataService.onChange((event) => {
+// watch() is optional on IMetadataService — check before subscribing
+const handle = metadataService.watch?.('object', (event) => {
// Invalidate the affected key when metadata changes.
// The contract deletes individual keys — track related keys
// (e.g. cached queries) yourself to invalidate them.
- cacheService.delete(`meta:object:${event.objectName}`);
+ cacheService.delete(`meta:object:${event.name}`);
});
```
diff --git a/content/docs/kernel/contracts/index.mdx b/content/docs/kernel/contracts/index.mdx
index bc0c675d5e..1dd213da85 100644
--- a/content/docs/kernel/contracts/index.mdx
+++ b/content/docs/kernel/contracts/index.mdx
@@ -104,7 +104,7 @@ export interface IDataEngine {
```
-**Convention:** Contract interfaces are prefixed with `I` (e.g., `IDataEngine`). Services are registered and resolved by **string name** through the kernel service registry (e.g., `ctx.registerService('dataEngine', impl)`).
+**Convention:** Contract interfaces are prefixed with `I` (e.g., `IDataEngine`). Services are registered and resolved by **string name** through the kernel service registry (e.g., `ctx.registerService('data', impl)` — see the `CoreServiceName` enum for the standard names).
---
@@ -125,7 +125,7 @@ export const myPlugin: Plugin = {
// Init phase: register the services this plugin provides
init(ctx) {
- ctx.registerService('dataEngine', new MyDataEngine(ctx));
+ ctx.registerService('data', new MyDataEngine(ctx));
// Consume a service another plugin registered
const cache = ctx.getService('cache');
@@ -157,6 +157,6 @@ flowchart LR
**See also:**
-- [Plugin Development Guide](/guides/plugin-development) for implementing contracts
-- [Kernel Services Guide](/guides/kernel-services) for runtime architecture
+- [Plugin Development](/docs/plugins/development) for implementing contracts
+- [Service Registry](/docs/kernel/services) for runtime architecture
diff --git a/content/docs/kernel/contracts/metadata-service.mdx b/content/docs/kernel/contracts/metadata-service.mdx
index 1b8b213d2d..40b03be06e 100644
--- a/content/docs/kernel/contracts/metadata-service.mdx
+++ b/content/docs/kernel/contracts/metadata-service.mdx
@@ -13,7 +13,7 @@ The Metadata Service manages all object and field definitions at runtime. It ser
-For the **data path** behind this contract — Repository → Change Log → Cache → Registry, plus HMR semantics — see [Metadata Lifecycle & HMR](/concepts/metadata-lifecycle).
+For the **data path** behind this contract — Repository → Change Log → Cache → Registry, plus HMR semantics — see [Metadata Lifecycle & HMR](/docs/concepts/metadata-lifecycle).
---
@@ -261,7 +261,7 @@ console.log(result.failed); // failed
## UI Metadata (Views & Dashboards)
-UI metadata types (`view`, `dashboard`, `page`, `app`, `theme`) are first-class citizens in the Metadata Service. The previously separate `IUIService` was removed in 11 — use the Metadata Service for views/dashboards.
+UI metadata types (`view`, `dashboard`, `page`, `app`, `theme`) are first-class citizens in the Metadata Service. The previously separate `IUIService` was removed in v11 — use the Metadata Service for views/dashboards.
### Reading UI Metadata
diff --git a/content/docs/kernel/events.mdx b/content/docs/kernel/events.mdx
index ef2313ad78..d9844512c0 100644
--- a/content/docs/kernel/events.mdx
+++ b/content/docs/kernel/events.mdx
@@ -81,15 +81,15 @@ Every data hook is a single-argument handler `(ctx: HookContext) => void | Promi
```typescript
// Enrich a record before it is created
engine.registerHook('beforeInsert', async (ctx) => {
- if (ctx.object === 'order' && ctx.input.doc.amount > 10000) {
+ if (ctx.object === 'order' && ctx.input.amount > 10000) {
ctx.logger?.warn?.('Large order detected!');
- ctx.input.doc.requires_approval = true;
+ ctx.input.requires_approval = true;
}
}, { object: 'order' });
// React after a record is created
engine.registerHook('afterInsert', async (ctx) => {
- await notifyWebhook(ctx.object, ctx.input.doc.id);
+ await notifyWebhook(ctx.object, ctx.input.id);
}, { object: 'order' });
```
@@ -102,7 +102,7 @@ What happens when a data hook throws is governed by the hook's **`onError`** pol
```typescript
engine.registerHook('beforeInsert', async (ctx) => {
- if (ctx.object === 'invoice' && !ctx.input.doc.customer_id) {
+ if (ctx.object === 'invoice' && !ctx.input.customer_id) {
throw new Error('Invoice must have a customer'); // Aborts the insert (default onError)
}
});
diff --git a/content/docs/kernel/index.mdx b/content/docs/kernel/index.mdx
index 948fd02cfb..1e74f7de56 100644
--- a/content/docs/kernel/index.mdx
+++ b/content/docs/kernel/index.mdx
@@ -5,7 +5,7 @@ description: The ObjectKernel runtime — plugin host, event bus, service regist
# Kernel & Services
-The kernel is ObjectStack's runtime: it loads your metadata artifact, hosts plugins, wires up services, and enforces the lifecycle that keeps the system stable. This module documents the **ObjectOS layer** in practice — the [System Protocol](/docs/protocol/objectos) is its normative spec. If you write hooks or plugins, this is where the APIs you call and the contracts they implement are documented.
+The kernel is ObjectStack's runtime: it loads your metadata artifact, hosts plugins, wires up services, and enforces the lifecycle that keeps the system stable. This module documents the **ObjectOS layer** in practice — the [System Protocol](/docs/protocol/objectos) is its normative spec. If you write hooks or plugins, this is where the capability surface you call — `ctx.api`, `ctx.getService(...)`, and the `services.*` contract they implement — is documented.
## How it fits together
@@ -23,7 +23,7 @@ The kernel is ObjectStack's runtime: it loads your metadata artifact, hosts plug
| [`services.storage`](/docs/kernel/runtime-services/storage-service) | stable | File storage |
| [`services.audit`](/docs/kernel/runtime-services/audit-service) | experimental | Append-only audit sink (query history via `services.data`) |
-- **Service contracts** (28 interface files in `packages/spec/src/contracts/` — `IDataEngine`, `IAuthService`, `ICacheService`, `IMetadataService`, `IStorageService`, …) define what any implementation must provide, so services are swappable. Fifteen runtime service packages ship in this repo (cache, cluster, datasource, i18n, job, knowledge, messaging, realtime, …).
+- **Service contracts** (`IDataEngine`, `IAuthService`, `ICacheService`, `IMetadataService`, `IStorageService`, … in `packages/spec/src/contracts/`) define what any implementation must provide, so services are swappable — the repo ships runtime service packages for cache, cluster, datasource, i18n, jobs, knowledge, messaging, realtime, and more.
- **Cluster semantics** define how the runtime behaves beyond a single node — including the per-partition locking the webhook outbox relies on.
## What's in this module
diff --git a/content/docs/kernel/runtime-services/examples.mdx b/content/docs/kernel/runtime-services/examples.mdx
index 112e4ce689..d87393952d 100644
--- a/content/docs/kernel/runtime-services/examples.mdx
+++ b/content/docs/kernel/runtime-services/examples.mdx
@@ -9,10 +9,8 @@ description: Practical examples for flow nodes, hooks, and plugin event subscrip
```ts
export async function run(ctx: any) {
- const order = await ctx.services?.data?.findOne('sales_order', {
- where: { id: ctx.input.orderId },
- });
- const lines = await ctx.services?.data?.find('sales_order_line', {
+ const { record: order } = await ctx.services.data.get('sales_order', ctx.input.orderId);
+ const lines = await ctx.services.data.find('sales_order_line', {
where: { sales_order_id: order.id },
orderBy: [{ field: 'line_no', order: 'asc' }],
limit: 200,
@@ -20,7 +18,7 @@ export async function run(ctx: any) {
return {
order,
- lines: lines ?? [],
+ lines: lines.records ?? [],
};
}
```
@@ -53,8 +51,8 @@ export const ExamplePlugin: Plugin = {
ctx.logger.info('Kernel ready, initializing subscriptions');
});
- ctx.hook('service:registered', async (serviceName: string) => {
- ctx.logger.debug('Service registered', { serviceName });
+ ctx.hook('kernel:shutdown', async () => {
+ ctx.logger.info('Shutdown signal received, cleaning up');
});
},
};
diff --git a/content/docs/kernel/runtime-services/index.mdx b/content/docs/kernel/runtime-services/index.mdx
index 4f6135040c..25e06e50e2 100644
--- a/content/docs/kernel/runtime-services/index.mdx
+++ b/content/docs/kernel/runtime-services/index.mdx
@@ -5,6 +5,15 @@ description: Reference entry for runtime `services.*` APIs used by flow nodes, h
# Runtime Service APIs
+
+**Binding note.** These pages document the stable `services.*` contract surface
+(signatures match the client SDK and `packages/spec/src/contracts/`). In this
+repo's runtime, hook bodies reach the equivalent capabilities through `ctx.api`
+(scoped data operations) and `ctx.getService(...)` (any registered service) —
+a literal `services.*` object is not injected into hook contexts by the open
+framework today. Managed runtimes provide the `services.*` binding directly.
+
+
This chapter documents the runtime `services.*` APIs used in hook/action/flow/plugin code:
- `services.data`
diff --git a/content/docs/kernel/services-checklist.mdx b/content/docs/kernel/services-checklist.mdx
index b5cfb12ed9..d735e13e9d 100644
--- a/content/docs/kernel/services-checklist.mdx
+++ b/content/docs/kernel/services-checklist.mdx
@@ -61,9 +61,9 @@ The ObjectStack protocol defines **17 kernel services** registered via the `Core
| 11 | **i18n** | `core` | 3 | ✅ Built-in (in-memory fallback) | `@objectstack/service-i18n` |
| 12 | **file-storage** | `optional` | — | ❌ Plugin Required | TBD plugin |
| 13 | **search** | `optional` | — | ❌ Plugin Required | TBD plugin |
-| 14 | **cache** | `core` | — | ❌ Plugin Required | TBD plugin |
-| 15 | **queue** | `core` | — | ❌ Plugin Required | TBD plugin |
-| 16 | **job** | `core` | — | ❌ Plugin Required | TBD plugin |
+| 14 | **cache** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-cache` |
+| 15 | **queue** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-queue` |
+| 16 | **job** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-job` |
| 17 | **graphql** | `optional` | — | ❌ Plugin Required | TBD plugin |
@@ -260,7 +260,6 @@ The i18n service is a **core built-in service** with automatic in-memory fallbac
| **Production** | `I18nServicePlugin` | File-based `FileI18nAdapter` loads JSON locale files from disk |
| **Built-in Fallback** | Kernel | In-memory Map-backed stub, auto-injected when no plugin provides `i18n` |
| **Development** | `DevPlugin` | In-memory Map-backed stub, supports `loadTranslations()` |
-| **Mock / MSW** | `MswPlugin` | Routes via `HttpDispatcher.dispatch()` catch-all — requires one of the above |
```typescript
// Production (overrides built-in fallback)
@@ -315,15 +314,17 @@ AppPlugin will:
---
-## 12–17. Infrastructure Services ❌ Plugin Required
+## 12–17. Infrastructure Services
+
+`cache`, `queue`, and `job` are `core` services: like `i18n`, the kernel auto-injects an in-memory fallback when no plugin registers them (see `CORE_FALLBACK_FACTORIES` in `packages/core/src/fallbacks/`). The `optional` services (`file-storage`, `search`, `graphql`) stay disabled until a plugin provides them.
| Service | Description |
|:--------|:------------|
| **file-storage** | Unified upload/download/delete. Drivers: local FS, S3, MinIO. |
| **search** | Full-text search. Drivers: in-memory, Elasticsearch, Meilisearch. |
-| **cache** | General-purpose cache. Drivers: in-memory LRU, Redis. |
-| **queue** | Message queue. Drivers: in-memory, BullMQ / Redis Streams. |
-| **job** | Scheduled task execution. Cron-based, concurrency control. |
+| **cache** | General-purpose cache. In-memory fallback; Redis via `@objectstack/service-cache`. |
+| **queue** | Message queue. In-memory fallback; BullMQ / Redis via `@objectstack/service-queue`. |
+| **job** | Scheduled task execution. In-memory fallback; cron-based, concurrency control. |
| **graphql** | Auto-generate GraphQL schema from Object metadata. |
---
@@ -368,24 +369,26 @@ AppPlugin will:
```typescript
import type { Plugin } from '@objectstack/core';
+let authService: AuthServiceImpl;
+
export const authPlugin: Plugin = {
name: 'plugin-auth',
- provides: ['auth'], // CoreServiceName value
-
+
async init(ctx) {
+ // Register the 'auth' CoreServiceName value
const engine = ctx.getService('data');
- const authService = new AuthServiceImpl(engine);
+ authService = new AuthServiceImpl(engine);
ctx.registerService('auth', authService);
},
-
+
async start(ctx) {
const auth = ctx.getService('auth');
await auth.onReady();
},
-
- async stop(ctx) {
- const auth = ctx.getService('auth');
- await auth.shutdown();
+
+ // destroy() takes no arguments — capture what you need in module scope
+ async destroy() {
+ await authService.shutdown();
},
};
```
@@ -401,17 +404,17 @@ When a plugin registers a service, the discovery endpoint automatically updates:
diff --git a/content/docs/permissions/access-recipes.mdx b/content/docs/permissions/access-recipes.mdx
index 1f88eb83e8..a78bcf9962 100644
--- a/content/docs/permissions/access-recipes.mdx
+++ b/content/docs/permissions/access-recipes.mdx
@@ -37,7 +37,7 @@ definePermissionSet({
});
```
-- **"My own"** vs **"my team's"** vs **"the org's"** is the RLS *depth* axis (`readScope` / `writeScope`: `own | own_and_reports | unit | unit_and_below | org`, ADR-0057). A manager set uses `unit`; a rep uses `own`.
+- **"My own"** vs **"my team's"** vs **"the org's"** is the RLS *depth* axis (`readScope` / `writeScope`: `own | own_and_reports | unit | unit_and_below | org`, ADR-0057). A manager set uses `unit`; a rep uses `own`. The hierarchy-relative scopes (`own_and_reports` / `unit` / `unit_and_below`) need the enterprise hierarchy resolver and **fail closed to `own`** without it — see [Access depth](/docs/permissions/profiles#access-depth--readscope--writescope-adr-0057-d1).
- Grants combine **most-permissively** across a user's sets; the tenant-isolation policy `AND`s on top; the superuser bypass (`viewAllRecords` / `modifyAllRecords`) short-circuits RLS where the object's posture allows it (ADR-0066).
- For genuinely sensitive objects, set `access: { default: 'private' }` so they are **not** covered by blanket wildcard grants — access needs an explicit grant.
@@ -58,7 +58,7 @@ Two separate questions:
## Why
-Keeping capability, assignment and requirement decoupled means resources stay stable (`requiredPermissions: ['export_data']`) while admins re-assign who holds `export_data` at runtime, with no code change. The fixed precedence (AND-gates → most-permissive grant union → RLS → explicit deny) is specified in ADR-0066, so combinations are predictable rather than ad-hoc.
+Keeping capability, assignment and requirement decoupled means resources stay stable (`requiredPermissions: ['export_data']`) while admins re-assign who holds `export_data` at runtime, with no code change. The fixed precedence (AND-gates → most-permissive grant union → RLS; an explicit deny/muting layer is reserved but [not yet implemented](/docs/permissions/authorization#combination-semantics-the-fixed-order)) is specified in ADR-0066, so combinations are predictable rather than ad-hoc.
## Runnable example
diff --git a/content/docs/permissions/authorization.mdx b/content/docs/permissions/authorization.mdx
index 298baea874..dc5654752e 100644
--- a/content/docs/permissions/authorization.mdx
+++ b/content/docs/permissions/authorization.mdx
@@ -45,8 +45,8 @@ site — the file you read when behavior surprises you.
| 1 | **Anonymous deny** | No identity → HTTP 401 on `/data/*`. **Default-on** (ADR-0056 D2): public data serving requires an explicit `api.requireAuth: false` opt-out, which logs a boot warning. Control plane (`/auth`, `/health`, `/discovery`) is exempt; share-links validate their token then read as SYSTEM. | `packages/rest/src/rest-server.ts` `enforceAuth` (default in `packages/spec/src/api/rest-server.zod.ts`) | fail-closed |
| 2 | **Public-form grant** | An anonymous form submission carries a declaration-derived `publicFormGrant` authorizing ONLY create + read-back on the form's declared target object — never anything else (ADR-0056 Option A). No `guest_portal` profile needed. | `packages/plugins/plugin-security/src/security-plugin.ts` (ObjectQL middleware) | scope-limited allow |
| 3 | **Object CRUD** | `allowRead/Create/Edit/Delete` (+ the destructive lifecycle class `allowTransfer/Restore/Purge`, gated ahead of the M2 operations — #1883) resolved across the caller's permission sets. | `packages/plugins/plugin-security/src/permission-evaluator.ts` `checkObjectPermission` | fail-closed 403 |
-| 4 | **OWD / sharing** | Org-wide default (`private` / `public_read` / `public_read_write` / `controlled_by_parent`), manual record shares, criteria/owner sharing rules, business-unit hierarchy widening (ADR-0057 D5: hierarchy lives on `sys_business_unit`, not roles). | `packages/plugins/plugin-sharing/src/sharing-service.ts` + `sharing-rule-service.ts` | owner-only baseline |
-| 5 | **Row-level security** | CEL predicates (`using` read filter, `check` write post-image) compiled into the query. An applicable-but-uncompilable policy **denies** — it is never silently dropped. Tenant isolation is a wildcard RLS rule AND-ed on top. | `packages/plugins/plugin-security/src/rls-compiler.ts` + `security-plugin.ts` | fail-closed |
+| 4 | **OWD / sharing** | Org-wide default (`private` / `public_read` / `public_read_write` / `controlled_by_parent`), manual record shares, criteria sharing rules (owner-type rules are declared but seed-skipped — [not enforced](/docs/permissions/sharing-rules#owner-based-sharing-rules)), business-unit hierarchy widening (ADR-0057 D5: scope-depth hierarchy lives on `sys_business_unit`, not roles). | `packages/plugins/plugin-sharing/src/sharing-service.ts` + `sharing-rule-service.ts` | owner-only baseline |
+| 5 | **Row-level security** | CEL predicates (`using` read filter, `check` write post-image) compiled into the query. If **no** applicable policy compiles, the result is a deny-all sentinel (fail-closed); an uncompilable policy alongside compilable ones is excluded from the OR-union **with a logged warning** — exclusion can only narrow access, never widen it. Tenant isolation is a wildcard RLS rule AND-ed on top. | `packages/plugins/plugin-security/src/rls-compiler.ts` + `security-plugin.ts` | fail-closed |
| 6 | **Field-level security** | Read mask (strip non-readable fields) + write deny per `fields` rules. | `packages/plugins/plugin-security/src/field-masker.ts` | see posture note below |
Two orthogonal identity-layer gates run before all of this: the ADR-0069
@@ -169,8 +169,10 @@ The complete, prioritized gap map lives in issue **#2561** (the production
- **Deny/muting layer** (ADR-0066 ⑦⑧) — union-only grants can't take access
away; needed for large-org governance and packaged-set adjustment.
-- **Capability registry** (ADR-0066 D1) — `systemPermissions` strings become
- `sys_permission` records, with an authoring lint for unregistered
+- **Capability registry** (ADR-0066 D1) — **landed**: capabilities are seeded
+ as first-class `sys_capability` records
+ (`packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts`);
+ the remaining gap is the authoring lint for unregistered `systemPermissions`
references (ADR-0066 ⑨).
- **Per-operation `requiredPermissions`** (ADR-0066 ⑤) — read-open /
write-gated objects.
diff --git a/content/docs/permissions/field-level-security.mdx b/content/docs/permissions/field-level-security.mdx
index 44a422b002..d87e597a7a 100644
--- a/content/docs/permissions/field-level-security.mdx
+++ b/content/docs/permissions/field-level-security.mdx
@@ -1,6 +1,6 @@
---
title: "Field-Level Security"
-description: "Control visibility and editability of specific fields — readable/editable rules, hidden vs. read-only semantics, and fail-closed server-side enforcement."
+description: "Control visibility and editability of specific fields — readable/editable rules, hidden vs. read-only semantics, and server-side enforcement of declared rules."
---
# Field-Level Security
@@ -36,7 +36,7 @@ fields: {
{ readable: true, editable: true }
```
-## Server-side enforcement (fail-closed)
+## Server-side enforcement of declared rules
The client-side ObjectForm / inline grid hides non-editable fields from
the UI — but that is a **UX layer only**. The SecurityPlugin middleware
@@ -73,9 +73,19 @@ field exists. Throwing makes the boundary observable in both directions
— legitimate UIs get an actionable error to fix; probing clients learn
nothing they could not already infer.
-**Allow-list semantics.** Fields without an explicit rule pass through
-untouched. Permission sets only constrain fields they explicitly
-enumerate.
+**Default-visible (block-list) semantics.** Fields without an explicit
+rule pass through untouched — readable *and* writable. Permission sets
+only constrain fields they explicitly enumerate, and field grants union
+most-permissively across a user's sets (one set's `readable: true`
+out-votes another set's `false`). Until a subtractive muting layer lands
+(ADR-0066 ⑧), a `{ readable: false }` rule masks the field only as long
+as **no other set the user holds** declares it `readable: true` — so
+protect sensitive fields by granting them only in the sets that need
+them, and treat a sensitive field on a broadly-granted object as a
+review smell — see the
+[FLS posture warning](/docs/permissions/authorization#combination-semantics-the-fixed-order).
+Declared rules themselves are enforced fail-closed: a masked field is
+stripped on read and a write to a non-editable field throws.
**Bulk inserts.** Arrays are checked row-by-row; a single offending
field in any row rejects the whole batch atomically.
diff --git a/content/docs/permissions/index.mdx b/content/docs/permissions/index.mdx
index 4691c2efa5..f75f9121bd 100644
--- a/content/docs/permissions/index.mdx
+++ b/content/docs/permissions/index.mdx
@@ -13,7 +13,10 @@ The model has five layers, each with schema **and** runtime support: **profiles*
(one per user) and additive **permission sets** for object/field rights, a **role
hierarchy** for reporting structure, **sharing rules + Organization-Wide Defaults**
for record visibility, **row-level security** (compiled into every query), and
-**field-level security** (masked server-side, fail-closed). Object access scopes
+**field-level security** (declared rules masked and enforced server-side; fields
+without a declared rule stay visible by default — see the
+[FLS posture note](/docs/permissions/authorization#combination-semantics-the-fixed-order)).
+Object access scopes
go from `own` through `own_and_reports`, `unit`, `unit_and_below`, to `org`.
Access rules are metadata like everything else — this is a real sharing rule from
@@ -36,7 +39,7 @@ export const HighValueOpportunitySharingRule = defineSharingRule({
Because AI agents act through the same permission-aware surface, these rules bound
agent access exactly as they bound users ([Actions as Tools](/docs/ai/actions-as-tools)).
-> **Implementation status — Phase-1 RBAC is live.** REST → ObjectQL now propagates a populated `ExecutionContext` (userId, tenantId, roles, permissions) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks actually fire on every authenticated request. The default `member_default` permission set ships a wildcard RLS rule `organization_id == current_user.organization_id` plus explicit per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the two global tables that lack an `organization_id` column. RLS expressions, the physical column, and `RLSUserContext.organization_id` all use the same canonical name — there is no `tenantField` rewrite indirection (schemas with a different physical tenant column should fork the defaults). The legacy `objectql.registerTenantMiddleware` has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics also reuses the same read scope: `@objectstack/service-analytics` auto-bridges to `security.getReadFilter(object, context)` when the security service is registered, so dataset-bound dashboards/reports do not bypass RLS. End-to-end verified on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic is denied by default** (the ADR-0056 D2 default-deny flip landed: `requireAuth` defaults to `true`); an explicit `requireAuth: false` opt-out logs a boot-time warning, and public forms self-authorize via a declaration-derived `publicFormGrant` (ADR-0056 Option A — see [Public Forms](/docs/ui/forms)). Organization-Wide Defaults (`private` / `public_read` / `public_read_write` / `controlled_by_parent`) and Sharing Rules (owner + criteria, with `role_and_subordinates` hierarchy widening) are live and dogfood-proven (ADR-0056); the Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See `CHANGELOG.md` and [Implementation Status](/docs/releases/implementation-status) for the latest matrix.
+> **Implementation status — Phase-1 RBAC is live.** REST → ObjectQL now propagates a populated `ExecutionContext` (userId, tenantId, roles, permissions) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks actually fire on every authenticated request. The default `member_default` permission set ships a wildcard RLS rule `organization_id == current_user.organization_id` plus explicit per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the two global tables that lack an `organization_id` column. RLS expressions, the physical column, and `RLSUserContext.organization_id` all use the same canonical name — there is no `tenantField` rewrite indirection (schemas with a different physical tenant column should fork the defaults). The legacy `objectql.registerTenantMiddleware` has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics also reuses the same read scope: `@objectstack/service-analytics` auto-bridges to `security.getReadFilter(object, context)` when the security service is registered, so dataset-bound dashboards/reports do not bypass RLS. End-to-end verified on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic is denied by default** (the ADR-0056 D2 default-deny flip landed: `requireAuth` defaults to `true`); an explicit `requireAuth: false` opt-out logs a boot-time warning, and public forms self-authorize via a declaration-derived `publicFormGrant` (ADR-0056 Option A — see [Public Forms](/docs/ui/forms)). Organization-Wide Defaults (`private` / `public_read` / `public_read_write` / `controlled_by_parent`) and criteria Sharing Rules (with `role_and_subordinates` hierarchy widening) are live and dogfood-proven (ADR-0056); **owner-type sharing rules and `group`/`guest` recipients are declared but not enforced** — the seeder skips them with a warning ([Sharing Rules](/docs/permissions/sharing-rules#owner-based-sharing-rules)); the Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See `CHANGELOG.md` and [Implementation Status](/docs/releases/implementation-status) for the latest matrix.
## What's in this module
@@ -75,7 +78,7 @@ ObjectStack implements a multi-layered security model:
├─────────────────────────────────────┤
│ Record-Level Security │ ← Which records can be accessed?
│ - Organization-Wide Defaults │
-│ - Role Hierarchy │
+│ - Scope-Depth Grants (readScope) │
│ - Sharing Rules │
│ - Manual Sharing │
├─────────────────────────────────────┤
diff --git a/content/docs/permissions/permission-metadata.mdx b/content/docs/permissions/permission-metadata.mdx
index 674661ce11..41874559c1 100644
--- a/content/docs/permissions/permission-metadata.mdx
+++ b/content/docs/permissions/permission-metadata.mdx
@@ -121,7 +121,7 @@ fields: {
| `readable` | User can see the field value |
| `editable` | User can modify the field value |
-**Note:** `editable: true` requires `readable: true`. A field that is not readable is completely hidden from the user.
+**Note:** `readable` defaults to `true` and `editable` to `false`. The schema does not force `editable: true` to be paired with `readable: true`, but a field with `readable: false` is stripped from every read regardless of `editable` — always declare them together, and treat `{ readable: false }` as completely hiding the field.
## Tab Permissions
diff --git a/content/docs/permissions/permissions-matrix.mdx b/content/docs/permissions/permissions-matrix.mdx
index c61684373b..c1ba4103ce 100644
--- a/content/docs/permissions/permissions-matrix.mdx
+++ b/content/docs/permissions/permissions-matrix.mdx
@@ -8,7 +8,7 @@ description: Visual reference for ObjectStack's security model — permission ty
This page provides a comprehensive visual reference for ObjectStack's security model — from object-level permissions to field-level security, sharing rules, and role hierarchies.
-**Security Model:** ObjectStack uses a layered security model inspired by Salesforce. Permissions are evaluated in order: Object Permission → Organization-Wide Defaults → Record Ownership → Role Hierarchy → Sharing Rules → Field-Level Security.
+**Security Model:** ObjectStack uses a layered security model inspired by Salesforce. Permissions are evaluated in order: Object Permission → Organization-Wide Defaults → Record Ownership / Scope Depth → Sharing (rules + manual shares) → Row-Level Security → Field-Level Security. Unlike Salesforce, the role hierarchy is **not** an automatic layer — it widens access only where a sharing rule's `role_and_subordinates` recipient or a scope-depth grant explicitly invokes it.
---
@@ -135,9 +135,13 @@ Sharing rules extend access beyond ownership and role hierarchy. The declarative
| Mechanism | `type` | Description | Example |
|:---|:---|:---|:---|
-| **Owner-Based** | `owner` | Share records owned by a specific group/role with another recipient | All accounts owned by "West Region" team are shared with "Sales Directors" |
+| **Owner-Based** | `owner` | Share records owned by a specific group/role with another recipient — **[experimental — not enforced]**: owner rules are skipped at seed time and materialize no shares yet | All accounts owned by "West Region" team are shared with "Sales Directors" |
| **Criteria-Based** | `criteria` | Share records matching a CEL predicate over field values | All opportunities where `record.amount > 100000` are shared with "VP Sales" |
+
+**Enforcement status:** only **criteria** rules are enforced today. Declared `owner`-type rules, and rules with `group` / `guest` recipients, are skipped at seed time with a warning (ADR-0049 — never silently advertised as live). See [Sharing Rules](/docs/permissions/sharing-rules#owner-based-sharing-rules).
+
+
**Beyond declarative rules:** Two other sharing mechanisms exist but are **not** `SharingRule` types. **Manual sharing** is a runtime grant — `sys_record_share` rows created with `source: 'manual'` (see `packages/plugins/plugin-sharing/src/sharing-service.ts`). **Territories** are a separate matrix model (`TerritorySchema` in `packages/spec/src/security/territory.zod.ts`) with their own account/opportunity/case access levels, parallel to the role hierarchy. Owner/criteria rules are re-evaluated on insert/update via internal sharing rule hooks.
@@ -180,7 +184,7 @@ OWD sets the baseline access level for each object across the entire organizatio
| **Public Read/Write** | `read_write` | All users | All users | Low-sensitivity data (e.g., tasks, wiki pages) |
| **Public Read Only** | `read` | All users | Owner + shared | Moderate sensitivity (e.g., accounts, contacts) |
| **Private** | `private` | Owner + shared | Owner + shared | High sensitivity (e.g., opportunities, HR records) |
-| **Full Access** | `full` | All users | All users (incl. transfer/share) | Fully collaborative data |
+| **Full Access** | `full` | All users | All users | Legacy alias — enforced identically to `public_read_write` |
Since ADR-0056 (D1), `object.sharingModel` accepts the **canonical OWD vocabulary** — `private`, `public_read`, `public_read_write`, and `controlled_by_parent` — *in addition to* the legacy `read` / `read_write` / `full` spellings shown above (both are valid; the canonical values are preferred for new objects). `controlled_by_parent` is for child objects in a master-detail relationship, where access is **derived from the parent record** (a line is visible/editable only if its master is). These models are enforced by `plugin-sharing` + `plugin-security` and dogfood-proven over the real HTTP stack.
@@ -209,7 +213,10 @@ defineStack({
## 6. Role Hierarchy
-The role hierarchy determines which users can see records owned by users below them.
+The role hierarchy is a reporting structure that sharing rules can reference to
+widen record access. It grants **nothing automatically**: a user's position
+above another role does not by itself expose that role's records (ADR-0057 D5 —
+the visibility hierarchy does not live on roles).
```mermaid
graph TD
@@ -258,11 +265,19 @@ graph TD
style ENG_MGR fill:#0284c7,color:#fff
```
-**How it works:** Each role can see all records owned by users in roles below it. For example:
-- **CEO** can see all records in the organization
-- **VP Sales** can see records owned by Directors and Sales Reps
-- **Director - East** can see records owned by East Sales Reps only
-- **Sales Rep** can only see their own records (plus sharing rules)
+**How it is consumed:** the `parent` graph powers the `role_and_subordinates`
+sharing-rule recipient — one rule can grant access to a role *and every role
+beneath it* (see [Recipient types](/docs/permissions/sharing-rules#recipient-types)).
+Manager-over-subordinate record visibility is built explicitly, per grant:
+
+- **VP Sales sees Directors' and Reps' records** — author a criteria sharing
+ rule with `sharedWith: { type: 'role', value: 'vp_sales' }`, or grant the VP's
+ permission set `readScope: 'unit_and_below'` (scope depth, ADR-0057).
+- **Cascade one rule down a branch** — share with
+ `{ type: 'role_and_subordinates', value: 'director_east' }` to reach the
+ director and everyone below that role.
+- **Sales Rep** sees only their own records (plus whatever sharing rules and
+ manual shares grant) — this part needs no configuration under a `private` OWD.
### Configuration Example
@@ -293,7 +308,7 @@ flowchart TD
C -->|Public Read/Write| ALLOW[✅ Access Granted]
C -->|No| D{Is owner?}
D -->|Yes| ALLOW
- D -->|No| E{Role hierarchy grants?}
+ D -->|No| E{Scope-depth grant? readScope}
E -->|Yes| ALLOW
E -->|No| F{Sharing rule matches?}
F -->|Yes| ALLOW
@@ -311,11 +326,11 @@ flowchart TD
| 1 | **Object Permission** | Does the profile grant any access to this object? |
| 2 | **OWD** | Is the object public? If so, grant access immediately |
| 3 | **Record Ownership** | Does the user own this record? |
-| 4 | **Role Hierarchy** | Does the user's role sit above the owner's role? |
-| 5 | **Sharing Rules** | Do any sharing rules grant access to this record? |
-| 6 | **Manual Sharing** | Was this record explicitly shared with the user? |
+| 4 | **Scope Depth** | Does a `readScope` / `writeScope` grant (`own_and_reports` / `unit` / `unit_and_below` / `org`) widen the owner-match to cover the record's owner? |
+| 5 | **Sharing Rules** | Did a criteria sharing rule materialize a share for this user on this record? |
+| 6 | **Manual Sharing** | Was this record explicitly shared with the user (`sys_record_share`, `source: 'manual'`)? |
| 7 | **Field-Level Security** | Which fields is the user allowed to see/edit? |
-**Performance:** Steps 2–6 are compiled into SQL WHERE clauses (RLS) at query time, not evaluated record-by-record. This ensures security checks are efficient even on tables with millions of rows.
+**Performance:** For reads, steps 2–6 are compiled into a query filter (owner-match ∪ materialized shares, AND-ed with RLS) at query time, not evaluated record-by-record. By-id writes are verified with a pre-image check: the target row is re-read through the write-scope filter before the mutation. This keeps security checks efficient even on tables with millions of rows.
diff --git a/content/docs/permissions/roles.mdx b/content/docs/permissions/roles.mdx
index fb79ea1ba1..4218de8a50 100644
--- a/content/docs/permissions/roles.mdx
+++ b/content/docs/permissions/roles.mdx
@@ -1,11 +1,15 @@
---
title: "Role Hierarchy"
-description: "Roles control record-level access through a hierarchy derived from each role's parent link — access is granted upward to managers over their subordinates' records."
+description: "Roles bundle permissions (via role-based permission-set grants) and form a reporting hierarchy derived from each role's parent link — consumed by role_and_subordinates sharing-rule recipients."
---
# Role Hierarchy
-Roles control record-level access through a hierarchy.
+Roles serve two purposes: they are an **assignment target for permission sets**
+(grant a set to a role via a `sys_role_permission_set` row — see
+[Permission Sets](/docs/permissions/permission-sets#assigning-permission-sets)),
+and their `parent` links form a **reporting hierarchy** that sharing rules can
+reference to widen record access.
The hierarchy is derived from the `parent` link on each role — there is no
`RoleHierarchy` container schema. Author roles as individual `*.role.ts`
@@ -43,12 +47,27 @@ export const roles: Role[] = [
Sales Rep Service Agent
```
-**Grant Access UP:** Users see records owned by:
-- Themselves
-- Their subordinates
-- Their subordinates' subordinates (all levels down)
+**There is no implicit "managers see subordinates' records" grant.** Unlike
+Salesforce, the role hierarchy by itself does not widen record visibility
+(ADR-0057 D5: the visibility hierarchy does not live on roles). Upward
+visibility is always an explicit, opt-in grant:
-**Example:** Sales Director sees all records owned by Sales Managers and Sales Reps.
+- **Sharing rules** — a criteria rule whose recipient is
+ `{ type: 'role', value: 'sales_director' }` or
+ `{ type: 'role_and_subordinates', value: 'sales_manager' }`. The
+ `role_and_subordinates` recipient is where the `parent` graph is consumed:
+ it expands to everyone holding the named role *or any role beneath it*.
+ See [Sharing Rules](/docs/permissions/sharing-rules#recipient-types).
+- **Scope-depth grants** — an owner-scoped object grant with
+ `readScope: 'own_and_reports'` (manager chain via `sys_user.manager_id`) or
+ `'unit'` / `'unit_and_below'` (business units). These require the paid
+ hierarchy resolver and **fail closed to `own`** without it — see
+ [Access depth](/docs/permissions/profiles#access-depth--readscope--writescope-adr-0057-d1).
+
+**Example:** to let a Sales Director see records owned by Sales Managers and
+Sales Reps, author a criteria sharing rule with the director role as recipient,
+or grant the director's permission set `readScope: 'unit_and_below'` — neither
+happens automatically.
---
diff --git a/content/docs/permissions/sharing-rules.mdx b/content/docs/permissions/sharing-rules.mdx
index 2db72a9e54..e43a66fa98 100644
--- a/content/docs/permissions/sharing-rules.mdx
+++ b/content/docs/permissions/sharing-rules.mdx
@@ -29,11 +29,19 @@ export const OrganizationDefaults = {
| Level | Description |
|-------|-------------|
-| `private` | Owner only (+ role hierarchy) |
+| `private` | Owner only (+ scope-depth grants and record shares) |
| `public_read` | All users can read |
| `public_read_write` | All users can read and edit |
| `controlled_by_parent` | Controlled by parent object |
+> **The default is open, not private.** An object that declares **no
+> `sharingModel`** — or that has no `owner_id` field for the owner-match to key
+> on — gets **no record-level OWD filter at all** (it behaves as public within
+> the tenant; only tenant-isolation RLS still applies). If records should be
+> owner-scoped, declare `sharingModel: 'private'` explicitly on the object.
+> See `effectiveSharingModel` in
+> `packages/plugins/plugin-sharing/src/sharing-service.ts`.
+
## Criteria-Based Sharing Rules
Share records based on field criteria:
@@ -69,7 +77,7 @@ export const AccountTeamSharingRule = defineSharingRule({
| `type` | Shares with |
|:--|:--|
| `user` | A single user |
-| `group` | All members of a public group |
+| `group` | All members of a public group — **declared but not enforced**: there is no runtime recipient mapping yet, so a rule with a `group` recipient is skipped at seed time with a warning (ADR-0049) |
| `role` | Everyone assigned that role |
| `role_and_subordinates` | Everyone in that role **and every role below it** in the hierarchy (ADR-0056 D6) — configurable per rule, so one rule can cascade down a branch of the org chart |
@@ -82,8 +90,21 @@ The recipient set is expanded by `@objectstack/plugin-sharing` when the rule is
evaluated (`afterInsert` / `afterUpdate`); `role_and_subordinates` walks the
`sys_role.parent` graph (cycle-safe).
+A criteria `condition` must be compilable by the CEL → filter pushdown compiler.
+A condition the compiler cannot lower (functions, cross-object traversal) is
+**skipped and logged — never seeded as a permissive match-all** (ADR-0049), so a
+bad condition under-shares rather than over-shares.
+
## Owner-Based Sharing Rules
+> **[Experimental — not enforced.]** Owner-based (`type: 'owner'`) rules depend
+> on live role membership and have no static `criteria_json` equivalent, so the
+> seeder **skips them with a warning** — they materialize **no** record shares
+> today (see `packages/plugins/plugin-sharing/src/bootstrap-declared-sharing-rules.ts`
+> and the `SharingRuleSchema` notes in `packages/spec/src/security/sharing.zod.ts`).
+> Do not rely on an owner-based rule to deliver access; use a criteria rule or a
+> scope-depth grant until this ships.
+
Share based on record owner characteristics:
```typescript
diff --git a/content/docs/plugins/anatomy.mdx b/content/docs/plugins/anatomy.mdx
index 8e65a5c5a1..805aa5da49 100644
--- a/content/docs/plugins/anatomy.mdx
+++ b/content/docs/plugins/anatomy.mdx
@@ -71,7 +71,7 @@ ObjectStack uses `type` discrimination to optimize runtime behavior, allowing th
* **Use Cases:** Admin Console, Low-code Studio, SPA Dashboard.
* **Behavior:**
* **Passive:** Driven by `plugin-hono-server` (or other HTTP adapters).
- * **Static Assets:** Must verify `staticPath` pointing to a build output (e.g., `dist/`).
+ * **Static Assets:** Must provide `staticPath` pointing to a build output (e.g., `dist/`).
* **Routing:** Automatically mounted to `/{slug}` with SPA fallback support.
* **Assets:** Files are served locally under `/{slug}/assets`.
diff --git a/content/docs/plugins/development.mdx b/content/docs/plugins/development.mdx
index 88caf129c7..373ce6c211 100644
--- a/content/docs/plugins/development.mdx
+++ b/content/docs/plugins/development.mdx
@@ -31,7 +31,7 @@ A plugin is a self-contained module that extends the ObjectStack kernel with:
mkdir objectstack-plugin-hello
cd objectstack-plugin-hello
npm init -y
-npm install @objectstack/spec
+npm install @objectstack/core @objectstack/spec
npm install -D typescript vitest @types/node
```
@@ -62,7 +62,8 @@ A plugin package describes itself with a manifest validated by `ManifestSchema`.
import { ManifestSchema } from '@objectstack/spec/kernel';
export const manifest = ManifestSchema.parse({
- name: 'objectstack-plugin-hello',
+ id: 'com.example.hello', // required — reverse-domain package id
+ name: 'objectstack-plugin-hello', // required — human-readable name
version: '1.0.0',
description: 'A simple example plugin that adds a greeting service.',
type: 'plugin',
@@ -77,7 +78,7 @@ A runtime plugin's behaviour (services, hooks, objects) is declared in code via
## Step 3: Implement the Plugin
-Create `src/index.ts`. A runtime plugin implements the `Plugin` interface: services are registered inside `init(ctx)` via `ctx.registerService`, and lifecycle hooks are wired with `ctx.hook(...)`.
+Create `src/index.ts`. A runtime plugin implements the `Plugin` interface: services are registered inside `init(ctx)` via `ctx.registerService`. Kernel lifecycle hooks (`kernel:ready`, custom events) are wired with `ctx.hook(...)`; record lifecycle hooks (`beforeInsert`, `afterUpdate`, …) are registered on the **data engine** — see [Events & Hooks](/docs/kernel/events) for the two hook systems.
```typescript
import type { Plugin, PluginContext } from '@objectstack/core';
@@ -96,20 +97,25 @@ export function createHelloPlugin(): Plugin {
return {
name: 'hello_world',
version: '1.0.0',
+ // Ensure the data engine plugin initializes before this one
+ dependencies: ['com.objectstack.engine.objectql'],
init(ctx: PluginContext) {
// Register services so other plugins can consume them
ctx.registerService('greeting', greetingService);
- // Hook into record lifecycle
- ctx.hook('data:beforeInsert', (context: { object: string; data: Record }) => {
- if (context.object === 'contact' && context.data.first_name) {
+ // Hook into the record lifecycle via the data engine.
+ // Data hooks receive a single HookContext; mutate the flat hookCtx.input
+ // in before* hooks to change the operation.
+ const engine = ctx.getService('data');
+ engine.registerHook('beforeInsert', async (hookCtx: any) => {
+ if (hookCtx.input.first_name) {
// Auto-generate a greeting field
- context.data.welcome_message = greetingService.greet(
- context.data.first_name as string
+ hookCtx.input.welcome_message = greetingService.greet(
+ hookCtx.input.first_name as string
);
}
- });
+ }, { object: 'contact' }); // the engine only runs this hook for 'contact'
}
};
}
@@ -151,15 +157,22 @@ import { describe, it, expect } from 'vitest';
import type { PluginContext } from '@objectstack/core';
import { createHelloPlugin } from './index';
-// Minimal fake PluginContext that captures registered services and hooks.
+// Minimal fake PluginContext with a stub data engine that captures hooks.
function createFakeContext() {
const services: Record = {};
- const hooks: Record void> = {};
+ const hooks: Record void | Promise> = {};
+ const hookOptions: Record = {};
+ const engine = {
+ registerHook: (event: string, handler: (hookCtx: any) => void, options?: unknown) => {
+ hooks[event] = handler;
+ hookOptions[event] = options;
+ },
+ };
const ctx = {
registerService: (name: string, service: unknown) => { services[name] = service; },
- hook: (name: string, handler: (...args: any[]) => void) => { hooks[name] = handler; },
+ getService: (name: string) => (name === 'data' ? engine : services[name]),
} as unknown as PluginContext;
- return { ctx, services, hooks };
+ return { ctx, services, hooks, hookOptions };
}
describe('HelloPlugin', () => {
@@ -180,20 +193,19 @@ describe('HelloPlugin', () => {
expect(greeting.greet('Alice')).toBe('Hello, Alice! Welcome to ObjectStack.');
});
- it('should add welcome message on contact create', () => {
+ it('should add welcome message on contact create', async () => {
const { ctx, hooks } = createFakeContext();
createHelloPlugin().init(ctx);
- const context = { object: 'contact', data: { first_name: 'Bob' } as Record };
- hooks['data:beforeInsert'](context);
- expect(context.data.welcome_message).toBe('Hello, Bob! Welcome to ObjectStack.');
+ const hookCtx = { object: 'contact', input: { doc: { first_name: 'Bob' } as Record } };
+ await hooks['beforeInsert'](hookCtx);
+ expect(hookCtx.input.welcome_message).toBe('Hello, Bob! Welcome to ObjectStack.');
});
- it('should not modify non-contact objects', () => {
- const { ctx, hooks } = createFakeContext();
+ it('should scope the hook to the contact object', () => {
+ // The engine (not the handler) filters by object — assert the option
+ const { ctx, hookOptions } = createFakeContext();
createHelloPlugin().init(ctx);
- const context = { object: 'task', data: { title: 'Test' } as Record };
- hooks['data:beforeInsert'](context);
- expect(context.data).not.toHaveProperty('welcome_message');
+ expect(hookOptions['beforeInsert']).toEqual({ object: 'contact' });
});
});
```
diff --git a/content/docs/plugins/index.mdx b/content/docs/plugins/index.mdx
index 3fc97005ab..6747044f74 100644
--- a/content/docs/plugins/index.mdx
+++ b/content/docs/plugins/index.mdx
@@ -38,7 +38,7 @@ ObjectStack is built on a **microkernel architecture** where nearly everything b
│ │ (data) │ │ (security) │ │ (server) │ │ (driver)│ │
│ └────────────┘ └────────────┘ └────────────┘ └─────────┘ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
-│ │ REST API │ │ MSW │ │ Custom │ │
+│ │ REST API │ │ Dev │ │ Custom │ │
│ │ (api) │ │ (testing) │ │ (your own) │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────┘
diff --git a/content/docs/plugins/packages.mdx b/content/docs/plugins/packages.mdx
index 01454514d5..f57e51585e 100644
--- a/content/docs/plugins/packages.mdx
+++ b/content/docs/plugins/packages.mdx
@@ -13,10 +13,10 @@ ObjectStack is organized into **70 package manifests** across multiple categorie
|:---|:---:|:---|
| **Core runtime** | 9 | `spec`, `core`, `runtime`, `types`, `metadata`, `objectql`, `rest`, `formula`, `platform-objects` |
| **Client / DX** | 5 | `client`, `client-react`, `cli`, `create-objectstack`, `vscode-objectstack` |
-| **Framework adapters** | 7 | `express`, `fastify`, `hono`, `nestjs`, `nextjs`, `nuxt`, `sveltekit` |
+| **Framework adapters** | 1 | `hono` (other frameworks: build a thin adapter on `HttpDispatcher` — see below) |
| **Drivers** | 4 | `driver-memory`, `driver-sql`, `driver-sqlite-wasm`, `driver-mongodb` |
-| **Plugins** | 22 | `plugin-auth`, `plugin-security`, `plugin-org-scoping`, `plugin-audit`, `plugin-approvals`, `plugin-sharing`, `plugin-email`, `plugin-webhooks`, `plugin-reports`, `plugin-hono-server`, `mcp`, `plugin-msw`, `plugin-dev`, trigger plugins, and knowledge/embedder plugins |
-| **Platform services** | 16 | `service-analytics`, `service-automation`, `service-cache`, `service-cluster`, `service-cluster-redis`, `service-datasource`, `service-feed`, `service-i18n`, `service-job`, `service-knowledge`, `service-messaging`, `service-package`, `service-queue`, `service-realtime`, `service-settings`, `service-storage` |
+| **Plugins** | 18 | `plugin-auth`, `plugin-security`, `plugin-org-scoping`, `plugin-audit`, `plugin-approvals`, `plugin-sharing`, `plugin-email`, `plugin-webhooks`, `plugin-reports`, `plugin-hono-server`, `plugin-dev`, `mcp`, trigger plugins (`trigger-api`, `trigger-record-change`, `trigger-schedule`), and knowledge/embedder plugins (`knowledge-memory`, `knowledge-ragflow`, `embedder-openai`) |
+| **Platform services** | 15 | `service-analytics`, `service-automation`, `service-cache`, `service-cluster`, `service-cluster-redis`, `service-datasource`, `service-i18n`, `service-job`, `service-knowledge`, `service-messaging`, `service-package`, `service-queue`, `service-realtime`, `service-settings`, `service-storage` |
## Core Packages
@@ -44,7 +44,7 @@ import { ObjectSchema, Field } from '@objectstack/spec/data';
- **Purpose**: ObjectKernel with dependency injection, lifecycle hooks, and event bus
- **Exports**: `ObjectKernel`, `LiteKernel`, `Plugin` interface, service management
- **When to use**: Bootstrap your application, manage plugins and services
-- **README**: [View README](/packages/core/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/core/README.md)
```typescript
import { ObjectKernel } from '@objectstack/core';
@@ -58,7 +58,7 @@ const kernel = new ObjectKernel();
- **Purpose**: High-level runtime bootstrap and plugin composition
- **Exports**: Runtime configuration, plugin loaders, capability system
- **When to use**: Use with `defineStack()` for application setup
-- **README**: [View README](/packages/runtime/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/runtime/README.md)
### @objectstack/objectql
@@ -67,7 +67,7 @@ const kernel = new ObjectKernel();
- **Purpose**: ObjectQL query engine with filters, aggregations, and window functions
- **Exports**: Query parser, filter engine, schema registry
- **When to use**: Advanced query operations, custom data access patterns
-- **README**: [View README](/packages/objectql/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/objectql/README.md)
### @objectstack/metadata
@@ -76,7 +76,7 @@ const kernel = new ObjectKernel();
- **Purpose**: Load, validate, and manage metadata from files or runtime
- **Exports**: Metadata loaders, serializers, overlay system, validation
- **When to use**: Dynamic metadata loading, multi-source metadata composition
-- **README**: [View README](/packages/metadata/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/metadata/README.md)
### @objectstack/rest
@@ -85,7 +85,7 @@ const kernel = new ObjectKernel();
- **Purpose**: Automatic REST API generation based on object definitions
- **Exports**: REST server, route generators, middleware
- **When to use**: Expose ObjectStack data via REST API
-- **README**: [View README](/packages/rest/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/rest/README.md)
### @objectstack/formula
@@ -113,7 +113,7 @@ const kernel = new ObjectKernel();
- **Purpose**: Type-safe client for ObjectStack REST API with batching and error handling
- **Exports**: `ObjectStackClient`, query builders, error classes
- **When to use**: Any JavaScript/TypeScript application (Node, React, Vue, Angular, etc.)
-- **README**: [View README](/packages/client/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/client/README.md)
```typescript
import { ObjectStackClient } from '@objectstack/client';
@@ -127,7 +127,7 @@ const client = new ObjectStackClient({ baseUrl: 'https://api.example.com' });
- **Purpose**: React hooks for queries, mutations, real-time subscriptions
- **Exports**: `useQuery`, `useMutation`, `useRealtimeConnection`, `useView`, `useObject`, `useMetadata`, etc.
- **When to use**: React applications
-- **README**: [View README](/packages/client-react/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/client-react/README.md)
```typescript
import { useQuery, useMutation } from '@objectstack/client-react';
@@ -143,7 +143,7 @@ import { useQuery, useMutation } from '@objectstack/client-react';
- **Purpose**: Fast in-memory data storage with full ObjectQL support
- **When to use**: Development, testing, demos (data is lost on restart)
-- **README**: [View README](/packages/plugins/driver-memory/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/driver-memory/README.md)
```typescript
import { InMemoryDriver } from '@objectstack/driver-memory';
@@ -156,7 +156,7 @@ import { InMemoryDriver } from '@objectstack/driver-memory';
- **Purpose**: Production-ready SQL database support with migrations
- **Supports**: PostgreSQL, MySQL, SQLite, and all Knex-compatible databases
- **When to use**: Traditional relational database deployments
-- **README**: [View README](/packages/plugins/driver-sql/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/driver-sql/README.md)
```typescript
import { SqlDriver } from '@objectstack/driver-sql';
@@ -173,7 +173,7 @@ const driver = new SqlDriver({
- **Purpose**: SQLite running entirely in WebAssembly (no native bindings), with optional `fs`-backed persistence
- **Modes**: In-memory (`:memory:`) or a file path persisted via the `persist` option
- **When to use**: Environments without native SQLite, edge/browser runtimes, lightweight local-first storage
-- **README**: [View README](/packages/plugins/driver-sqlite-wasm/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/driver-sqlite-wasm/README.md)
```typescript
import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm';
@@ -186,7 +186,7 @@ const driver = new SqliteWasmDriver({ filename: ':memory:' });
- **Purpose**: Native MongoDB driver for ObjectQL with document-flavored objects
- **When to use**: Existing MongoDB infrastructure, document-shaped data
-- **README**: [View README](/packages/plugins/driver-mongodb/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/driver-mongodb/README.md)
---
@@ -200,7 +200,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Aggregations, time series, funnels, dashboards
- **When to use**: Business intelligence, reporting, metrics dashboards
-- **README**: [View README](/packages/services/service-analytics/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-analytics/README.md)
### @objectstack/service-automation
@@ -208,7 +208,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Autolaunched, screen, and scheduled flows with visual builder support
- **When to use**: Business process automation, approval workflows, scheduled tasks
-- **README**: [View README](/packages/services/service-automation/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-automation/README.md)
### @objectstack/service-cache
@@ -217,15 +217,14 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Adapters**: Memory (dev), Redis (production)
- **Features**: TTL, namespaces, pattern matching, statistics
- **When to use**: Performance optimization, reduce database load
-- **README**: [View README](/packages/services/service-cache/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-cache/README.md)
-### @objectstack/service-feed
+### @objectstack/service-messaging
-**Feed/Chatter Service** — Activity feed with comments, reactions, subscriptions.
+**Messaging Service** — Outbound notification dispatch (ADR-0012).
-- **Features**: Comments, @mentions, reactions, field change tracking, presence
-- **When to use**: Collaboration features, activity streams, social features
-- **README**: [View README](/packages/services/service-feed/README.md)
+- **Features**: `MessagingChannel` registry, `emit()` fan-out, always-on inbox channel; email/webhook/push/IM channels plug in
+- **When to use**: Notifying users across channels from flows, hooks, and plugins
### @objectstack/service-i18n
@@ -233,7 +232,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Multi-language support, interpolation, pluralization, fallback chains
- **When to use**: Multi-language applications, global deployments
-- **README**: [View README](/packages/services/service-i18n/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-i18n/README.md)
### @objectstack/service-job
@@ -241,7 +240,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Cron expressions, intervals, one-time jobs, retry logic, history
- **When to use**: Background tasks, scheduled reports, cleanup jobs
-- **README**: [View README](/packages/services/service-job/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-job/README.md)
### @objectstack/service-queue
@@ -249,7 +248,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Priority queues, retry, rate limiting, worker pools, job events
- **When to use**: Async processing, email sending, report generation, webhooks
-- **README**: [View README](/packages/services/service-queue/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-queue/README.md)
### @objectstack/service-realtime
@@ -257,7 +256,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Channels, presence, broadcasting, typing indicators, cursor tracking
- **When to use**: Real-time dashboards, collaborative editing, live notifications
-- **README**: [View README](/packages/services/service-realtime/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-realtime/README.md)
### @objectstack/service-storage
@@ -265,7 +264,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Upload, download, signed URLs, multipart uploads, metadata
- **When to use**: File attachments, document management, media storage
-- **README**: [View README](/packages/services/service-storage/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-storage/README.md)
### @objectstack/service-package
@@ -273,7 +272,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Upsert by `(id, version)`, SHA-256 integrity hash, `latest` resolution, bulk list/delete
- **When to use**: Marketplace backends, internal tenant-facing registries, CI-driven metadata distribution
-- **README**: [View README](/packages/services/service-package/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-package/README.md)
### @objectstack/service-settings
@@ -281,7 +280,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Per-org / per-user settings, Zod-validated namespaces, default fallbacks
- **When to use**: Application/feature configuration that must be editable at runtime
-- **README**: [View README](/packages/services/service-settings/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/services/service-settings/README.md)
---
@@ -293,7 +292,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Email/password, OAuth providers, session management, RBAC
- **When to use**: User authentication and authorization
-- **README**: [View README](/packages/plugins/plugin-auth/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-auth/README.md)
### @objectstack/plugin-security
@@ -301,7 +300,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Role-based access control, object/field permissions, row-level security, `owner_id` auto-stamp
- **When to use**: Multi-user applications with access control requirements
-- **README**: [View README](/packages/plugins/plugin-security/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-security/README.md)
### @objectstack/plugin-org-scoping
@@ -309,7 +308,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: `organization_id` auto-stamp on insert, per-org seed-data replay, default-org bootstrap, orphan-row claim hook
- **When to use**: Multi-organization SaaS where every row is scoped to an `sys_organization`. Enable by setting `OS_MULTI_ORG_ENABLED=true`; registered automatically before `plugin-security`
-- **README**: [View README](/packages/plugins/plugin-org-scoping/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-org-scoping/README.md)
### @objectstack/plugin-audit
@@ -317,7 +316,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: CRUD audit logs, field-level changes, security events, compliance reports
- **When to use**: SOC 2, HIPAA, GDPR compliance, security monitoring
-- **README**: [View README](/packages/plugins/plugin-audit/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-audit/README.md)
### @objectstack/mcp
@@ -325,7 +324,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: AI tools, data resources, prompt templates for Claude, Cursor, Cline
- **When to use**: AI agent integration, MCP-compatible tools
-- **README**: [View README](/packages/mcp/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/mcp/README.md)
### @objectstack/plugin-hono-server
@@ -333,7 +332,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Lightweight HTTP server, middleware support, edge-compatible
- **When to use**: Serve ObjectStack REST API with Hono
-- **README**: [View README](/packages/plugins/plugin-hono-server/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-hono-server/README.md)
### @objectstack/plugin-dev
@@ -341,7 +340,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern
- **Features**: Metadata validation, schema introspection, debugging tools
- **When to use**: Development and debugging
-- **README**: [View README](/packages/plugins/plugin-dev/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/plugins/plugin-dev/README.md)
### @objectstack/plugin-approvals
@@ -389,7 +388,7 @@ The open edition ships the **Hono** adapter. Hono runs on Node.js, Bun, Deno, an
**Hono Adapter** — the supported HTTP adapter; edge-native and multi-runtime.
- **Use case**: Node.js, Bun, Deno, Cloudflare Workers, Vercel Edge
-- **README**: [View README](/packages/adapters/hono/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/adapters/hono/README.md)
---
@@ -401,7 +400,7 @@ The open edition ships the **Hono** adapter. Hono runs on Node.js, Bun, Deno, an
- **Commands**: `serve`, `dev`, `start`, `doctor`, `compile`, `build`, `validate`, `generate`, `package`, `meta`, … (binary is `os` / `objectstack`)
- **When to use**: Development, deployment, project management
-- **README**: [View README](/packages/cli/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/cli/README.md)
```bash
npx os serve --dev
@@ -413,7 +412,7 @@ npx os serve --dev
- **Templates**: `blank` (default, bundled), plus remote templates `todo`, `compliance`, `content`, `contracts`, `procurement`
- **When to use**: Start a new ObjectStack project
-- **README**: [View README](/packages/create-objectstack/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/create-objectstack/README.md)
```bash
npx create-objectstack my-app
@@ -425,7 +424,7 @@ npx create-objectstack my-app
- **Features**: Syntax highlighting, autocomplete, validation for metadata files
- **When to use**: Enhanced development experience in VS Code
-- **README**: [View README](/packages/vscode-objectstack/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/vscode-objectstack/README.md)
---
@@ -437,7 +436,7 @@ npx create-objectstack my-app
- **Exports**: Type helpers, utility types, branded types
- **When to use**: Imported automatically by other packages
-- **README**: [View README](/packages/types/README.md)
+- **README**: [View README](https://github.com/objectstack-ai/framework/blob/main/packages/types/README.md)
---
diff --git a/content/docs/protocol/diagram.mdx b/content/docs/protocol/diagram.mdx
index ec977f0a15..1277ca2b19 100644
--- a/content/docs/protocol/diagram.mdx
+++ b/content/docs/protocol/diagram.mdx
@@ -278,7 +278,7 @@ These rules ensure clean architecture and prevent circular dependencies.
**See also:**
-- [Data Flow Guide](/guides/data-flow) for detailed sequence diagrams
-- [Kernel Services](/guides/kernel-services) for runtime architecture
-- [API Protocol](/references/api/contract) for endpoint contracts
+- [Data Flow Guide](/docs/api/data-flow) for detailed sequence diagrams
+- [Kernel Services](/docs/kernel/services) for runtime architecture
+- [API Protocol](/docs/references/api/contract) for endpoint contracts
diff --git a/content/docs/protocol/objectos/error-handling.mdx b/content/docs/protocol/objectos/error-handling.mdx
index 1a5b42b030..80a1b64b8b 100644
--- a/content/docs/protocol/objectos/error-handling.mdx
+++ b/content/docs/protocol/objectos/error-handling.mdx
@@ -1047,12 +1047,12 @@ for (let i = 0; i < 1000000; i++) {
icon={}
title="HTTP API"
description="Learn REST endpoint conventions and CRUD operations"
- href="/docs/transport/http-api"
+ href="/docs/protocol/objectos/http-protocol"
/>
}
title="Real-Time Protocols"
description="Implement WebSocket subscriptions and event streaming"
- href="/docs/transport/realtime"
+ href="/docs/protocol/objectos/realtime-protocol"
/>
diff --git a/content/docs/protocol/objectql/security.mdx b/content/docs/protocol/objectql/security.mdx
index 59c61e7f5f..c891e54948 100644
--- a/content/docs/protocol/objectql/security.mdx
+++ b/content/docs/protocol/objectql/security.mdx
@@ -251,7 +251,15 @@ fields:
## 4. Data Masking
-Masking is configured **on the field** via `maskingRule` (`MaskingRuleSchema` in `packages/spec/src/system/masking.zod.ts`). The strategy enum is one of: `redact`, `partial`, `hash`, `tokenize`, `randomize`, `nullify`, `substitute`.
+> **Status: schema exists, field wiring removed.** `MaskingRuleSchema`
+> (`packages/spec/src/system/masking.zod.ts`, strategies `redact`, `partial`,
+> `hash`, `tokenize`, `randomize`, `nullify`, `substitute`) remains in the
+> System namespace, but the per-field `maskingRule` property was **pruned from
+> the Field schema in 2026-06** as dead surface — it was never read by the
+> runtime (see `docs/audits/2026-06-dead-surface-disposition-plan.md`). To keep
+> a value out of reader hands today, use FLS (`readable: false`), `hidden`, or
+> a `type: 'secret'` field. The examples below show the *schema shapes* for
+> implementers, not a wired runtime capability.
### Partial Masking
@@ -374,7 +382,15 @@ publicSharing:
## 6. Field-Level Encryption
-Sensitive fields can be encrypted at rest via `encryptionConfig` (`EncryptionConfigSchema` in `packages/spec/src/system/encryption.zod.ts`). Supported algorithms are `aes-256-gcm` (default), `aes-256-cbc`, and `chacha20-poly1305`. Keys are held by a key-management provider (`local`, `aws-kms`, `azure-key-vault`, `gcp-kms`, `hashicorp-vault`).
+> **Status: schema exists, field wiring removed.** `EncryptionConfigSchema`
+> (`packages/spec/src/system/encryption.zod.ts`, algorithms `aes-256-gcm`
+> default / `aes-256-cbc` / `chacha20-poly1305`; key providers `local`,
+> `aws-kms`, `azure-key-vault`, `gcp-kms`, `hashicorp-vault`) remains in the
+> System namespace, but the per-field `encryptionConfig` property was **pruned
+> from the Field schema in 2026-06** — at-rest field encryption was never
+> implemented by the runtime. The supported channel for secret values is a
+> `type: 'secret'` field (one-way handling via the settings crypto provider).
+> The example below shows the *schema shape* for implementers.
```yaml
fields:
@@ -440,7 +456,7 @@ rowLevelSecurity:
### Defense in Depth
-Combine OWD (`sharingModel: private`), an RLS `using` policy, FLS (`readable: false`) on sensitive fields, and `encryptionConfig` for data at rest.
+Combine OWD (`sharingModel: private`), an RLS `using` policy, FLS (`readable: false`) on sensitive fields, and `type: 'secret'` for values that must never round-trip to clients.
### Security by Default
diff --git a/content/docs/protocol/objectql/types.mdx b/content/docs/protocol/objectql/types.mdx
index 25dfebddea..fff208d5a6 100644
--- a/content/docs/protocol/objectql/types.mdx
+++ b/content/docs/protocol/objectql/types.mdx
@@ -936,32 +936,23 @@ How types convert between databases:
## Sensitive Data: Masking & Encryption
-There is no user-defined "custom type" registry. Instead, sensitive-data behavior
-is configured directly on a field via the `maskingRule` and `encryptionConfig`
-properties (and the `secret` type for reversible encrypted-at-rest values).
+There is no user-defined "custom type" registry. For sensitive values, use the
+dedicated `secret` field type, which encrypts on write and masks on read, and
+restrict reader access with [field-level security](/docs/permissions/field-level-security)
+(`readable: false`) or `hidden`.
```yaml
-# PII masking — show only the last 4 digits to most roles
-ssn:
- type: text
- label: Social Security Number
- maskingRule:
- field: ssn
- strategy: partial
- pattern: "\\d{3}-\\d{2}-(\\d{4})"
- exemptRoles: [compliance_officer]
-
-# Field-level encryption (PCI/HIPAA/GDPR)
-card_number:
- type: text
- label: Credit Card
- encryptionConfig:
- enabled: true
- algorithm: aes-256-gcm
+# Reversible secret (API keys, DB passwords)
+api_key:
+ type: secret
+ label: API Key
```
-For reversible secrets (API keys, DB passwords) use the dedicated `secret` field
-type, which encrypts on write and masks on read.
+> The per-field `maskingRule` and `encryptionConfig` properties were **pruned
+> from the Field schema in 2026-06** — they were declared surface with no
+> runtime consumer. The `MaskingRuleSchema`/`EncryptionConfigSchema` shapes
+> remain in the System namespace for implementers; see
+> [Security & Access Control](/docs/protocol/objectql/security) for their status.
## Type Selection Guide
diff --git a/content/docs/protocol/objectui/concept.mdx b/content/docs/protocol/objectui/concept.mdx
index 02fc7dddc4..30443ae1e3 100644
--- a/content/docs/protocol/objectui/concept.mdx
+++ b/content/docs/protocol/objectui/concept.mdx
@@ -721,6 +721,6 @@ function renderField(field: FieldDefinition) {
### For Business Users
-- [Page Builder](/docs/references/studio/page-builder) - Visual page & form designer
+- [Page API](/docs/references/ui/page) - Page & form composition reference
- [Object Designer](/docs/references/studio/object-designer) - Model objects and fields
- [Flow Builder](/docs/references/studio/flow-builder) - Build automation flows
diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx
index ece83adb17..5197cb2863 100644
--- a/content/docs/references/index.mdx
+++ b/content/docs/references/index.mdx
@@ -43,7 +43,7 @@ Defines the "Shape of Data" and business logic.
| File | Schema | Purpose |
| :--- | :--- | :--- |
-| `field.zod.ts` | `FieldSchema` | Field definitions with 49 types (text, number, select, lookup, formula, vector, location, etc.) |
+| `field.zod.ts` | `FieldSchema` | Field definitions (text, number, select, lookup, formula, vector, location, etc.) |
| `object.zod.ts` | `ObjectSchema` | Object/table definitions with fields, indexes, and capabilities |
| `query.zod.ts` | `QuerySchema` | Abstract query AST supporting window functions, HAVING, DISTINCT, subqueries |
| `validation.zod.ts` | `ValidationRuleSchema` | Validation rules for data integrity |
@@ -63,7 +63,7 @@ Defines the "Shape of Data" and business logic.
| `driver/mongo.zod.ts` | `MongoConfigSchema` | MongoDB driver configuration |
**Key Features:**
-- 49 field types including AI/ML vectors and GPS locations
+- Field types spanning text, relationships, formulas, AI/ML vectors, and GPS locations
- Advanced query capabilities (window functions, HAVING, DISTINCT, subqueries)
- Validation rules and formulas
- Lifecycle hooks for business logic
diff --git a/content/docs/releases/implementation-status.mdx b/content/docs/releases/implementation-status.mdx
index 91e41a9db4..119eefcbd4 100644
--- a/content/docs/releases/implementation-status.mdx
+++ b/content/docs/releases/implementation-status.mdx
@@ -515,22 +515,22 @@ Implementation spans every layer of the platform. Core infrastructure, data mode
diff --git a/content/docs/ui/apps.mdx b/content/docs/ui/apps.mdx
index 126c14b83f..32ee9cd3c8 100644
--- a/content/docs/ui/apps.mdx
+++ b/content/docs/ui/apps.mdx
@@ -50,8 +50,8 @@ const crmApp = {
| `branding` | `AppBranding` | optional | Visual customization |
| `requiredPermissions` | `string[]` | optional | Required permissions to access |
| `homePageId` | `string` | optional | ID of the navigation item to serve as the landing page |
-| `objects` | `string[]` | optional | Objects included in this app |
-| `apis` | `string[]` | optional | API endpoints included |
+| `objects` | `unknown[]` | optional | Objects belonging to this app |
+| `apis` | `unknown[]` | optional | Custom APIs belonging to this app |
| `mobileNavigation` | `object` | optional | Mobile-specific navigation |
## Navigation Items
diff --git a/content/docs/ui/dashboards.mdx b/content/docs/ui/dashboards.mdx
index c22c9c47e9..d2347cdace 100644
--- a/content/docs/ui/dashboards.mdx
+++ b/content/docs/ui/dashboards.mdx
@@ -100,7 +100,7 @@ selects `dimensions` (X / group / split) and `values` (the measures to plot):
| `title` | `string` | optional | Widget display title |
| `description` | `string` | optional | Text shown below the title |
| `filter` | `FilterCondition` | optional | Presentation-scope filter (`runtimeFilter`) |
-| `layout` | `object` | ✅ | Grid position and size |
+| `layout` | `object` | optional | Grid position and size (auto-flowed into the grid when omitted) |
| `chartConfig` | `object` | optional | Advanced chart configuration |
| `colorVariant` | `enum` | optional | KPI/card accent color |
| `compareTo` | `enum \| object` | optional | Period-over-period comparison window |
diff --git a/content/docs/ui/index.mdx b/content/docs/ui/index.mdx
index ecae436daf..6e9035c597 100644
--- a/content/docs/ui/index.mdx
+++ b/content/docs/ui/index.mdx
@@ -1,6 +1,6 @@
---
title: UI Engine
-description: Apps, 9 view render types, dashboards, themes, and public portals — server-driven UI declared as metadata and rendered by the ObjectUI runtime.
+description: Apps, views, dashboards, themes, and public portals — server-driven UI declared as metadata and rendered by the ObjectUI runtime.
---
# UI Engine
@@ -29,7 +29,7 @@ export const CrmApp = App.create({
## The building blocks
- **Apps** group navigation, branding, and entry points for one audience.
-- **Views** present object records in **9 render types**: `grid` (the standard data table), `kanban`, `gallery`, `calendar`, `timeline`, `gantt`, `map`, `chart`, and `tree`. Forms are their own view kind with per-mode layouts ([Views](/docs/ui/views)).
+- **Views** present object records as `grid` (the standard data table), `kanban`, `gallery`, `calendar`, `timeline`, `gantt`, `map`, `chart`, or `tree`. Forms are their own view kind with per-mode layouts ([Views](/docs/ui/views)).
- **Pages** compose free-form layouts from widgets; **Dashboards** combine charts, reports, and datasets for analytics.
- **Themes** define palettes, typography, and spacing as metadata — the CRM example ships light and dark theme variants.
- **Portals** open a scoped slice of the app to external audiences, including **anonymous entry routes** for public data collection ([Forms](/docs/ui/forms), [public data collection](/docs/ui/public-data-collection)).
@@ -39,7 +39,7 @@ export const CrmApp = App.create({
-
+
diff --git a/content/docs/ui/pages.mdx b/content/docs/ui/pages.mdx
index 832e68be21..45c0dd189d 100644
--- a/content/docs/ui/pages.mdx
+++ b/content/docs/ui/pages.mdx
@@ -62,7 +62,7 @@ const homePage = {
| `type` | `enum` | optional | Page type (see below; default `'record'`) |
| `object` | `string` | optional | Associated object (for `record` type) |
| `template` | `string` | optional | Layout template name (default: `'default'`) |
-| `regions` | `PageRegion[]` | ✅ | Layout regions with components |
+| `regions` | `PageRegion[]` | optional | Layout regions with components (default `[]` — `list` pages render via `interfaceConfig`, and an empty `record`/`home`/`app` page falls back to the synthesized default layout) |
| `variables` | `PageVariable[]` | optional | Local state variables |
| `isDefault` | `boolean` | optional | Is default page for its type |
| `assignedProfiles` | `string[]` | optional | Profiles that can access this page |
@@ -77,7 +77,7 @@ const homePage = {
| `utility` | Utility/helper panel | Tools, settings, wizards |
| `list` | Record list/interface surface | Data-driven interface pages |
-The schema also declares roadmap types (`dashboard`, `form`, `record_detail`, `record_review`, `overview`, `blank`) that currently fall back to the list renderer; only the five types above have dedicated renderers today.
+Earlier roadmap types (`dashboard`, `form`, `record_detail`, `record_review`, `overview`, `blank`) were **removed from the schema** because they never shipped a renderer (ADR-0049 enforce-or-remove); only the five types above are valid.
## Regions
@@ -132,7 +132,7 @@ Components are the building blocks placed inside regions.
| `type` | `string` | ✅ | Component type (standard `PageComponentType` enum or custom string) |
| `id` | `string` | optional | Unique component instance identifier |
| `label` | `string` | optional | Display label |
-| `properties` | `Record` | ✅ | Component-specific configuration |
+| `properties` | `Record` | optional | Component-specific configuration (default `{}` — many components carry no props) |
| `events` | `Record` | optional | Event handlers (action expressions) |
| `style` | `object` | optional | CSS styles |
| `className` | `string` | optional | CSS class names |
@@ -146,7 +146,7 @@ The `type` field is a union of the standard `PageComponentType` enum and any cus
- **Navigation:** `app:launcher`, `nav:menu`, `nav:breadcrumb`
- **Utility:** `global:search`, `global:notifications`, `user:profile`
- **AI:** `ai:chat_window`, `ai:suggestion`
-- **Elements:** `element:text`, `element:number`, `element:image`, `element:divider`, `element:button`, `element:filter`, `element:form`, `element:record_picker`
+- **Elements:** `element:text`, `element:number`, `element:image`, `element:divider`, `element:button`, `element:filter`, `element:form`, `element:record_picker`, `element:text_input`
Components may also carry `dataSource` (per-element object binding for multi-object pages), `responsive`, and `aria` configuration. Custom string types are also accepted for project-specific widgets.
@@ -188,7 +188,7 @@ const accountRecordPage = {
width: 'full',
components: [
{
- type: 'record_header',
+ type: 'record:highlights',
id: 'header',
properties: {
fields: ['name', 'type', 'industry', 'owner'],
@@ -202,7 +202,7 @@ const accountRecordPage = {
width: 'small',
components: [
{
- type: 'record_detail',
+ type: 'record:details',
id: 'key_fields',
label: 'Key Fields',
properties: {
@@ -210,7 +210,7 @@ const accountRecordPage = {
},
},
{
- type: 'activity_timeline',
+ type: 'record:activity',
id: 'activities',
label: 'Activity',
},
@@ -221,13 +221,13 @@ const accountRecordPage = {
width: 'large',
components: [
{
- type: 'related_list',
+ type: 'record:related_list',
id: 'contacts',
label: 'Contacts',
properties: { object: 'contact', relationship: 'account' },
},
{
- type: 'related_list',
+ type: 'record:related_list',
id: 'opportunities',
label: 'Opportunities',
properties: { object: 'opportunity', relationship: 'account' },
diff --git a/content/docs/ui/views.mdx b/content/docs/ui/views.mdx
index 71ea1e4c1c..0e65c54c59 100644
--- a/content/docs/ui/views.mdx
+++ b/content/docs/ui/views.mdx
@@ -45,6 +45,7 @@ const taskListView = {
| `gantt` | Gantt chart with dependencies | Project management |
| `map` | Geographic map pins | Location-based data |
| `chart` | Aggregate chart visualization | Dashboards and summaries |
+| `tree` | Self-referencing hierarchy (tree-grid) | Org charts, category trees |
### List View Properties
@@ -52,8 +53,8 @@ const taskListView = {
| :--- | :--- | :--- | :--- |
| `name` | `string` | ✅ | Machine name (`snake_case`) |
| `label` | `string` | ✅ | Display label |
-| `type` | `enum` | ✅ | View type (see table above) |
-| `data` | `ViewData` | ✅ | Data source configuration |
+| `type` | `enum` | optional | View type (see table above; default `'grid'`) |
+| `data` | `ViewData` | optional | Data source configuration (defaults to the `object` provider) |
| `columns` | `ListColumn[]` | optional | Column definitions |
| `filter` | `array` | optional | Filter criteria |
| `sort` | `array` | optional | Sort configuration |
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e659a91ad0..d7e038cc06 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -54,9 +54,15 @@ importers:
lucide-react:
specifier: ^1.22.0
version: 1.22.0(react@19.2.7)
+ mermaid:
+ specifier: ^11.16.0
+ version: 11.16.0
next:
specifier: 16.2.9
version: 16.2.9(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ next-themes:
+ specifier: ^0.4.6
+ version: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
react:
specifier: ^19.2.7
version: 19.2.7
@@ -66,6 +72,9 @@ importers:
tailwind-merge:
specifier: ^3.6.0
version: 3.6.0
+ unist-util-visit:
+ specifier: ^5.1.0
+ version: 5.1.0
devDependencies:
'@objectstack/spec':
specifier: workspace:*
@@ -2214,6 +2223,9 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
+ '@antfu/install-pkg@1.1.0':
+ resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
+
'@authenio/xml-encryption@2.0.2':
resolution: {integrity: sha512-cTlrKttbrRHEw3W+0/I609A2Matj5JQaRvfLtEIGZvlN0RaPi+3ANsMeqAyCAVlH/lUIW2tmtBlSMni74lcXeg==}
engines: {node: '>=12'}
@@ -2529,6 +2541,9 @@ packages:
'@better-fetch/fetch@1.3.1':
resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==}
+ '@braintree/sanitize-url@7.1.2':
+ resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}
+
'@changesets/apply-release-plan@7.1.1':
resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==}
@@ -2584,6 +2599,9 @@ packages:
'@changesets/write@0.4.0':
resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==}
+ '@chevrotain/types@11.1.2':
+ resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==}
+
'@cloudflare/workers-types@4.20260520.1':
resolution: {integrity: sha512-wdmf9Fwabp06OgK9ZyCl8Q77GZ94k9J7OA9qA65FbgxZ/hd3hYRkVNiY7Bvsx4tKim+sWmKqGOblecZInAvoRg==}
@@ -2862,6 +2880,12 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
+ '@iconify/types@2.0.0':
+ resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
+
+ '@iconify/utils@3.1.3':
+ resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==}
+
'@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
@@ -3117,6 +3141,9 @@ packages:
'@mdx-js/mdx@3.1.1':
resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==}
+ '@mermaid-js/parser@1.2.0':
+ resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==}
+
'@modelcontextprotocol/sdk@1.29.0':
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
engines: {node: '>=18'}
@@ -4192,6 +4219,99 @@ packages:
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
+ '@types/d3-array@3.2.2':
+ resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+
+ '@types/d3-axis@3.0.6':
+ resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
+
+ '@types/d3-brush@3.0.6':
+ resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
+
+ '@types/d3-chord@3.0.6':
+ resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
+
+ '@types/d3-color@3.1.3':
+ resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+ '@types/d3-contour@3.0.6':
+ resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
+
+ '@types/d3-delaunay@6.0.4':
+ resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
+
+ '@types/d3-dispatch@3.0.7':
+ resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==}
+
+ '@types/d3-drag@3.0.7':
+ resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+ '@types/d3-dsv@3.0.7':
+ resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
+
+ '@types/d3-ease@3.0.2':
+ resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+ '@types/d3-fetch@3.0.7':
+ resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
+
+ '@types/d3-force@3.0.10':
+ resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
+
+ '@types/d3-format@3.0.4':
+ resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
+
+ '@types/d3-geo@3.1.0':
+ resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
+
+ '@types/d3-hierarchy@3.1.7':
+ resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
+
+ '@types/d3-interpolate@3.0.4':
+ resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+ '@types/d3-path@3.1.1':
+ resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+
+ '@types/d3-polygon@3.0.2':
+ resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
+
+ '@types/d3-quadtree@3.0.6':
+ resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
+
+ '@types/d3-random@3.0.4':
+ resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==}
+
+ '@types/d3-scale-chromatic@3.1.0':
+ resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}
+
+ '@types/d3-scale@4.0.9':
+ resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
+ '@types/d3-selection@3.0.11':
+ resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+ '@types/d3-shape@3.1.8':
+ resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==}
+
+ '@types/d3-time-format@4.0.3':
+ resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
+
+ '@types/d3-time@3.0.4':
+ resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
+ '@types/d3-timer@3.0.2':
+ resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+ '@types/d3-transition@3.0.9':
+ resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+ '@types/d3-zoom@3.0.8':
+ resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+ '@types/d3@7.4.3':
+ resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
+
'@types/debug@4.1.13':
resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
@@ -4210,6 +4330,9 @@ packages:
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+ '@types/geojson@7946.0.16':
+ resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
+
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
@@ -4343,6 +4466,9 @@ packages:
'@ungap/structured-clone@1.3.2':
resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==}
+ '@upsetjs/venn.js@2.0.0':
+ resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==}
+
'@vercel/oidc@3.2.0':
resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==}
engines: {node: '>= 20'}
@@ -5007,6 +5133,14 @@ packages:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
+ commander@7.2.0:
+ resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+ engines: {node: '>= 10'}
+
+ commander@8.3.0:
+ resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
+ engines: {node: '>= 12'}
+
commondir@1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
@@ -5068,6 +5202,12 @@ packages:
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
engines: {node: '>= 0.10'}
+ cose-base@1.0.3:
+ resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
+
+ cose-base@2.2.0:
+ resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
+
crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'}
@@ -5095,6 +5235,162 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ cytoscape-cose-bilkent@4.1.0:
+ resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
+ peerDependencies:
+ cytoscape: ^3.2.0
+
+ cytoscape-fcose@2.2.0:
+ resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==}
+ peerDependencies:
+ cytoscape: ^3.2.0
+
+ cytoscape@3.34.0:
+ resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==}
+ engines: {node: '>=0.10'}
+
+ d3-array@2.12.1:
+ resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
+
+ d3-array@3.2.4:
+ resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+ engines: {node: '>=12'}
+
+ d3-axis@3.0.0:
+ resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
+ engines: {node: '>=12'}
+
+ d3-brush@3.0.0:
+ resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
+ engines: {node: '>=12'}
+
+ d3-chord@3.0.1:
+ resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
+ engines: {node: '>=12'}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-contour@4.0.2:
+ resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
+ engines: {node: '>=12'}
+
+ d3-delaunay@6.0.4:
+ resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+
+ d3-dsv@3.0.1:
+ resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-fetch@3.0.1:
+ resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
+ engines: {node: '>=12'}
+
+ d3-force@3.0.0:
+ resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
+ engines: {node: '>=12'}
+
+ d3-format@3.1.2:
+ resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
+ engines: {node: '>=12'}
+
+ d3-geo@3.1.1:
+ resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
+ engines: {node: '>=12'}
+
+ d3-hierarchy@3.1.2:
+ resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-path@1.0.9:
+ resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}
+
+ d3-path@3.1.0:
+ resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+ engines: {node: '>=12'}
+
+ d3-polygon@3.0.1:
+ resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
+ engines: {node: '>=12'}
+
+ d3-quadtree@3.0.1:
+ resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+ engines: {node: '>=12'}
+
+ d3-random@3.0.1:
+ resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
+ engines: {node: '>=12'}
+
+ d3-sankey@0.12.3:
+ resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==}
+
+ d3-scale-chromatic@3.1.0:
+ resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
+ engines: {node: '>=12'}
+
+ d3-scale@4.0.2:
+ resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+ engines: {node: '>=12'}
+
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-shape@1.3.7:
+ resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==}
+
+ d3-shape@3.2.0:
+ resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+ engines: {node: '>=12'}
+
+ d3-time-format@4.1.0:
+ resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+ engines: {node: '>=12'}
+
+ d3-time@3.1.0:
+ resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ d3-transition@3.0.1:
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+
+ d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+
+ d3@7.9.0:
+ resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
+ engines: {node: '>=12'}
+
+ dagre-d3-es@7.0.14:
+ resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==}
+
dayjs@1.11.21:
resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==}
@@ -5149,6 +5445,9 @@ packages:
defu@6.1.7:
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
+ delaunator@5.1.0:
+ resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
+
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -5199,6 +5498,9 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
+ dompurify@3.4.11:
+ resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==}
+
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
@@ -5298,6 +5600,9 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
+ es-toolkit@1.49.0:
+ resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==}
+
esast-util-from-estree@2.0.0:
resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==}
@@ -5821,6 +6126,9 @@ packages:
resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==}
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
+ hachure-fill@0.5.2:
+ resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
+
happy-dom@20.10.2:
resolution: {integrity: sha512-5p9Sxis3eowDJKqx90QCsgbNA02XXqJ59NOHvD4V6cxp+rP4d/xOyVx7uY3hS8hiUbY1VeiFH8lbJ81AyuDVLQ==}
engines: {node: '>=20.0.0'}
@@ -5947,6 +6255,9 @@ packages:
immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
+ import-meta-resolve@4.2.0:
+ resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==}
+
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
@@ -5975,6 +6286,13 @@ packages:
inline-style-parser@0.2.7:
resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
+ internmap@1.0.1:
+ resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
+
+ internmap@2.0.3:
+ resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+ engines: {node: '>=12'}
+
interpret@2.2.0:
resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==}
engines: {node: '>= 0.10'}
@@ -6195,12 +6513,19 @@ packages:
jws@4.0.1:
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
+ katex@0.16.47:
+ resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==}
+ hasBin: true
+
keytar@7.9.0:
resolution: {integrity: sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==}
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ khroma@2.1.0:
+ resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
+
kleur@4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
@@ -6243,6 +6568,12 @@ packages:
resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==}
engines: {node: '>=22.0.0'}
+ layout-base@1.0.2:
+ resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
+
+ layout-base@2.0.1:
+ resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
+
lazystream@1.0.1:
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
engines: {node: '>= 0.6.3'}
@@ -6360,6 +6691,9 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
+ lodash-es@4.18.1:
+ resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
+
lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
@@ -6479,6 +6813,11 @@ packages:
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
+ marked@16.4.2:
+ resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
+ engines: {node: '>= 20'}
+ hasBin: true
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -6549,6 +6888,9 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
+ mermaid@11.16.0:
+ resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==}
+
micromark-core-commonmark@2.0.3:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
@@ -7117,6 +7459,9 @@ packages:
package-manager-detector@0.2.11:
resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==}
+ package-manager-detector@1.7.0:
+ resolution: {integrity: sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==}
+
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
@@ -7146,6 +7491,9 @@ packages:
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+ path-data-parser@0.1.0:
+ resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==}
+
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -7277,6 +7625,12 @@ packages:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
+ points-on-curve@0.2.0:
+ resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
+
+ points-on-path@0.2.1:
+ resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}
+
postcss-load-config@6.0.1:
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
engines: {node: '>= 18'}
@@ -7598,6 +7952,9 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
+ robust-predicates@3.0.3:
+ resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
+
rolldown@1.0.3:
resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -7611,6 +7968,9 @@ packages:
rou3@0.7.12:
resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==}
+ roughjs@4.6.6:
+ resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
+
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
@@ -7622,6 +7982,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ rw@1.3.3:
+ resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
+
safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
@@ -7901,6 +8264,9 @@ packages:
babel-plugin-macros:
optional: true
+ stylis@4.4.0:
+ resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==}
+
sucrase@3.35.1:
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -8073,6 +8439,10 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
+ ts-dedent@2.3.0:
+ resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==}
+ engines: {node: '>=6.10'}
+
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
@@ -8251,6 +8621,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ uuid@14.0.1:
+ resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==}
+ hasBin: true
+
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
@@ -8570,6 +8944,11 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
+ '@antfu/install-pkg@1.1.0':
+ dependencies:
+ package-manager-detector: 1.7.0
+ tinyexec: 1.2.4
+
'@authenio/xml-encryption@2.0.2':
dependencies:
'@xmldom/xmldom': 0.8.13
@@ -9065,6 +9444,8 @@ snapshots:
'@better-fetch/fetch@1.3.1': {}
+ '@braintree/sanitize-url@7.1.2': {}
+
'@changesets/apply-release-plan@7.1.1':
dependencies:
'@changesets/config': 3.1.4
@@ -9208,6 +9589,8 @@ snapshots:
human-id: 4.2.0
prettier: 2.8.8
+ '@chevrotain/types@11.1.2': {}
+
'@cloudflare/workers-types@4.20260520.1':
optional: true
@@ -9415,6 +9798,14 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
+ '@iconify/types@2.0.0': {}
+
+ '@iconify/utils@3.1.3':
+ dependencies:
+ '@antfu/install-pkg': 1.1.0
+ '@iconify/types': 2.0.0
+ import-meta-resolve: 4.2.0
+
'@img/colour@1.1.0':
optional: true
@@ -9646,6 +10037,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@mermaid-js/parser@1.2.0':
+ dependencies:
+ '@chevrotain/types': 11.1.2
+
'@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)':
dependencies:
'@hono/node-server': 1.19.14(hono@4.12.27)
@@ -10668,6 +11063,123 @@ snapshots:
'@types/cookie@0.6.0':
optional: true
+ '@types/d3-array@3.2.2': {}
+
+ '@types/d3-axis@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-brush@3.0.6':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-chord@3.0.6': {}
+
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-contour@3.0.6':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-delaunay@6.0.4': {}
+
+ '@types/d3-dispatch@3.0.7': {}
+
+ '@types/d3-drag@3.0.7':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-dsv@3.0.7': {}
+
+ '@types/d3-ease@3.0.2': {}
+
+ '@types/d3-fetch@3.0.7':
+ dependencies:
+ '@types/d3-dsv': 3.0.7
+
+ '@types/d3-force@3.0.10': {}
+
+ '@types/d3-format@3.0.4': {}
+
+ '@types/d3-geo@3.1.0':
+ dependencies:
+ '@types/geojson': 7946.0.16
+
+ '@types/d3-hierarchy@3.1.7': {}
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-path@3.1.1': {}
+
+ '@types/d3-polygon@3.0.2': {}
+
+ '@types/d3-quadtree@3.0.6': {}
+
+ '@types/d3-random@3.0.4': {}
+
+ '@types/d3-scale-chromatic@3.1.0': {}
+
+ '@types/d3-scale@4.0.9':
+ dependencies:
+ '@types/d3-time': 3.0.4
+
+ '@types/d3-selection@3.0.11': {}
+
+ '@types/d3-shape@3.1.8':
+ dependencies:
+ '@types/d3-path': 3.1.1
+
+ '@types/d3-time-format@4.0.3': {}
+
+ '@types/d3-time@3.0.4': {}
+
+ '@types/d3-timer@3.0.2': {}
+
+ '@types/d3-transition@3.0.9':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-zoom@3.0.8':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3@7.4.3':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/d3-axis': 3.0.6
+ '@types/d3-brush': 3.0.6
+ '@types/d3-chord': 3.0.6
+ '@types/d3-color': 3.1.3
+ '@types/d3-contour': 3.0.6
+ '@types/d3-delaunay': 6.0.4
+ '@types/d3-dispatch': 3.0.7
+ '@types/d3-drag': 3.0.7
+ '@types/d3-dsv': 3.0.7
+ '@types/d3-ease': 3.0.2
+ '@types/d3-fetch': 3.0.7
+ '@types/d3-force': 3.0.10
+ '@types/d3-format': 3.0.4
+ '@types/d3-geo': 3.1.0
+ '@types/d3-hierarchy': 3.1.7
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-path': 3.1.1
+ '@types/d3-polygon': 3.0.2
+ '@types/d3-quadtree': 3.0.6
+ '@types/d3-random': 3.0.4
+ '@types/d3-scale': 4.0.9
+ '@types/d3-scale-chromatic': 3.1.0
+ '@types/d3-selection': 3.0.11
+ '@types/d3-shape': 3.1.8
+ '@types/d3-time': 3.0.4
+ '@types/d3-time-format': 4.0.3
+ '@types/d3-timer': 3.0.2
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+
'@types/debug@4.1.13':
dependencies:
'@types/ms': 2.1.0
@@ -10684,6 +11196,8 @@ snapshots:
'@types/estree@1.0.9': {}
+ '@types/geojson@7946.0.16': {}
+
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -10832,6 +11346,11 @@ snapshots:
'@ungap/structured-clone@1.3.2': {}
+ '@upsetjs/venn.js@2.0.0':
+ optionalDependencies:
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
'@vercel/oidc@3.2.0': {}
'@vitest/coverage-v8@4.1.9(vitest@4.1.9)':
@@ -11505,6 +12024,10 @@ snapshots:
commander@4.1.1: {}
+ commander@7.2.0: {}
+
+ commander@8.3.0: {}
+
commondir@1.0.1: {}
compress-commons@4.1.2:
@@ -11551,6 +12074,14 @@ snapshots:
object-assign: 4.1.1
vary: 1.1.2
+ cose-base@1.0.3:
+ dependencies:
+ layout-base: 1.0.2
+
+ cose-base@2.2.0:
+ dependencies:
+ layout-base: 2.0.1
+
crc-32@1.2.2: {}
crc32-stream@4.0.3:
@@ -11578,6 +12109,190 @@ snapshots:
csstype@3.2.3: {}
+ cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0):
+ dependencies:
+ cose-base: 1.0.3
+ cytoscape: 3.34.0
+
+ cytoscape-fcose@2.2.0(cytoscape@3.34.0):
+ dependencies:
+ cose-base: 2.2.0
+ cytoscape: 3.34.0
+
+ cytoscape@3.34.0: {}
+
+ d3-array@2.12.1:
+ dependencies:
+ internmap: 1.0.1
+
+ d3-array@3.2.4:
+ dependencies:
+ internmap: 2.0.3
+
+ d3-axis@3.0.0: {}
+
+ d3-brush@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ d3-chord@3.0.1:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-color@3.1.0: {}
+
+ d3-contour@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-delaunay@6.0.4:
+ dependencies:
+ delaunator: 5.1.0
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-dsv@3.0.1:
+ dependencies:
+ commander: 7.2.0
+ iconv-lite: 0.6.3
+ rw: 1.3.3
+
+ d3-ease@3.0.1: {}
+
+ d3-fetch@3.0.1:
+ dependencies:
+ d3-dsv: 3.0.1
+
+ d3-force@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-timer: 3.0.1
+
+ d3-format@3.1.2: {}
+
+ d3-geo@3.1.1:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-hierarchy@3.1.2: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-path@1.0.9: {}
+
+ d3-path@3.1.0: {}
+
+ d3-polygon@3.0.1: {}
+
+ d3-quadtree@3.0.1: {}
+
+ d3-random@3.0.1: {}
+
+ d3-sankey@0.12.3:
+ dependencies:
+ d3-array: 2.12.1
+ d3-shape: 1.3.7
+
+ d3-scale-chromatic@3.1.0:
+ dependencies:
+ d3-color: 3.1.0
+ d3-interpolate: 3.0.1
+
+ d3-scale@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+ d3-format: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ d3-selection@3.0.0: {}
+
+ d3-shape@1.3.7:
+ dependencies:
+ d3-path: 1.0.9
+
+ d3-shape@3.2.0:
+ dependencies:
+ d3-path: 3.1.0
+
+ d3-time-format@4.1.0:
+ dependencies:
+ d3-time: 3.1.0
+
+ d3-time@3.1.0:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-timer@3.0.1: {}
+
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ d3@7.9.0:
+ dependencies:
+ d3-array: 3.2.4
+ d3-axis: 3.0.0
+ d3-brush: 3.0.0
+ d3-chord: 3.0.1
+ d3-color: 3.1.0
+ d3-contour: 4.0.2
+ d3-delaunay: 6.0.4
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-dsv: 3.0.1
+ d3-ease: 3.0.1
+ d3-fetch: 3.0.1
+ d3-force: 3.0.0
+ d3-format: 3.1.2
+ d3-geo: 3.1.1
+ d3-hierarchy: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-path: 3.1.0
+ d3-polygon: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-random: 3.0.1
+ d3-scale: 4.0.2
+ d3-scale-chromatic: 3.1.0
+ d3-selection: 3.0.0
+ d3-shape: 3.2.0
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+ d3-timer: 3.0.1
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+ d3-zoom: 3.0.0
+
+ dagre-d3-es@7.0.14:
+ dependencies:
+ d3: 7.9.0
+ lodash-es: 4.18.1
+
dayjs@1.11.21: {}
debug@4.3.4:
@@ -11616,6 +12331,10 @@ snapshots:
defu@6.1.7: {}
+ delaunator@5.1.0:
+ dependencies:
+ robust-predicates: 3.0.3
+
delayed-stream@1.0.0: {}
delegates@1.0.0:
@@ -11656,6 +12375,10 @@ snapshots:
dependencies:
domelementtype: 2.3.0
+ dompurify@3.4.11:
+ optionalDependencies:
+ '@types/trusted-types': 2.0.7
+
domutils@3.2.2:
dependencies:
dom-serializer: 2.0.0
@@ -11751,6 +12474,8 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.4
+ es-toolkit@1.49.0: {}
+
esast-util-from-estree@2.0.0:
dependencies:
'@types/estree-jsx': 1.0.5
@@ -12385,6 +13110,8 @@ snapshots:
graphql@16.14.2:
optional: true
+ hachure-fill@0.5.2: {}
+
happy-dom@20.10.2:
dependencies:
'@types/node': 26.0.1
@@ -12614,6 +13341,8 @@ snapshots:
immediate@3.0.6: {}
+ import-meta-resolve@4.2.0: {}
+
imurmurhash@0.1.4: {}
indent-string@4.0.0: {}
@@ -12634,6 +13363,10 @@ snapshots:
inline-style-parser@0.2.7: {}
+ internmap@1.0.1: {}
+
+ internmap@2.0.3: {}
+
interpret@2.2.0: {}
ioredis-mock@8.13.1(@types/ioredis-mock@8.2.7(ioredis@5.11.1))(ioredis@5.11.1):
@@ -12844,6 +13577,10 @@ snapshots:
jwa: 2.0.1
safe-buffer: 5.2.1
+ katex@0.16.47:
+ dependencies:
+ commander: 8.3.0
+
keytar@7.9.0:
dependencies:
node-addon-api: 4.3.0
@@ -12854,6 +13591,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
+ khroma@2.1.0: {}
+
kleur@4.1.5:
optional: true
@@ -12884,6 +13623,10 @@ snapshots:
kysely@0.29.2: {}
+ layout-base@1.0.2: {}
+
+ layout-base@2.0.1: {}
+
lazystream@1.0.1:
dependencies:
readable-stream: 2.3.8
@@ -12971,6 +13714,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
+ lodash-es@4.18.1: {}
+
lodash.defaults@4.2.0: {}
lodash.difference@4.5.0: {}
@@ -13085,6 +13830,8 @@ snapshots:
markdown-table@3.0.4: {}
+ marked@16.4.2: {}
+
math-intrinsics@1.1.0: {}
mdast-util-find-and-replace@3.0.2:
@@ -13260,6 +14007,30 @@ snapshots:
merge2@1.4.1: {}
+ mermaid@11.16.0:
+ dependencies:
+ '@braintree/sanitize-url': 7.1.2
+ '@iconify/utils': 3.1.3
+ '@mermaid-js/parser': 1.2.0
+ '@types/d3': 7.4.3
+ '@upsetjs/venn.js': 2.0.0
+ cytoscape: 3.34.0
+ cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0)
+ cytoscape-fcose: 2.2.0(cytoscape@3.34.0)
+ d3: 7.9.0
+ d3-sankey: 0.12.3
+ dagre-d3-es: 7.0.14
+ dayjs: 1.11.21
+ dompurify: 3.4.11
+ es-toolkit: 1.49.0
+ katex: 0.16.47
+ khroma: 2.1.0
+ marked: 16.4.2
+ roughjs: 4.6.6
+ stylis: 4.4.0
+ ts-dedent: 2.3.0
+ uuid: 14.0.1
+
micromark-core-commonmark@2.0.3:
dependencies:
decode-named-character-reference: 1.3.0
@@ -13959,6 +14730,8 @@ snapshots:
dependencies:
quansync: 0.2.11
+ package-manager-detector@1.7.0: {}
+
pako@1.0.11: {}
parse-entities@4.0.2:
@@ -13998,6 +14771,8 @@ snapshots:
path-browserify@1.0.1: {}
+ path-data-parser@0.1.0: {}
+
path-exists@4.0.0: {}
path-expression-matcher@1.6.1: {}
@@ -14099,6 +14874,13 @@ snapshots:
pluralize@8.0.0: {}
+ points-on-curve@0.2.0: {}
+
+ points-on-path@0.2.1:
+ dependencies:
+ path-data-parser: 0.1.0
+ points-on-curve: 0.2.0
+
postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.16)(tsx@4.22.4)(yaml@2.9.0):
dependencies:
lilconfig: 3.1.3
@@ -14474,6 +15256,8 @@ snapshots:
glob: 7.2.3
optional: true
+ robust-predicates@3.0.3: {}
+
rolldown@1.0.3:
dependencies:
'@oxc-project/types': 0.133.0
@@ -14528,6 +15312,13 @@ snapshots:
rou3@0.7.12: {}
+ roughjs@4.6.6:
+ dependencies:
+ hachure-fill: 0.5.2
+ path-data-parser: 0.1.0
+ points-on-curve: 0.2.0
+ points-on-path: 0.2.1
+
router@2.2.0:
dependencies:
debug: 4.4.3(supports-color@8.1.1)
@@ -14544,6 +15335,8 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
+ rw@1.3.3: {}
+
safe-buffer@5.1.2: {}
safe-buffer@5.2.1: {}
@@ -14890,6 +15683,8 @@ snapshots:
client-only: 0.0.1
react: 19.2.7
+ stylis@4.4.0: {}
+
sucrase@3.35.1:
dependencies:
'@jridgewell/gen-mapping': 0.3.13
@@ -15100,6 +15895,8 @@ snapshots:
dependencies:
typescript: 6.0.3
+ ts-dedent@2.3.0: {}
+
ts-interface-checker@0.1.13: {}
ts-morph@28.0.0:
@@ -15301,6 +16098,8 @@ snapshots:
util-deprecate@1.0.2: {}
+ uuid@14.0.1: {}
+
uuid@8.3.2: {}
validate-npm-package-license@3.0.4: