diff --git a/.changeset/docs-audit-4161-service-automation.md b/.changeset/docs-audit-4161-service-automation.md new file mode 100644 index 0000000000..060a42a733 --- /dev/null +++ b/.changeset/docs-audit-4161-service-automation.md @@ -0,0 +1,40 @@ +--- +--- + +Docs only — no package changes, nothing to release. + +Back-fills the implementation-accuracy audit that #4161 never got. Its drift +comment was computed by the mapper bug fixed in #4206: the change to +`packages/services/service-automation` collapsed to the container directory, so +the four docs that actually reference `@objectstack/service-automation` were +either attributed to the wrong package or (on a service-automation-only diff) +not reported at all. Re-derived with the fixed mapper and audited: +`automation/flows.mdx`, `plugins/packages.mdx`, `releases/implementation-status.mdx`, +`releases/v9.mdx`. + +44 evidence-backed fixes, 5 repaired by the adversarial verifier. The +substantive ones: + +- **`flows.mdx`** — `runAs` now states that a `'user'` run which resolved no + trigger user has its data operations refused (the axis #4161 tightened); + `node.type` is documented as an open `string` checked against the live action + registry at `registerFlow()` (ADR-0018), not a closed enum; the `script` + executor is described as naming a callable, with `'email'` / `'slack'` called + out as logger-backed markers that record intent without delivering; the + `.strict()` flow/node/edge/variable shells and the previously undocumented + `timeoutMs`, `inputSchema`, `waitEventConfig`, `boundaryConfig`, + `successMessage`, `errorMessage` keys are added. +- **`implementation-status.mdx`** — REST rows carry their real `/api/v1` prefix; + the memory driver's capability list is replaced with what `InMemoryDriver.supports` + actually declares; Cache/Encryption/Dataset/Sorting statuses corrected; + the `msw` column dropped (not a workspace package); `Role` → `Position` + (ADR-0090 D3), which ratchets `scripts/role-word-baseline.json` down by one — + that gate fails on a *decrease* too, so the baseline update ships with it. +- **`packages.mdx`** — package inventory and the `create*` plugin-factory claim + corrected against the real exports. +- **`v9.mdx`** — notes that `os package publish --visibility` has since shipped + (`private` / `org` / `marketplace`, default `org`). + +18 residual items the agents could not resolve without a code-owner decision are +recorded in the PR body rather than guessed at; two are genuine code/doc +contradictions worth their own issues. diff --git a/content/docs/automation/flows.mdx b/content/docs/automation/flows.mdx index b866045dac..df40e3f0e9 100644 --- a/content/docs/automation/flows.mdx +++ b/content/docs/automation/flows.mdx @@ -73,11 +73,13 @@ const approvalFlow = { | `version` | `number` | optional | Version number (defaults to `1`) | | `status` | `enum` | optional | `'draft'`, `'active'`, `'obsolete'`, `'invalid'` (defaults to `'draft'`) | | `type` | `FlowType` | ✅ | Flow trigger type (see below) | -| `runAs` | `enum` | optional | `'system'` or `'user'` execution context (defaults to `'user'`) | +| `runAs` | `enum` | optional | `'system'` (elevated, bypasses RLS) or `'user'` (the triggering user, RLS-respecting; the default). A `'user'` run that resolved **no** trigger user has nothing to scope to, so its data operations are refused — declare `'system'` for schedule / time-relative / API triggers and for record-change flows fired by a write that carried no user | | `variables` | `FlowVariable[]` | optional | Input/output variables | | `nodes` | `FlowNode[]` | ✅ | Flow nodes | | `edges` | `FlowEdge[]` | ✅ | Connections between nodes | | `errorHandling` | `object` | optional | Error handling strategy | +| `successMessage` | `string` | optional | Toast a screen-flow runner shows on terminal success (plain string — `{var}` is **not** interpolated) | +| `errorMessage` | `string` | optional | Toast a screen-flow runner shows on failure, instead of the raw error | ### Flow Types @@ -110,7 +112,7 @@ Each node performs a specific action in the flow. | `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`) | +| `script` | Call a named callable — a registered function (`config.function`) or a built-in side-effect marker (`config.actionType`) | | `screen` | Display a user form/screen (durable pause) | | `wait` | Pause for a timer or named signal (durable pause; timers auto-resume) | | `subflow` | Invoke another flow — a pause inside the child suspends both runs as a linked chain | @@ -134,11 +136,21 @@ Each node performs a specific action in the flow. | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | `id` | `string` | ✅ | Unique node identifier | -| `type` | `FlowNodeAction` | ✅ | Node type | +| `type` | `string` | ✅ | Node type — a built-in id from the table above **or** a plugin-registered one. Per ADR-0018 the spec does not gate this with a closed enum; it is checked against the live action registry at `registerFlow()` | | `label` | `string` | ✅ | Display label | -| `config` | `object` | optional | Type-specific configuration | -| `connectorConfig` | `object` | optional | External connector settings | +| `config` | `object` | optional | Type-specific configuration (an open record — the registered executor's `configSchema` owns its shape) | +| `connectorConfig` | `object` | optional | `{ connectorId, actionId, input }` for a `connector_action` node | | `position` | `{ x, y }` | optional | Visual position on canvas | +| `timeoutMs` | `number` | optional | Per-node execution timeout | +| `inputSchema` | `object` | optional | Declared input parameter types, for Studio form generation and runtime validation | +| `waitEventConfig` | `object` | optional | `wait`-node event descriptor (`eventType`, `timerDuration`, `signalName`, `timeoutMs`, `onTimeout`) | +| `boundaryConfig` | `object` | optional | BPMN boundary-event descriptor (interop) | + + +The flow, node, edge, and variable **shells are `.strict()`** — a key they do not +declare is a parse error naming the likely intended key, not a silent strip. (A +node's `config` stays open, so plugin node types keep their own vocabulary.) + ### Node Examples @@ -193,18 +205,36 @@ Each node performs a specific action in the flow. **Script:** -The built-in `script` executor dispatches on `config.actionType` (e.g. `email`) -rather than evaluating an arbitrary JavaScript string: +The built-in `script` executor never evaluates an arbitrary JavaScript string — +it **names a callable**. Two forms: + +- **`config.function`** — a function registered through + `defineStack({ functions })`. `config.inputs` is `{var}`-interpolated and + handed to it; `config.outputVariable` binds the returned value as a flow + variable, so a later declarative node persists it. This is the supported way + to run server logic. +- **`config.actionType`** — one of the two built-in side-effect markers, + `'email'` or `'slack'`. These are **logger-backed**: they record the intent + and succeed, they do not deliver anything — reach for a `notify` node when you + want real delivery. Any other `actionType` value is treated as a + registered-function name — except the marker `'invoke_function'`, which means + "call the function named in `config.function`" and errors if that key is + missing. + +Inline `config.script` (a JS source body) is *recognized* but **not executed** — +the built-in runtime has no server-side JS sandbox, so such a node warns and +no-ops. A script node that names neither a built-in action nor a registered +function fails the step loudly rather than passing silently. ```typescript { - id: 'send_welcome', + id: 'score_lead', type: 'script', - label: 'Send Welcome Email', + label: 'Score Lead', config: { - actionType: 'email', - template: 'welcome', - recipients: '{record.email}', + function: 'scoreLead', // registered via defineStack({ functions }) + inputs: { rating: '{record.rating}' }, // {var} templates resolve against flow variables + outputVariable: 'leadScore', // later: fields: { score: '{leadScore}' } }, } ``` @@ -247,8 +277,11 @@ Two things worth knowing: value at all, which counts as unanswered — without `defaultValue: false` the user cannot express "no" by leaving it clear. - **A broken predicate fails open** (the field stays visible) rather than hiding - an input the flow may be waiting on. Nothing validates the expression at - author time yet, so a typo shows up as a field that never hides. + an input the flow may be waiting on. `registerFlow()` does check the predicate + as bare CEL — a `{var}` template or a syntax error is a loud registration + failure that quotes the source and locates it as + `config.fields[N].visibleWhen` — but a predicate that *parses* and simply + names the wrong field still shows up as a field that never hides. **Screen (object form):** @@ -561,8 +594,9 @@ Edges connect nodes and define the execution path: | `source` | `string` | ✅ | Source node ID | | `target` | `string` | ✅ | Target node ID | | `type` | `enum` | optional | `'default'` (success), `'fault'` (error), `'conditional'` (expression-guarded), or `'back'` (declared back-edge, ADR-0044); defaults to `'default'` | -| `condition` | `string` | optional | Boolean expression for branching | +| `condition` | `string` | optional | Boolean CEL predicate for branching (a bare string is stored as `{ dialect: 'cel', source }`) | | `label` | `string` | optional | Label displayed on the connector — cosmetic only. It does **not** select a path except on a branching node (`decision` / `approval`), which picks its out-edge by label. | +| `isDefault` | `boolean` | optional | BPMN default-flow marker (interop). Accepted by the schema, but **the engine does not read it** — traversal selects by `condition` and by `label`. On a `decision` node, the fallback is the out-edge labelled `default`: when no `conditions[]` entry matches, the node emits `branchLabel: 'default'` | ### Fault edges — handling a failed node @@ -582,7 +616,7 @@ edges: [ the type leaves an ordinary edge, and every unconditional out-edge is traversed (in parallel) on **success**. The handler then runs whenever the node *succeeds*, never when it fails, and the run still aborts on failure. Both -halves are silent, so `objectstack validate` reports this as +halves are silent, so `os validate` reports this as `flow-error-label-not-fault`. @@ -621,7 +655,7 @@ The split is deliberate. A dropped filter condition does not narrow a query, it widens it — so if guards were routable, one `fault` edge on a `delete_record` would turn off the protection against emptying the object while the run still reported success. Re-running changes nothing either: the fix is to correct the -metadata, which `objectstack validate` will point at. +metadata, which `os validate` will point at. #### Fault edges vs. `errorHandling.retry` @@ -673,9 +707,12 @@ errorHandling: { | Property | Type | Description | | :--- | :--- | :--- | -| `strategy` | `enum` | `'fail'` (stop), `'retry'` (retry), `'continue'` (skip) | +| `strategy` | `enum` | `'fail'` (stop) or `'retry'` (re-run the whole flow). `'continue'` parses but the engine branches only on `'retry'`, so it behaves exactly like `'fail'` — use a `fault` edge to keep going past a failed node | | `maxRetries` | `number` | Maximum retry attempts (0-10) | | `retryDelayMs` | `number` | Delay between retries (ms) | +| `backoffMultiplier` | `number` | Exponential backoff multiplier (default `1`) | +| `maxRetryDelayMs` | `number` | Ceiling on the backed-off delay (default `30000`) | +| `jitter` | `boolean` | Randomize the delay to avoid a thundering herd (default `false`) | ## Discovery & Registration @@ -782,30 +819,43 @@ credentials. **`success: true` doesn't always mean "it ran".** If the start condition isn't -met, the response is a *successful* skip: -`{ "success": true, "data": { "skipped": true, "reason": "condition_not_met" } }`. +met, the response is a *successful* skip — the run result rides in `data`, and +the skip marker sits on its `output`: +`{ "success": true, "data": { "success": true, "output": { "skipped": true, "reason": "condition_not_met" } } }`. A self-triggering flow caught by the loop guard reports -`reason: "reentrancy_loop_guard"` the same way. Check `skipped` before assuming -the work happened. +`reason: "reentrancy_loop_guard"` the same way. Check `data.output.skipped` +before assuming the work happened. ## Console Flow Viewer & Test Runner -Every flow can surface in the Console metadata browser under `/_console/`. -ObjectUI's flow viewer replaces the default JSON inspector with rich tabs when -the flow viewer plugin is installed: +Every flow surfaces in the Console metadata browser under `/_console/`, where +ObjectUI's app-shell registers `FlowPreview` as the `flow` metadata preview +(`registerMetadataPreview('flow', FlowPreview)`) in place of the default JSON +inspector. It renders the graph on a canvas — editable (add a node, patch a +selection) only in the designer's edit mode, read-only otherwise. Opening a flow +shows the **full-width canvas with no side panel**; the toolbar toggles one +collapsible panel at a time: -| Tab | Component | Purpose | +| Side panel | Component | Purpose | |:---|:---|:---| -| **Overview** | `FlowViewer` | Renders trigger type, variables, nodes, edges, and `errorHandling` as inspector cards | -| **Run** | `FlowTestRunner` | Auto-generates a form for every `isInput: true` variable (with type-aware coercion for `number` / `boolean` / `object` / `list`), executes the flow against the per-project kernel, and shows the returned outputs + run id | -| **Runs** | `FlowRunsPanel` | Lists historical executions for the selected flow with status, duration, and a deep-link to the run record | - -The runner posts to the same -[`POST /api/v1/automation/:name/trigger`](#run-a-flow-via-api) endpoint, -scoped to the active project. All three components participate in the Studio -authentication / project-scope context, so they work identically in -single-project mode and in cloud mode. +| **Variables** | — | The flow's declared variables with their types and input/output flags. Opt-in — it is the fallback panel, not one that opens on its own | +| **Problems** | `ProblemsPanel` | Validation findings; selecting one reveals the offending node or edge on the canvas | +| **Debug** | `FlowSimulatorPanel` | Steps the graph client-side, highlighting the active node, the visited nodes, and the traversed edges | +| **Runs** | `FlowRunsPanel` | Real run history from [`GET /api/v1/automation/:name/runs`](#observing-runs), each run's step log nested by loop iteration / parallel branch / try-catch handler | + +Actually *running* a flow is a separate Console page — **Developer › Flow Runs** +(`developer/flow-runs`). Its test runner auto-generates a form for every +`isInput: true` variable (with type-aware coercion for `number` / `boolean` / +`object` / `list`) and calls `client.automation.execute(name, { params })` — +the same [`POST /api/v1/automation/:name/trigger`](#run-a-flow-via-api) +endpoint — then renders the returned result. A screen flow that comes back +`paused` is handed to the same `FlowRunner` the record and list surfaces use, +so multi-step wizards (and `object-form` steps) can be driven to completion +from here instead of orphaning a suspended run. A run-history panel beside it +lists recent runs and expands one for its step detail. The Console is not +project-scoped: the client comes from the app adapter, with no `projectId` in +the route. ## Flows in practice @@ -1040,6 +1090,6 @@ To branch on **which** event fired, test `previous` — it is empty on create ## Related -- [Workflow Metadata](/docs/automation/workflows) — Event-triggered automation rules +- [Workflow Metadata](/docs/automation/workflows) — why there is no standalone Workflow Rule type, and what replaces it - [Object Metadata](/docs/data-modeling/objects) — Objects that flows operate on - [Validation Metadata](/docs/data-modeling/validation) — Data validation rules diff --git a/content/docs/plugins/packages.mdx b/content/docs/plugins/packages.mdx index 8201a58c19..7b6ca62e8d 100644 --- a/content/docs/plugins/packages.mdx +++ b/content/docs/plugins/packages.mdx @@ -5,7 +5,7 @@ description: Complete guide to all ObjectStack packages, services, drivers, plug # Package Overview -ObjectStack is organized into **71 package manifests** across multiple categories. This guide provides an overview of the framework packages, services, drivers, plugins, and adapters in the [framework repository](https://github.com/objectstack-ai/objectstack/tree/main/packages). +ObjectStack is organized into **72 package manifests** across multiple categories. This guide provides an overview of the framework packages, services, drivers, plugins, and adapters in the [framework repository](https://github.com/objectstack-ai/objectstack/tree/main/packages). ### Package categories at a glance @@ -198,9 +198,9 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern ### @objectstack/service-analytics -**Analytics Service** — Multi-driver analytics with NativeSQL, ObjectQL, InMemory strategies. +**Analytics Service** — Multi-driver analytics with built-in NativeSQL and ObjectQL strategies (the lowest-priority `InMemoryStrategy` is not built in — it ships in `@objectstack/driver-memory`). -- **Features**: Aggregations, time series, funnels, dashboards +- **Features**: Aggregations (measures/dimensions), time dimensions with granularity, dashboard/report widget queries - **When to use**: Business intelligence, reporting, metrics dashboards - **README**: [View README](https://github.com/objectstack-ai/objectstack/blob/main/packages/services/service-analytics/README.md) @@ -214,10 +214,10 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern ### @objectstack/service-cache -**Cache Service** — In-memory and Redis caching. +**Cache Service** — In-memory caching behind the `ICacheService` contract. -- **Adapters**: Memory (dev), Redis (production) -- **Features**: TTL, namespaces, pattern matching, statistics +- **Adapters**: Memory (the only working adapter). `RedisCacheAdapter` is a **skeleton** — every method throws `not yet implemented`, and `CacheServicePlugin({ adapter: 'redis' })` refuses to start. For a real distributed cache, register your own `ICacheService` via `ctx.registerService('cache', impl)`. +- **Features**: `get` / `set` / `delete` / `has` / `clear` / `stats`, per-entry TTL, `maxSize` eviction, lookup & write metrics - **When to use**: Performance optimization, reduce database load - **README**: [View README](https://github.com/objectstack-ai/objectstack/blob/main/packages/services/service-cache/README.md) @@ -253,18 +253,19 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern ### @objectstack/service-queue -**Queue Service** — Job queues with in-memory and BullMQ adapters. +**Queue Service** — Job queues with an in-memory adapter and a durable, database-backed adapter (`sys_job_queue`). No BullMQ/Redis adapter is shipped. -- **Features**: Priority queues, retry, rate limiting, worker pools, job events +- **Features**: Priority, delayed / scheduled delivery, `maxAttempts` + fixed/exponential backoff, dead-letter queue and replay, idempotency keys; multi-node claim via a lease on the `db` adapter - **When to use**: Async processing, email sending, report generation, webhooks - **README**: [View README](https://github.com/objectstack-ai/objectstack/blob/main/packages/services/service-queue/README.md) ### @objectstack/service-realtime -**Realtime Service** — WebSocket pub/sub for live updates. +**Realtime Service** — in-process pub/sub implementing `IRealtimeService`, backed by an in-memory adapter. -- **Features**: Channels, presence, broadcasting, typing indicators, cursor tracking -- **When to use**: Real-time dashboards, collaborative editing, live notifications +- **Features**: `publish` / `subscribe` / `unsubscribe`, channel routing, per-subscription filtering by object name and event type, a `maxSubscriptions` cap; registers the `sys_presence` object +- **Not included**: there is **no WebSocket/SSE endpoint and no client transport** — `IRealtimeService.handleUpgrade` is deliberately unimplemented platform-wide until the identity-admission seam exists. The adapter is process-local (single-instance only). +- **When to use**: Trusted, server-internal subscribers (webhook auto-enqueuer, knowledge sync) - **README**: [View README](https://github.com/objectstack-ai/objectstack/blob/main/packages/services/service-realtime/README.md) ### @objectstack/service-storage @@ -299,8 +300,8 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern **Authentication Plugin** — Better-auth integration with ObjectQL. -- **Features**: Email/password, OAuth providers, session management, RBAC -- **When to use**: User authentication and authorization +- **Features**: Email/password, OAuth providers, session management, bearer-token auth, password reset and email verification (authorization itself lives in `plugin-security`) +- **When to use**: User authentication and identity - **README**: [View README](https://github.com/objectstack-ai/objectstack/blob/main/packages/plugins/plugin-auth/README.md) ### @objectstack/plugin-security @@ -354,7 +355,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern **Approvals Plugin** — Contributes the `approval` flow node (ADR-0019): an approval runs on the one automation engine as a durable-pause node, backed by `sys_approval_request` / `sys_approval_action`. -- **Features**: Approver resolution (user/org_membership_level/position/team/department/manager/field/queue), `first_response` / `unanimous`, record lock, status mirror, per-node SLA escalation, audit trail +- **Features**: Approver resolution (manager/position/department/team/field/expression/org_membership_level/user), `first_response` / `unanimous` / `quorum` / `per_group`, record lock, status mirror, per-node SLA escalation, audit trail - **When to use**: Any flow that needs human sign-off (expense, quote, contract, …) — add an `approval` node and branch on `approve` / `reject` ### @objectstack/plugin-sharing @@ -368,7 +369,7 @@ All services implement contracts from `@objectstack/spec/contracts` and are kern **Email Plugin** — Outbound email channel and templates. -- **Features**: Provider adapters, MJML templates, delivery tracking +- **Features**: Provider transports (Postmark, Resend), a minimal mustache-style `{{placeholder}}` template renderer, delivery tracking on `sys_email` - **When to use**: Transactional and workflow-driven email ### @objectstack/plugin-webhooks @@ -440,9 +441,9 @@ npx create-objectstack my-app ### @objectstack/types -**Shared Type Utilities** — Common TypeScript types and utilities. +**Shared Type Utilities** — Dependency-light runtime contracts that break circular imports between packages. -- **Exports**: Type helpers, utility types, branded types +- **Exports**: `IKernel`, `RuntimePlugin`, `RuntimeContext`, plus env-reading (`readEnvWithDeprecation`), degraded-boot, error-leak, and module-not-found helpers - **When to use**: Imported automatically by other packages - **README**: [View README](https://github.com/objectstack-ai/objectstack/blob/main/packages/types/README.md) @@ -460,13 +461,16 @@ The snippets below illustrate which **runtime** packages you typically reach for ### For New Projects (edge / AI-native) ```typescript -// Host bootstrap (pseudocode — exact shape depends on adapter) -import { ObjectKernel } from '@objectstack/core'; +// Host bootstrap (exact shape depends on adapter) +import { ObjectKernel, DriverPlugin } from '@objectstack/runtime'; +import { ObjectQLPlugin } from '@objectstack/objectql'; import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; // Open edition: AI is exposed via @objectstack/mcp (BYO-AI), not an in-process plugin. const kernel = new ObjectKernel(); -await kernel.use(new SqliteWasmDriver({ filename: ':memory:' })); +await kernel.use(new ObjectQLPlugin()); +// A driver is not a plugin — wrap it in `DriverPlugin` before `kernel.use()`. +await kernel.use(new DriverPlugin(new SqliteWasmDriver({ filename: ':memory:' }))); // Point your own AI (Claude/Cursor/local model) at objects, queries & actions over MCP. await kernel.bootstrap(); ``` @@ -474,28 +478,29 @@ await kernel.bootstrap(); ### For Traditional Web Apps ```typescript -import { createDriverPlugin } from '@objectstack/driver-sql'; -import { createAuthPlugin } from '@objectstack/plugin-auth'; -import { createServiceQueuePlugin } from '@objectstack/service-queue'; +import { DriverPlugin } from '@objectstack/runtime'; +import { SqlDriver } from '@objectstack/driver-sql'; +import { AuthPlugin } from '@objectstack/plugin-auth'; +import { QueueServicePlugin } from '@objectstack/service-queue'; -await kernel.use(createDriverPlugin({ client: 'pg', /* … */ })); -await kernel.use(createAuthPlugin({ /* … */ })); -await kernel.use(createServiceQueuePlugin({ /* … */ })); +await kernel.use(new DriverPlugin(new SqlDriver({ client: 'pg', connection: { /* … */ } }))); +await kernel.use(new AuthPlugin({ /* … */ })); +await kernel.use(new QueueServicePlugin({ adapter: 'auto' })); ``` ### For Enterprise Applications ```typescript -import { createSecurityPlugin } from '@objectstack/plugin-security'; -import { createAuditPlugin } from '@objectstack/plugin-audit'; -import { createServiceAnalyticsPlugin } from '@objectstack/service-analytics'; +import { SecurityPlugin } from '@objectstack/plugin-security'; +import { AuditPlugin } from '@objectstack/plugin-audit'; +import { AnalyticsServicePlugin } from '@objectstack/service-analytics'; -await kernel.use(createSecurityPlugin({ /* … */ })); -await kernel.use(createAuditPlugin({ /* … */ })); -await kernel.use(createServiceAnalyticsPlugin({ /* … */ })); +await kernel.use(new SecurityPlugin({ /* … */ })); +await kernel.use(new AuditPlugin()); +await kernel.use(new AnalyticsServicePlugin({ /* … */ })); ``` -> The exact factory names follow each package's `README.md` — they all return a plugin object compatible with `kernel.use()`. +> The official plugin and service packages above export plugin **classes** you instantiate (`new XPlugin(options)`) — check each package's `README.md` for its option shape. Some infrastructure packages ship a `create*Plugin` factory instead (`createRestApiPlugin`, `createDispatcherPlugin`, `createPlatformObjectsPlugin`, …). Either way the result implements the `Plugin` interface `kernel.use()` accepts. --- diff --git a/content/docs/releases/implementation-status.mdx b/content/docs/releases/implementation-status.mdx index 71270ef03a..61688480d6 100644 --- a/content/docs/releases/implementation-status.mdx +++ b/content/docs/releases/implementation-status.mdx @@ -58,8 +58,8 @@ This matrix is generated from actual codebase analysis and represents the curren **Notes:** - ObjectQL implements complete IDataEngine interface -- Memory driver is reference implementation (basic CRUD only) -- Advanced query features (aggregations, window functions) require production drivers +- Memory driver is the reference implementation: filters, sorting, pagination, aggregations, snapshot transactions, streaming and `syncSchema()` are on; joins, window functions, subqueries, full-text and vector search are off (`InMemoryDriver.supports`) +- Advanced query features (joins, window functions, subqueries) require production drivers - Memory driver supports aggregations (groupBy, count, sum, avg, min, max) ### Metadata Framework @@ -70,8 +70,9 @@ This matrix is generated from actual codebase analysis and represents the curren - Metadata API (list types, list items, get item) is fully functional - Metadata is loaded from config files or `dist/objectstack.json` at startup and held in memory -- `MetadataPlugin` no longer registers `sys_metadata` / `sys_metadata_history` into the runtime or auto-bridges ObjectQL to `DatabaseLoader` -- Future: production Artifact API loader and durable local artifact cache +- `MetadataPlugin` auto-provisions the `sys_metadata` / `sys_metadata_history` storage objects (plus the ADR-0067 commit log and the metadata audit trail) by default, so `PUT /api/v1/meta/{type}/{name}` always has somewhere to write. Per-project (cloud) kernels opt out with `registerSystemObjects: false`, because there the control plane owns those tables +- `MetadataPlugin` does **not** auto-bridge ObjectQL to a `DatabaseLoader` — there is no automatic runtime project-DB bridge, and nothing in the packages calls the bridge during boot. Database-backed metadata is an explicit `MetadataManager` opt-in (`setDataEngine()` / `setDatabaseDriver()`, or a `datasource` + `driver` pair on the manager config), and even then it is control-plane only: both methods return early whenever an `environmentId` is supplied, so a per-project kernel never gets a database-backed metadata loader +- Future: production Artifact API loader (`artifactSource: { mode: 'artifact-api' }` is reserved) and durable local artifact cache ### System Services @@ -83,10 +84,10 @@ This matrix is generated from actual codebase analysis and represents the curren | **Tracing** | @objectstack/observability | ⚠️ | Error/exporter pipeline shipped (`error-exporters.ts`); full distributed tracing in progress | | **Audit** | @objectstack/plugin-audit | ✅ | Audit writers + audit objects shipped | | **Job** | @objectstack/service-job | ✅ | Job service with cron/db/interval adapters | -| **Cache** | ⚠️ | ⚠️ | HTTP caching implemented, in-memory cache partial | +| **Cache** | @objectstack/service-cache | ✅ | `ICacheService` with in-memory and Redis adapters (`cache-service-plugin.ts`); HTTP caching ships separately in `@objectstack/rest` | | **Translation** | @objectstack/service-i18n | ✅ | i18n/translation service with file adapter | | **Feature Flags** | ❌ | 📋 | Planned | -| **Encryption** | ❌ | 📋 | Planned | +| **Encryption** | @objectstack/service-settings | ⚠️ | `ICryptoProvider` seam with a local AES-256-GCM provider (`local-crypto-provider.ts`); `secret` fields are encrypted at rest into `sys_secret` (`objectql/secret-fields.ts`). Managed KMS/Vault providers not shipped | | **Compliance** | ❌ | 📋 | Planned | | **Masking** | ❌ | 📋 | Planned | | **Notification** | @objectstack/service-messaging | 🟡 | Framework pipeline shipped; objectui bell cut-over remains | @@ -105,7 +106,7 @@ This matrix is generated from actual codebase analysis and represents the curren | **Object** | ✅ | ✅ | ✅ | ✅ Full | | **Validation** | ✅ | ⚠️ | ❌ | ⚠️ Partial | | **Hook** | ✅ | ✅ | ❌ | ✅ Full | -| **Dataset** | ✅ | ❌ | ❌ | ❌ Not Impl | +| **Dataset** | ✅ | ❌ | ✅ | ✅ `@objectstack/service-analytics` | | **Mapping** | ✅ | ❌ | ❌ | ❌ Not Impl | | **Document** | ✅ | ❌ | ❌ | ❌ Not Impl | | **External Lookup** | ✅ | ❌ | ❌ | ❌ Not Impl | @@ -116,14 +117,14 @@ This matrix is generated from actual codebase analysis and represents the curren |:-----------|:-------------:|:------| | text, textarea, email, url, phone | ✅ | Full support | | number, currency, percent | ✅ | Full support | -| boolean, checkbox | ✅ | Full support | +| boolean, toggle | ✅ | Full support (there is no `checkbox` type — a checkbox group is `checkboxes`) | | date, datetime, time | ✅ | Full support | | select, multiselect | ✅ | Full support | | lookup, master_detail | ✅ | Full support in spec, partial in memory driver | | formula | ✅ | CEL-backed formula fields are implemented | | summary | ✅ | Server-side rollups for count/sum/min/max/avg on child records | -| json, array | ✅ | Full support | -| file, image | 📋 | Protocol defined, not implemented | +| json, tags | ✅ | Full support (there is no `array` type — use `json`, `tags`, or the `repeater` embedded type) | +| file, image | ✅ | File-class fields (`image`/`file`/`avatar`/`video`/`audio`) hold an opaque `sys_file` id; ownership + release are tracked by `@objectstack/service-storage` | ### Query Engine @@ -133,8 +134,8 @@ This matrix is generated from actual codebase analysis and represents the curren | **Query Builder** | ❌ | ✅ | ❌ | ✅ Client | | **Filter Operators** | ✅ | ✅ | ⚠️ | ⚠️ Partial | | **Aggregations** | ✅ | ✅ | ✅ | ✅ Full | -| **Joins** | ✅ | ✅ | ❌ | ⚠️ Spec only | -| **Sorting** | ✅ | ✅ | ❌ | ⚠️ Spec only | +| **Joins** | ✅ | ✅ | ❌ | ⚠️ Driver-dependent | +| **Sorting** | ✅ | ✅ | ✅ | ✅ Full | | **Pagination** | ✅ | ✅ | ✅ | ✅ Full | | **Window Functions** | ✅ | ❌ | ❌ | ❌ Spec only | | **Subqueries** | ✅ | ❌ | ❌ | ❌ Spec only | @@ -142,8 +143,8 @@ This matrix is generated from actual codebase analysis and represents the curren **Notes:** - Full query protocol is defined and implemented in ObjectQL engine - Actual query capability depends on driver implementation -- Memory driver supports basic queries only -- Client SDK provides query builder utilities +- Memory driver covers filters, sorting, pagination and aggregations; joins, window functions and subqueries are off +- Client SDK provides query builder utilities (`QueryBuilder` / `FilterBuilder`, `createQuery` / `createFilter`) --- @@ -164,27 +165,29 @@ This matrix is generated from actual codebase analysis and represents the curren | **HTTP Cache** | ✅ | ✅ | ✅ | ✅ | ✅ Full | | **Batch** | ✅ | ✅ | ✅ | ✅ | ✅ Full | -The data (`/data`), metadata (`/meta`), and batch endpoints are implemented in `@objectstack/rest` (`rest-server.ts`); `@objectstack/runtime` re-exports `RestServer` from that package and provides the underlying HTTP server/dispatcher. +The data (`/api/v1/data`), metadata (`/api/v1/meta`), and batch endpoints are implemented in `@objectstack/rest` (`rest-server.ts`); `@objectstack/runtime` re-exports `RestServer` from that package and provides the underlying HTTP server/dispatcher. **REST Endpoints Implemented:** | Endpoint Pattern | Method | Purpose | Status | |:----------------|:------:|:--------|:------:| -| `/api/v1` | GET | API discovery | ✅ | -| `/meta` | GET | List metadata types | ✅ | -| `/meta/{type}` | GET | List items of type | ✅ | -| `/meta/{type}/{name}` | GET | Get specific metadata item | ✅ | -| `/data/{object}` | GET | List records | ✅ | -| `/data/{object}/{id}` | GET | Get single record | ✅ | -| `/data/{object}` | POST | Create record | ✅ | -| `/data/{object}/{id}` | PATCH | Update record | ✅ | -| `/data/{object}/{id}` | DELETE | Delete record | ✅ | -| `/data/{object}/createMany` | POST | Batch create | ✅ | -| `/data/{object}/updateMany` | POST | Batch update | ✅ | -| `/data/{object}/deleteMany` | POST | Batch delete | ✅ | -| `/data/{object}/{id}/clone` | POST | Clone a record (gated by `enable.clone`) | ✅ | -| `/data/{object}/batch` | POST | Atomic batch operations | ✅ | -| `/batch` | POST | Cross-object atomic batch (parent + children in one transaction, `$ref`) | ✅ | +| `/api/v1` | GET | API discovery (alias; the SDK connects via `/api/v1/discovery`) | ✅ | +| `/api/v1/meta` | GET | List metadata types | ✅ | +| `/api/v1/meta/{type}` | GET | List items of type | ✅ | +| `/api/v1/meta/{type}/{name}` | GET | Get specific metadata item | ✅ | +| `/api/v1/data/{object}` | GET | List records | ✅ | +| `/api/v1/data/{object}/{id}` | GET | Get single record | ✅ | +| `/api/v1/data/{object}` | POST | Create record | ✅ | +| `/api/v1/data/{object}/{id}` | PATCH | Update record | ✅ | +| `/api/v1/data/{object}/{id}` | DELETE | Delete record | ✅ | +| `/api/v1/data/{object}/createMany` | POST | Batch create | ✅ | +| `/api/v1/data/{object}/updateMany` | POST | Batch update | ✅ | +| `/api/v1/data/{object}/deleteMany` | POST | Batch delete | ✅ | +| `/api/v1/data/{object}/{id}/clone` | POST | Clone a record (gated by `enable.clone`) | ✅ | +| `/api/v1/data/{object}/batch` | POST | Atomic batch operations | ✅ | +| `/api/v1/batch` | POST | Cross-object atomic batch (parent + children in one transaction, `$ref`) | ✅ | + +Every route carries the `/api/v1` prefix. When project scoping is enabled each row is additionally mirrored under `/api/v1/environments/{environmentId}`. The full audited route list lives in `packages/rest/src/rest-route-ledger.ts`. ### Advanced Protocols @@ -249,11 +252,11 @@ The data (`/data`), metadata (`/meta`), and batch endpoints are implemented in ` | **Webhook** | ✅ | ❌ | ✅ | ✅ `@objectstack/plugin-webhooks` | | **ETL** | ✅ | ❌ | ✅ | 📋 Plugin | | **Sync** | ✅ | ❌ | ✅ | 📋 Plugin | -| **Trigger Registry** | ✅ | ❌ | ✅ | ✅ `trigger-record-change` / `trigger-schedule` | +| **Trigger Registry** | ✅ | ❌ | ✅ | ✅ `trigger-api` / `trigger-record-change` / `trigger-schedule` | **Notes:** - Hook system is implemented in ObjectQL (beforeFind, afterInsert, etc.) — this is data-layer eventing, not workflow automation -- The flow/workflow engine ships in `@objectstack/service-automation` (`engine.ts`, builtin nodes, `plugin.ts`); approvals, webhooks, and triggers ship as `plugin-approvals`, `plugin-webhooks`, `trigger-record-change`, and `trigger-schedule` +- The flow/workflow engine ships in `@objectstack/service-automation` (`engine.ts`, builtin nodes, `plugin.ts`); approvals, webhooks, and triggers ship as `@objectstack/plugin-approvals`, `@objectstack/plugin-webhooks`, `@objectstack/trigger-api`, `@objectstack/trigger-record-change`, and `@objectstack/trigger-schedule` - ETL and Sync protocols are defined but not yet implemented as plugins - Discovery API reports automation service as `unavailable` until a plugin is registered @@ -267,26 +270,26 @@ The data (`/data`), metadata (`/meta`), and batch endpoints are implemented in ` ### Auth Service (`plugin-auth`) -The `auth` service in `CoreServiceName` covers both **authentication** (identity) and **authorization** (permissions). There is no separate `permission` service — it is part of `auth`. +`CoreServiceName` names a single `auth` service — there is no `permission` entry in that enum. Authentication/identity is what `@objectstack/plugin-auth` registers under that name. Authorization is registered *outside* `CoreServiceName`: `@objectstack/plugin-security` registers `security` (plus `security.permissions`, `security.rls`, `security.fieldMasker`) and `@objectstack/plugin-sharing` registers `sharing`, `sharingRules`, and `shareLinks`. | Protocol | Area | @objectstack/spec | Kernel | Plugin Required | Status | |:---------|:-----|:-----------------:|:------:|:---------------:|:------:| -| **Identity** | Authentication | ✅ | ❌ | ✅ | 📋 Plugin | -| **Auth Config** | Authentication | ✅ | ❌ | ✅ | 📋 Plugin | -| **Role** | Authentication | ✅ | ❌ | ✅ | 📋 Plugin | -| **Organization** | Authentication | ✅ | ❌ | ✅ | 📋 Plugin | +| **Identity** | Authentication | ✅ | ❌ | ✅ | ✅ `plugin-auth` (better-auth) | +| **Auth Config** | Authentication | ✅ | ❌ | ✅ | ✅ `plugin-auth` | +| **Position** | Authentication | ✅ | ❌ | ✅ | ✅ `plugin-auth` + `plugin-security` | +| **Organization** | Authentication | ✅ | ❌ | ✅ | ✅ `plugin-auth` (`tenancy` service) | | **Policy** | Authentication | ✅ | ❌ | ✅ | 📋 Plugin | -| **SCIM** | Authentication | ✅ | ❌ | ✅ | 📋 Plugin | +| **SCIM** | Authentication | ✅ | ❌ | ✅ | ⚠️ `plugin-auth`, opt-in via `OS_SCIM_ENABLED` | | **Permission** | Authorization | ✅ | ✅ Phase-1 | ✅ | ✅ `plugin-security` | -| **Sharing** | Authorization | ✅ | ❌ | ✅ | 📋 Plugin | +| **Sharing** | Authorization | ✅ | ❌ | ✅ | ✅ `plugin-sharing` | | **RLS** | Authorization | ✅ | ✅ Phase-1 (tenant + owner) | ✅ | ✅ `plugin-security` | **Notes:** -- All security protocols (identity + permission) are delivered by a single `auth` plugin — matching `CoreServiceName` +- Security is delivered by three cooperating plugins, not one: `plugin-auth` (identity, `auth` + `tenancy` services), `plugin-security` (permissions, FLS, RLS, tenant wall), and `plugin-sharing` (OWD, sharing rules, per-record shares, share links) - Client SDK supports bearer token header — but token validation requires the auth plugin - Auth route (`/auth/*`) only appears in Discovery when the auth plugin is registered -- Fine-grained authorization (RLS, sharing, territory) is internal to the auth plugin -- **Phase-1 RBAC enforcement is live end-to-end**: REST → ObjectQL → SecurityPlugin middleware now receives a populated `ExecutionContext` (userId, tenantId, positions, permissions). Tenant isolation is enforced as a Layer 0 tenant wall (`plugin-security/tenant-layer.ts`, ADR-0095 D1) that AND-composes `organization_id == current_user.organization_id` ahead of and independently of business RLS — the earlier wildcard `tenant_isolation` RLS policy on `member_default` was retired (an OR-merged business policy could widen it). The default `member_default` set still ships per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the global tables that lack an `organization_id` column. The earlier `tenantField` indirection (RLS expressions written against an abstract `tenant_id` column then rewritten to the configured physical column at compile time) was removed — the placeholder, the column name, and `RLSUserContext.organization_id` are now the same name end-to-end. The legacy `objectql.registerTenantMiddleware` (hardcoded `where.tenant_id` injection that pre-dated SecurityPlugin) has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics now uses the same reusable read scope via `security.getReadFilter`, so dataset-bound dashboards/reports do not bypass RLS. Verified cross-organization isolation on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_position_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 do not depend on any fall-open — they carry a declaration-derived `publicFormGrant` (ADR-0056 Option A). +- Fine-grained authorization (RLS, sharing) lives in `plugin-security` / `plugin-sharing`, not in the auth plugin. Territory-style access is expressed as an RLS dynamic-membership set (`ExecutionContext.rlsMembership`, e.g. `id in current_user.territory_account_ids`) rather than as a dedicated territory module +- **Phase-1 RBAC enforcement is live end-to-end**: REST → ObjectQL → SecurityPlugin middleware now receives a populated `ExecutionContext` (userId, tenantId, positions, permissions). Tenant isolation is enforced as a Layer 0 tenant wall (`plugin-security/tenant-layer.ts`, ADR-0095 D1) that AND-composes `organization_id == current_user.organization_id` ahead of and independently of business RLS — the earlier wildcard `tenant_isolation` RLS policy on `member_default` was retired (an OR-merged business policy could widen it). The default `member_default` set still ships per-object overrides `sys_organization_self` (`id == current_user.organization_id`) and `sys_user_self` (`id == current_user.id`) for the global tables that lack an `organization_id` column. The earlier `tenantField` indirection (RLS expressions written against an abstract `tenant_id` column then rewritten to the configured physical column at compile time) was removed — the placeholder, the column name, and `RLSUserContext.organization_id` are now the same name end-to-end. The legacy `objectql.registerTenantMiddleware` (hardcoded `where.tenant_id` injection that pre-dated SecurityPlugin) has been removed; SecurityPlugin is the sole authority for tenant isolation. Analytics now uses the same reusable read scope via `security.getReadFilter`, so dataset-bound dashboards/reports do not bypass RLS. Verified cross-organization isolation on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_position_permission_set`. **Anonymous traffic is always denied** (ADR-0056 D2). The deployment-wide opt-out is gone: `api.requireAuth` was retired in `@objectstack/spec` 18 (#3963) and is now a tombstoned key that fails validation rather than reopening the data plane. The single decision lives in `@objectstack/core` (`security/anonymous-deny.ts`, 401 `UNAUTHENTICATED`), and every surface that legitimately serves a session-less caller derives its own narrow authorization from a declaration instead: control-plane paths via the auth-gate allowlist, public forms via `publicFormGrant` (ADR-0056 Option A), share links via a capability token validated then read as SYSTEM, `book.audience: 'public'` reads via the audience gate, and MCP via an OAuth token or API key. - **OWD / sharing-model enforcement is live and proven end-to-end (ADR-0056)**: `private`, `public_read`, `public_read_write`, and `controlled_by_parent` are enforced through `plugin-sharing` + `plugin-security` and verified by dogfood proofs over the real HTTP stack. `object.sharingModel` accepts the canonical OWD vocabulary only (`private` / `public_read` / `public_read_write` / `controlled_by_parent`) — the legacy `read` / `read_write` / `full` aliases were removed from the enum (ADR-0090 D4), and an unset `sharingModel` on a custom object resolves to `private` (ADR-0090 D1). RLS owner policies resolve `current_user.email` in addition to `id` / `organization_id` / `positions` (#2054). Permission sets may declare `isDefault: true` as the install-time suggestion to bind the set to the built-in `everyone` position (ADR-0090 D5, superseding the ADR-0056 D7 fallback-profile mechanism). **A sharing rule must state its criteria** (#3896): all three write paths reject a match-all shape, a stored criteria-less rule matches nothing, and its materialised grants are revoked on the next reconcile. --- @@ -345,9 +348,11 @@ describe that separate runtime. **Features Implemented:** - TestSuite, TestScenario, TestStep schemas ✅ -- HTTP adapter for testing ✅ -- MSW integration for browser mocking ✅ -- QA runner in core ✅ +- HTTP adapter for testing ✅ (`packages/core/src/qa/http-adapter.ts`) +- QA runner in core ✅ (`packages/core/src/qa/runner.ts`) +- Conformance/dogfood harnesses in `@objectstack/verify` and `packages/qa` (`dogfood`, `downstream-contract`, `http-conformance`) ✅ + +There is no MSW package in this repo — browser API mocking is a devDependency of the sibling `objectui` frontend, and `@objectstack/driver-memory` is what backs those in-browser fixtures. --- @@ -413,29 +418,31 @@ describe that separate runtime. ### Phase 8: Automation (Plugin) 🟡 **IN PROGRESS** - [x] Flow Engine Plugin - [x] Approval Node Plugin +- [x] Trigger Registry Plugins — `@objectstack/trigger-api`, `@objectstack/trigger-record-change`, `@objectstack/trigger-schedule` (including the time-relative trigger) - [ ] ETL Pipeline Plugin -- [ ] Trigger Registry Plugin +- [ ] Sync Plugin ### Phase 9: Security (Plugin) 🟡 **PHASE-1 LANDED** - [x] Authentication Plugin (`@objectstack/plugin-auth`, better-auth) - [x] Authorization Plugin — `@objectstack/plugin-security` enforces CRUD/FLS/RLS in the ObjectQL middleware chain; REST → ObjectQL now propagates `ExecutionContext` end-to-end (Phase-1) -- [x] Row-Level Security — tenant isolation is a Layer 0 tenant wall (`plugin-security/tenant-layer.ts`, ADR-0095 D1) that AND-composes `organization_id == current_user.organization_id` independently of business RLS (the earlier wildcard `tenant_isolation` policy on `member_default` was retired); `member_default` still applies per-object overrides `sys_organization_self` / `sys_user_self`. The earlier `tenantField` rewrite indirection was removed: RLS column, placeholder, and `RLSUserContext.organization_id` use the same canonical name end-to-end. Policy `enabled: false` is enforced at the compiler choke point — a disabled policy no longer contributes its OR-branch grant (#3980) — and the void `priority` key is removed (#3990) +- [x] Row-Level Security — tenant isolation is a Layer 0 tenant wall (`plugin-security/tenant-layer.ts`, ADR-0095 D1) that AND-composes `organization_id == current_user.organization_id` independently of business RLS (the earlier wildcard `tenant_isolation` policy on `member_default` was retired); `member_default` still applies per-object overrides `sys_organization_self` / `sys_user_self`. The earlier `tenantField` rewrite indirection was removed: RLS column, placeholder, and `RLSUserContext.organization_id` use the same canonical name end-to-end. Policy `enabled: false` is enforced at the compiler choke point — a disabled policy no longer contributes its OR-branch grant (#3980) — and the void `rowLevelSecurity[].priority` key was removed in `@objectstack/spec` 17.0.0 (#3896 security audit) - [x] Analytics RLS bridge — `@objectstack/service-analytics` auto-bridges to `security.getReadFilter(object, context)` and fails closed when read-scope resolution cannot be safely applied - [x] Multi-tenancy — verified cross-organization isolation on `pnpm dev:crm` (Alice@OrgAlpha vs. Bob@OrgBeta only see their own records across `sys_organization`, `sys_member`, `sys_user`, and `sys_*_permission_set` link tables) - [x] Legacy `objectql.registerTenantMiddleware` removed — SecurityPlugin is now the sole tenant-isolation authority - [x] Organization-Wide Defaults / sharing model — `private`, `public_read`, `public_read_write`, and `controlled_by_parent` enforced via `plugin-sharing` + `plugin-security`, proven by dogfood over the real HTTP stack (ADR-0056). Canonical vocabulary only — legacy aliases removed from the enum (ADR-0090 D4); unset custom-object OWD resolves to `private` (ADR-0090 D1) - [x] Sharing Rule evaluator — criteria rules re-evaluated on `afterInsert` / `afterUpdate` (`plugin-sharing/rule-hooks.ts`); enforced recipients are user / position / `unit_and_subordinates` (business-unit-subtree widening, ADR-0057 D5 / ADR-0090 D3); owner-type rules and group/guest recipients remain `[experimental — not enforced]` - [x] Everyone-baseline suggestion — a permission set may set `isDefault: true` as the install-time suggestion to bind it to the built-in `everyone` position; resolved per-request as an additive baseline, no fallback cliff (ADR-0090 D5) -- [x] Default-deny for anonymous traffic — the global default-deny **flip landed** (ADR-0056 D2): `requireAuth` defaults to `true`, explicit `requireAuth: false` opt-outs warn at boot, and public forms self-authorize via `publicFormGrant` (Option A) +- [x] Default-deny for anonymous traffic — the global default-deny landed (ADR-0056 D2) and the `api.requireAuth` opt-out was then **removed** in `@objectstack/spec` 18 (#3963): the key is tombstoned and rejected at parse time, the deny decision is centralised in `@objectstack/core` `security/anonymous-deny.ts`, and public forms self-authorize via `publicFormGrant` (Option A) - [ ] Studio RLS visual editor - [ ] Per-user×org permission cache - [ ] Audit UI / denied-access logging -### Phase 10: AI Integration 📋 **PLANNED** -- [ ] Agent Framework -- [ ] RAG Pipeline -- [ ] NLQ Engine -- [ ] Model Registry +### Phase 10: AI Integration 🟡 **IN PROGRESS** +- [x] RAG Pipeline — `@objectstack/service-knowledge` plus the `knowledge-memory`, `knowledge-ragflow`, and `embedder-openai` plugins ship in this repo +- [x] MCP surface — `@objectstack/mcp` exposes the platform to any BYO-AI client +- [ ] Agent Framework (ObjectOS runtime; not in the open-source framework) +- [ ] NLQ Engine (ObjectOS runtime) +- [ ] Model Registry (ObjectOS runtime) --- @@ -448,8 +455,8 @@ describe that separate runtime. | **Data** | 16 | Core modeling, hooks, and query engine fully implemented; document/mapping/external-lookup still pending | | **UI** | 10 | Studio/ObjectUI render most authored surfaces (🟡); full cross-surface renderer parity in progress | | **API** | 14 | REST/HTTP/discovery/batch fully implemented; OData pending; GraphQL removed from the plan | -| **System** | 39 | Logging, audit, job, translation, metrics, notification implemented; several governance services pending | -| **Auth** (plugin) | 10 | Permission + RLS live (`plugin-security`); identity/sharing/territory still plugin-pending | +| **System** | 39 | Logging, audit, job, translation, metrics, cache, notification implemented; encryption partial (local crypto provider); several governance services pending | +| **Auth** (plugin) | 10 | Identity/organizations live (`plugin-auth`), permission + RLS live (`plugin-security`), OWD + sharing rules live (`plugin-sharing`); SCIM is opt-in | | **Automation** (plugin) | 7 | Flow, workflow, approval, webhook, and triggers implemented; ETL/Sync pending | | **AI** | 12 | Agents, model registry, conversation, tools, RAG implemented (ObjectOS runtime + open knowledge plugins); cost/predictive/DevOps-agent pending | | **Integration** | 7 | REST/OpenAPI/MCP/Slack connectors, file storage, and queue implemented; GitHub/Vercel connectors pending | @@ -457,7 +464,7 @@ describe that separate runtime. ### Implementation Coverage -Implementation spans every layer of the platform. Core infrastructure, data modeling, the REST API, client SDKs, security (Phase-1), automation, AI, and integration all have shipping implementations. Remaining gaps are concentrated in specific protocols (OData, cross-surface UI renderer parity, sharing/territory authorization, ETL/Sync, and a handful of governance and AI-cost services) rather than entire layers. Refer to the per-layer tables above for protocol-level status. +Implementation spans every layer of the platform. Core infrastructure, data modeling, the REST API, client SDKs, security (Phase-1), automation, AI, and integration all have shipping implementations. Remaining gaps are concentrated in specific protocols (OData, cross-surface UI renderer parity, ETL/Sync, the mapping/document/external-lookup data protocols, and a handful of governance and AI-cost services) rather than entire layers. Refer to the per-layer tables above for protocol-level status. ### Core Functionality Status @@ -471,38 +478,37 @@ Implementation spans every layer of the platform. Core infrastructure, data mode | **Metadata System** | ⚠️ | Partial (`sys_metadata` persistence via `MetadataPlugin`; without it the kernel's in-memory fallback fills the slot and discovery reports it `degraded`) | | **HTTP Caching** | ✅ | Yes | | **Testing Tools** | ✅ | Yes | -| **UI Rendering** | ❌ | No | +| **UI Rendering** | 🟡 | Partial (Studio/ObjectUI render authored surfaces; cross-surface renderer parity in progress) | | **Workflows** | ✅ | Yes (plugin: `service-automation`) | -| **Security** | ⚠️ | Partial (Phase-1 RBAC/RLS via `plugin-security`) | +| **Security** | ⚠️ | Partial (Phase-1 RBAC/FLS/RLS via `plugin-security`; OWD + sharing rules via `plugin-sharing`; identity via `plugin-auth`) | | **AI Features** | ⚠️ | Partial (ObjectOS runtime + open knowledge plugins) | --- ## Package Feature Matrix -| Feature | spec | core | objectql | runtime | client | client-react | cli | metadata | hono | memory | msw | -|:--------|:----:|:----:|:--------:|:-------:|:------:|:------------:|:---:|:--------:|:----:|:------:|:---:| -| **Protocol Definitions** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **Microkernel** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **Plugin System** | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **Service Registry** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **Event Bus** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **Logging** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **Schema Registry** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **ObjectQL Engine** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **Protocol Implementation** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **REST Server** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **Endpoint Generation** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **HTTP Server** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | -| **Client SDK** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **Query Builder** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **React Hooks** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | -| **CLI Commands** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | -| **Config Validation** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | -| **Metadata Loading** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | -| **File Watching** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | -| **Database Driver** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ⚠️ | ❌ | -| **API Mocking** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | +| Feature | spec | core | objectql | runtime | client | client-react | cli | metadata | hono | memory | +|:--------|:----:|:----:|:--------:|:-------:|:------:|:------------:|:---:|:--------:|:----:|:------:| +| **Protocol Definitions** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Microkernel** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Plugin System** | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Service Registry** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Event Bus** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Logging** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Schema Registry** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **ObjectQL Engine** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Protocol Implementation** | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **REST Server** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Endpoint Generation** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **HTTP Server** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | +| **Client SDK** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Query Builder** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **React Hooks** | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | +| **CLI Commands** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | +| **Config Validation** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | +| **Metadata Loading** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | +| **File Watching** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | +| **Database Driver** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ⚠️ | > The REST server, endpoint generation, and data/meta/batch endpoints are implemented in the `@objectstack/rest` package (not shown as a column above); `@objectstack/runtime` re-exports `RestServer` from it. diff --git a/content/docs/releases/v9.mdx b/content/docs/releases/v9.mdx index 6ebd3cbd26..6dbaf4c579 100644 --- a/content/docs/releases/v9.mdx +++ b/content/docs/releases/v9.mdx @@ -204,6 +204,11 @@ Coming in the next framework release: published packages default to Marketplace immediately, with private visibility reserved for targeted distribution. +> Note: this has since shipped — `os package publish --visibility` accepts +> `private` / `org` / `marketplace` and defaults to `org` ("auto-visible / +> installable across your organization's environments"). See the +> [Publish & preview guide](/docs/deployment/publish-and-preview). + ## Notable fixes - Publishing pending drafts by ref so package-less drafts can't get stuck in diff --git a/scripts/role-word-baseline.json b/scripts/role-word-baseline.json index aa9c9e8817..ad6ee3a2ae 100644 --- a/scripts/role-word-baseline.json +++ b/scripts/role-word-baseline.json @@ -30,7 +30,6 @@ "content/docs/protocol/objectql/security.mdx": 1, "content/docs/protocol/objectui/record-alert.mdx": 1, "content/docs/protocol/objectui/widget-contract.mdx": 3, - "content/docs/releases/implementation-status.mdx": 1, "content/docs/releases/index.mdx": 1, "content/docs/releases/v13.mdx": 15, "content/docs/releases/v14.mdx": 5,